Статья
Версия для печати
Обсудить на форуме
DataReader и доступ к данным


В очередной статье цикла мы познакомимся с первым объектом, который позволит нам обращаться к данным, хранящимся в реляционной базе данных, - DataReader.

Поскольку DataReader и Command связаны довольно тесно, в прошлой статье я уже вскользь упомянул о нем. Теперь пришла пора ознакомиться с DataReader более детально.

Назначение DataReader

Как уже упоминалось ранее, если результатом выполнения команды SQL посредством Command является набор данных, то в результате выполнения метода Command.ExecuteReader создается объект DataReader, посредством которого можно получить доступ к результату.

По своей сути DataReader представляет собой поток записей только на чтение, по которому можно передвигаться только вперед, последовательно от записи к записи. Работа с ним очень напоминает чтение данных из последовательного файла.

В процессе чтения в каждый момент времени в оперативной памяти клиентского компьютера находится одна-единственная текущая запись. Поэтому DataReader требует очень мало памяти клиента, даже если общий объем выборки данных велик.

Конечно же, за подобную экономию всегда приходится чем-то расплачиваться. В данном случае ценой является постоянно открытое соединение с источником данных, что создает нагрузку на сеть, а также требует от сервера баз данных выделения некоторых ресурсов на поддержку соединения с клиентом (если источником данных является SQL-сервер).

Кроме того, каждый объект DataReader занимает для работы отдельное соединение (Connection), так что при необходимости одновременно читать данные из нескольких источников вам придется создать несколько одновременно открытых соединений с источником данных. После того, как на данном соединении был открыт DataReader, никакие другие операции не могут быть выполнены с данным соединением вплоть до закрытия DataReader методом Close.

Перечисленные особенности DataReader накладывают определенные ограничения на способы использования этого объекта. Однако существует немало приложений, для которых этой функциональности вполне достаточно. Например, если требуется выбрать часть данных из базы и вывести ее для отчета в виде списка, DataReader вполне успешно справится с этой задачей, не нагружая при этом оперативную память клиентского компьютера, даже если предстоит обработать значительный объем данных.

Поддержка DataReader на стороне сервера  Если вам уже доводилось работать ранее с базами данных посредством ADO, OLE DB либо ODBC, несомненно, вы знакомы с понятием курсора.

В ADO.NET мы уже явно не встречаем понятие курсора, поскольку здесь отсутствует Recordset, который был непосредственно связан с ним. Однако на нижнем уровне, разумеется, принцип взаимодействия с серверами баз данных не изменился, и курсоры по-прежнему присутствуют, хотя это и скрыто от программиста.

Как нетрудно догадаться, DataReader для доступа к данным использует курсор только на чтение вперед на стороне сервера (read-only forward-only server-side cursor).

Создание DataReader

Среди методов DataReader вы не обнаружите конструктора. Таким образом, самостоятельно создать объект вам не удастся. Впрочем, в подобном создании все равно было бы мало смысла, поскольку нужно еще и наполнить объект данными от источника, а у нас пока еще нет к ним доступа.

Создается DataReader в результате вызова метода ExecuteReader объекта Command:

Код:

OleDbDataReader myReader = myCommand.ExecuteReader(CommandBehavior.CloseConnection);


После этого можно начинать работу с объектом.

Свойства DataReader

Свойства объекта DataReader представлены в следующей таблице:

СвойствоТипОписание
DepthintГлубина вложения текущей строки
FieldCountintКоличество столбцов в текущей строке
HasRowsboolУказывает, что DataReader содержит одну или более строк.
IsClosedboolУказывает, что DataReader закрыт
ItemobjectЗначение столбца во внутреннем формате. Для C# является индексатором DataReader.
RecordsAffectedintКоличество строк, измененных, вставленных или удаленных в результате выполнения оператора SQL.


Depth имеет смысл, если строка принадлежит иерархическому набору (поскольку посредством OLE DB можно получить доступ не только к реляционным базам данных). MS SQL Server не поддерживает иерархию, поэтому SqlDataReader.Depth всегда возвращает 0.

Посредством Item можно получить доступ к значению столбца данных текущей записи как по его номеру, так и по имени. Оба формата являются допустимыми:

Код:

int i = (int)myReader.Item[1];

string s = (string)myReader.Item[Name];


(разумеется, при условии, что не нарушены правила преобразования типов).

Методы DataReader

CloseЗакрывает DataReader
GetDataTypeNameВозвращает имя типа данных источника
GetFieldTypeВозвращает тип данных источника
GetNameВозвращает имя столбца, заданного по номеру
GetOrdinalВозвращает номер столбца, заданного по имени
GetSchemaTableВозвращает объект DataTable, содержащий метаданные о столбцах
GetValueВозвращает значение столбца, заданного по номеру, во внутреннем формате
GetValuesВозвращает все столбцы текущей строки
IsDBNullВозвращает значение, указывающее, содержит ли столбец несуществующее значение
NextResultОсуществляет переход на следующий результирующий набор
ReadОсуществляет переход к следующей записи


Close - завершает работу DataReader. Следует обязательно вызывать этот метод по завершении работы с объектом, поскольку в противном случае объект Connection отсается недоступным для других операций с источником данных.

GetDataTypeName и  GetFieldType  позволяют определить название типа данных и собственно тип данных столбца с заданным номером.

GetName и GetOrdinal являются взаимно дополняющими. Первый позволяет определить имя столбца по его номеру, второй, соответственно, номер столбца по его имени.

IsDBNull фактически единственный способ определить, что в данном столбце хранится пустое значение, поскольку среди типов данных .NET Framework отсутствует подходящий тип для его представления.

NextResult  используется для перехода к следующему набору данных, если команда SQL или хранимая процедура возвращают в качестве результата несколько наборов данных. Изначально DataReader позиционируется на первый набор, для доступа к следующим необходим явный переход.

Read  необходим для последовательного продвижения по строкам набора данных. Изначально сразу же после создания DataReader текущая позиция чтения устанавливается перед первой строкой набора данных, поэтому перед использованием данных необходимо вызвать Read.


Кроме указанных методов, DataReader включает также набор методов для доступа к значениям столбцов, которые возвращают значения заданного типа. Использование этих методов тривиально и ясно из названия, поэтому я просто перечислю их в таблице для справки:

GetBooleanGetCharsGetFloatGetInt64
GetByteGetDateTimeGetGuidGetString
GetBytesGetDecimalGetInt16GetTimeSpan
GetCharGetDoubleGetInt32

Помимо них, реализация SqlDataReader включает также набор методов, возвращающих специфические для MS SQL Server типы. Поскольку они в основном дублируют общие типы данных, не вижу смысла заострять здесь на них внимание.

Пример программы

В прошлой статье мы были вынуждены обойтись без примера работающей программы, поскольку не располагали средством для доступа к результатам выполнения запросов к базе данных. Теперь самое время закрепить на практике навыки, полученные при изучении материала прошлой и данной статей. Мы создадим простое, но законченное приложение, которое сначала выводит список стран, в которых имеются клиенты гипотетической фирмы NorthWind, предлагает нам ввести название одной из стран, а затем выводит список клиентов, проживающих в данной стране.

Разумеется, намного проще строить подобные приложения с использованием многочисленных волшебников, которые готовы облегчить вам работу, позволяя несколькими движениями мыши создавать целые разделы готового кода. Однако описывать работу с ними  весьма неблагодарное занятие, поскольку внятно передать словами последовательность движений и кликов мыши не столь просто, а загромождать статью длинной чередой скриншотов не хотелось бы. Посему пример представляет собой консольное приложение, в котором экземпляры всех нужных для работы с данными объектов создаются непосредственно перед использованием, программно. Хотя это и несколько более громоздко при разработке приложения, зато такой код может быть использован даже теми, кто лишен возможности использовать IDE VS.NET, а вынужден довольствоваться работой с компилятором C# из состава .NET Framework посредством командной строки.

Чтобы избежать возможных проблем с различными кодировками кириллицы, все консольные сообщения в программе выводятся на английском языке. Их число невелико, поэтому надеюсь, что это не вызовет затруднений у читателей, испытывающих трудности с этим языком.

Несмотря на простоту, программа включает в себя все необходимое для построения несложного отчета о содержимом базы данных: создание соединения, запросы данных (простой и параметризованный), вывод полученных данных, разрыв соединения с источником данных и освобождение ресурсов.

Конечно же, реальные задачи обычно сложнее, поскольку включают в себя не только последовательное чтение данных, но и их произвольную выборку, а также модификацию, удаление, добавление. Для выполнения этих действий требуются объекты, с которыми мы познакомимся в следующих статьях.

Код:
using System;
using System.Data;
using System.Data.OleDb;

namespace DataReaderSample
{
  class DataReaderSample
  {
    [STAThread]
    static void Main(string[] args)
    {
      Console.WriteLine("Command & DataReader Sample Program.\n");
      // создание и открытие Connection
      string strCnn = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=C:\Program Files\Microsoft Visual Studio\VB98\NWIND.MDB";
      OleDbConnection cnn = new OleDbConnection();
      cnn.ConnectionString = strCnn;
      cnn.Open();

      // создание Command
      OleDbCommand cmd = new OleDbCommand();
      cmd.Connection = cnn;

      // сначала выберем все страны, в которых имеются клиенты
      string strCmd = "SELECT DISTINCT Country FROM Customers ORDER BY Country";
      cmd.CommandText = strCmd;
      cmd.CommandType = CommandType.Text;

      OleDbDataReader rdr = cmd.ExecuteReader();
      // вывести список стран
      Console.WriteLine("The list of countries customers live at");
      while (rdr.Read())
        // используем порядковый номер столбца результирующего набора
        Console.WriteLine(rdr.GetString(0));
      rdr.Close(); // не забываем закрывать DataReader после использования!
      Console.WriteLine();

      Console.Write("Select one you want to get a customers list from: ");
      string ans = Console.ReadLine();

      // а теперь попробуем построить и выполнить запрос с параметром
      strCmd = "SELECT ContactName, CompanyName, ContactTitle FROM Customers WHERE (Country = ?)";
      cmd.CommandText = strCmd;
      OleDbParameter par = new OleDbParameter();
      par.OleDbType = OleDbType.VarWChar;
      par.Value = ans;
      cmd.Parameters.Add(par);

      rdr = cmd.ExecuteReader();
      Console.WriteLine("Customers from " + ans + " are:");
      while(rdr.Read())
        // в данном случае для разнообразия используем имена столбцов в базе данных, а не их номера
        Console.WriteLine(rdr["ContactName"].ToString() + ", "
          + rdr["CompanyName"].ToString() + ", "
          + rdr["ContactTitle"].ToString());

      // освобождаем ресурсы (хотя они автоматически освободятся при завершении программы,
      // лучше все же приучаться явно освобождать их сразу же после использования)
      rdr.Close();
      cnn.Close();

      Console.ReadLine();
    }
  }
}

Alf, 01/09/2004

Версия для печати
Обсудить на форуме