Лекция 13

advertisement
Лекция 13
Команды манипулирования данными часто вызываются с параметрами,
при этом некоторые элементы команды задаются только в период выполнения. Рассмотрим приложение для учета товаров в книжном магазине. В
нем предусмотрен поиск книг по названию, который можно реализовать
запросом к БД на основе следующего оператора SQL:
SELECT * FROM Books WHERE (Title LIKE <value>)
Требуемое название вводится пользователем в период выполнения.
Поскольку это значение заранее неизвестно, необходим механизм передачи
введенного значения оператору SQL во время выполнения программы.
Каждый параметр представляется экземпляром класса SqlParameter,
OleDbParameter и так далее, в зависимости от типа провайдера. Параметры
хранятся в свойстве-наборе Parameters объекта Command. В период
выполнения значения параметров считываются из этого свойства и
подставляются в оператор SQL либо передаются хранимой процедуре.
Набор Parameters состоит из объектов Parameter соответствующего типа.
Рассмотрим некоторые свойства класса Parameter.
 DbType (не отображается в дизайнере)
 OleDbType (только для объекта OleDbParameter)
 SQLType (только для SQLParameter)
 Direction
 ParameterName
 Precision
 Scale
 Size
 Value
Свойства DbType и OleDbType объекта OleDbParameter взаимосвязаны.
Первое представляет тип параметра в соответствии с общей системой типов
(CTS) платформы .NET, а второе – как он представлен в БД. Это необходимо,
2
поскольку не все БД совместимы с CTS. Объект Parameter выполняет
преобразование параметров из типа, используемого в приложении, в тип,
используемый в БД. Поскольку эти свойства взаимосвязаны, при изменении
значения одного из них значение другого автоматически меняется и
преобразуется в соответствующий поддерживаемый тип. Аналогичо связаны
свойства DbType и SqlType объектов SqlParameter: свойство SqlType
указывает тип БД SQL, представленный параметром.
Свойство Direction объекта параметра определяет, является ли этот
параметр входным или выходным. Возможные значения этого свойства –
Input, Output, InputOutput или ReturnValue.
На элементы набора Parameters можно ссылаться по индексу либо по
имени, заданному свойством ParameterName. Ниже показаны два способа
установки значения первого по счету параметра с именем «myParameter».
oleDbCommand.Parameters[0].Value = “Hello, World !”;
oleDbCommand.Parameters[“myParameter”].Value = “Goodbye for now”;
Свойства Precision, Scale и Size определяют длину и точность значения
параметров. Precision и Scale применяются с числовыми десятичными
параметрами. Они определяют разрядность и длину дробной части значения
свойства Value соответственно. Size применяется с двоичными и строковыми
параметрами и представляет максимальную длину такого поля. Свойство
Value содержит значение параметра.
Если свойство CommandType объекта Command установлено в Text,
необходимо предусмотреть поля подстановки для всех параметров оператора
SQL. В случае объекта OleDbCommand поле подстановки обозначается
символом “?”, например:
SELECT EmpId, Title, FirstName, LastName
FROM Employees
WHERE (FirstName = ?) AND (LastName = ?)
Порядок заполнения полей определяется порядком элементов набора
Parameters.
3
С объектом SqlCommand можно применять именованные параметры.
Чтобы создать поле подстановки для именованного параметра, необходимо
указать имя параметра (как оно задано свойством ParameterName), предварив
его символом «@». Например:
SELECT Empld, Title, FirstName, LastName
FROM Employees
WHERE (Title = @Title)
Выполнить с помощью объекта Command команду, не возвращающую
значений или возвращающую единственное значение, можно, например, так:
object O = myCommand.ExecuteScalar ();
6.2.3. Динамическое формирование SQL-запросов
Иногда необходимая конструкция запроса SQL становится известной
только в период выполнения программы. Например, запрос может содержать
введенную пользователем строку для поиска или возвращать столбцы и
таблицы, определенные программно в период выполнения. Для решения
таких задач команды создаются и настраиваются в период выполнения.
Первый шаг в этом направлении – формирование строки команды. Можно
предварительно подготовить заготовку команды и при необходимости в
период выполнения заменять некоторые значения строковыми переменными.
Для объединения строк можно использовать оператор конкатенации.
Строковые значения, передаваемые базе данных в конструкции WHERE,
необходимо заключать в апострофы. Если апостроф содержится внутри
такого значения, следует его удвоить.
У каждого провайдера данных есть конструктор класса Command,
позволяющий устанавливать свойства CommandText и Connection при
создании экземпляра Command. После установки этих свойств остается
открыть соединение и выполнить команду. Например:
public void DeleteRecord (string aString)
{
4
string Cmd;
Cmd = “DELETE FROM Employees WHERE Name = '” + aString + “'”;
OleDbCommand myCommand = new OleDbCommand (Cmd, myConnection);
myConnection.Ореn ();
myCommand.ExecuteNonQuery ();
myConnection.Close ();
}
6.2.4. Применение объекта DataReader
Для использования объекта Command с запросами, возвращающими
несколько значений, можно применить метод ExecuteReader. Он возвращает
объект
DataReader,
который
обеспечивает
быстрое
и
эффективное
последовательное однонаправленное чтение данных. Объект DataReader
ориентирован на использование постоянного соединения и, пока он
существует, требует монопольного доступа к активному соединению.
Объект DataReader нельзя создать напрямую, это делается путем вызова
метода ExecuteReader объекта Command. Подобно другим членам классов
провайдеров данных, у каждого класса DataProvider есть собственный класс
DataReader. Объект OleDbCommand возвращает OleDbDataReader, а объект
SqlCommand – SqlDataReader, например:
System.Data.OleDb.OleDbDataReader myOleDbReader;
System.Data.SqlClient.SqlDataReader mySqlReader;
myOleDbReader = myOleDbCommand.ExecuteReader ();
mySqlReader = mySqlCommand.ExecuteReader ();
Получив ссылку на объект DataReader, можно просматривать записи,
загружая нужные данные в память. У нового объекта DataReader указатель
чтения устанавливается на первую запись результирующего набора. Чтобы
сделать ее доступной, следует вызвать метод Read. Если запись доступна,
5
метод Read переводит указатель объекта DataReader к следующей записи и
возвращает true, в противном случае – false.
При чтении записи с помощью объекта DataReader значения отдельных
полей доступны через индексатор по индексу либо по имени поля, например:
while ( myDataReader.Read () )
{
object myObject = myDataReader[3];
object myOtherObject = myDataReader[“CustomerID”];
}
При таком способе доступа DataReader предоставляет все значения в виде
объектов. Однако этот класс имеет собственные методы для извлечения
типизированных данных. Имена этих методов образуются из префикса Get и
имени типа извлекаемых данных. Например, метод извлечения значения типа
Boolean называется GetBoolean:
bool myBool = myDataReader.GetBoolean (3);
При использовании такого способа извлечения данных необходимо
указывать порядковый номер, а не имя поля. Если известно только имя поля,
можно определить его порядковый номер методом GetOrdinal, например:
int iCustomerID = myDataReader.GetOrdinal (“CustomerID”);
string Customer = myDataReader.GetString (iCustomerID);
Прочитав данные с помощью DataReader, необходимо вызвать метод
Close, иначе объект DataReader будет удерживать монопольный доступ к
активному соединению, сделав его недоступным другим объектам:
myDataReader.Close ();
Если
перед
вызовом
метода
ExecuteReader
установить
свойство
CommandBehavior в СloseConnection, то по окончании чтения метод Close
явно вызывать не требуется.
Следующий пример демонстрирует перебор записей результирующего
набора для вывода содержимого одного из столбцов в окне Console. Этот
пример
предполагает
наличие
объекта
OleDbCommand
с
именем
6
“myOleDbCommand”, свойство Connection которого использует соединение с
именем “myConnection”.
myConnection.Ореn ();
System.Data.OleDb.OleDbDataReader myReader =
myOleDbCommand.ExecuteReader ();
while ( myReader.Read () )
{
Console.WriteLine ( myReader[“Customers”].ToString () );
}
myReader.Close ();
myConnection.Close ();
Если свойство CommandType объекта Соmmand установлено в Text, то
можно одной командой получить несколько результирующих наборов. Для
этого нужно в свойство CommandText поместить несколько SQL-операторов,
разделенных точкой с запятой, например:
SELECT * FROM Accounts;
SELECT * FROM Creditors
Операторы выполняются последовательно. В этом случае объект
DataReader вернет несколько наборов. Первый результирующий набор
DataReader возвращает автоматически. Чтобы получить доступ к другим
результирующим наборам, необходимо вызывать метод NextResult. Он
возвращает
false
при
попытке
обращения
к
несуществующему
результирующему набору, при этом устанавливает указатель объекта
DataReader на первый результирующий набор. Рассмотрим пример.
do
{
while ( myReader.Read () )
{
……………………………..
}
}
while ( myReader.NextResult () );
7
6.2.5. Создание и использование объекта DataAdapter
Объекты класса DataAdapter обеспечивают связь между источником
данных и объектом DataSet, управляя обменом данных и контролируя их
передачу. Объект DataAdapter способен извлекать данные, заполнять
объекты DataSet и обновлять содержимое источника данных.
В Visual Studio .NET доступны несколько видов DataAdapter, включая
SqlDataAdapter, оптимизированный для взаимодействия с БД MS SQL Server,
и OleDbDataAdapter, предоставляющий доступ к любому источнику данных,
поддерживаемому OleDbProvider.
Как правило, обменом данными между объектом DataTable и таблицей БД
управляет отдельный объект DataAdapter. В DataSet может быть несколько
таблиц, поэтому для каждой из них необходимо создавать собственный
объект DataAdapter.
Проще всего создать DataAdapter в окне Server Explorer. Узел Data
Connections содержит все доступные соединения с данными. Раскрыв узел,
можно получить дополнительные сведения о подключенной БД, в том числе
список имеющихся таблиц, просмотров и хранимых процедур. Для создания
объекта DataAdapter, представляющего таблицу, можно перетащить таблицу
из окна Server Explorer в окно дизайнера. Таким образом будет создан объект
DataAdapter соответствующего типа (SqlDataAdapter, OleDbDataAdapter, … )
с корректными значениями свойств SelectCommand, UpdateCommand,
InsertCommand и DeleteCommand.
Кроме того, предусмотрена настройка объекта DataAdapter для работы с
подмножеством столбцов таблицы. Для этого достаточно раскрыть узел таблицы и выбрать нужные столбцы, удерживая нажатой клавишу Ctrl. Затем
перетащить выбранные столбцы в окно дизайнера – получится объект
DataAdapter, сконфигурированный для доступа к выбранным столбцам.
8
Объект DataAdapter можно также создать, перетащив соответствующий
объект DataAdapter с панели Toolbox в окно дизайнера, после чего
автоматически будет вызван мастер Data Adapter Configuration.
Объект DataAdapter инкапсулирует функциональность, необходимую для
заполнения DataSet данными и обновления БД. Чтобы заполнить DataSet
данными, следует вызвать метод Fill объекта DataAdapter. Этот метод
выполняет команды, заданные свойством SelectCommand, на соединении,
указанном свойством Connection. Методу Fill необходимо передать целевой
объект, которым может быть DataSet либо DataTable, например:
DataSet myDataSet = new DataSet ();
myDataAdapter.Fill (myDataSet);
При выполнении команды, вызванной методом Fill, соединение с БД
открывается только на время извлечения данных, после чего сразу же
закрывается.
Таким
образом,
после
извлечения
данные
становятся
отсоединенными и ими можно манипулировать в коде независимо от БД, а
при необходимости ее можно обновлять.
Обычно для каждой таблицы данных создается отдельный объект
DataAdapter. Если нужно загрузить содержимое нескольких таблиц в один
объект DataSet, следует задействовать несколько объектов DataAdapter. Один
и тот же объект DataSet может быть передан методу Fill каждого объекта
DataAdapter, при этом всякий раз создается новый объект DataTable,
заполняется данными и добавляется к объекту DataSet.
6.2.6. Типизированные объекты DataSet
Стандартные объекты DataSet не являются строго типизированными.
Каждый
элемент
данных
доступен
в
форме
объекта.
ADO
.NET
поддерживает также типизированные объекты DataSet. На таблицы и поля
такого объекта DataSet можно ссылаться по именам, соответствующим
реальным именам таблиц и столбцов; их значения доступны в виде значений
9
соответствующих типов, а не объектов. Это дает ряд преимуществ. Вопервых, код программы становится более понятным и его удобнее сопровождать. Во-вторых, ошибки из-за несоответствия типов обнаруживаются в
период компиляции, а не в период выполнения – это экономит время,
необходимое для тестирования. Наконец, полные имена членов наборов
можно заменять их дружественными именами, при этом в период разработки
имена типизированных членов данных отображаются в окнах среды
разработки. Ниже приводятся эквивалентные примеры, использующие
соответственно нетипизированный и типизированный объекты DataSet.
string myOrder = (string) dsOrders.Tables[“Orders”].Rows[0][“OrderID”];
string myOrder = dsOrders.Orders[0].OrderID;
Типизированный DataSet – это экземпляр другого класса, производного от
DataSet. Структура этого класса определяется файлом схемы XML (XSDфайлом), в котором описаны особенности объекта DataSet, включая имена
таблиц и столбцов. Для создания типизированного объекта DataSet требуется
файл схемы. Его можно создать только для заранее известной структуры
данных. Типизированный класс DataSet можно сгенерировать автоматически,
выбрав в VS в меню Data команду Generate DataSet.
Резюме
 Объект Connection реализует подключение к БД.
 Объект Command представляет команду SQL или ссылку на хранимую
процедуру. Объекты OleDbCommand и SqlCommand содержат три
метода для выполнения команд: ExecuteNonQuery, ExecuteScalar и
ExecuteReader.
 Параметры – это значения, необходимые для выполнения команд,
представленных объектами Command. При использовании объекта
OleDbCommand поля для подстановки значений параметров в
операторах SQL обозначаются знаком вопроса. Объект SqlCommand
допускает применение только именованных параметров.
10
 Объекты DataReader
предоставляют доступ
к подсоединенным
данным. Данные, полученные через объект DataReader, доступны
только для последовательного однонаправленного чтения. DataReader
требует монопольного доступа к соединению с источником данных.
 Объекты
DataReader
имеют
методы
для
извлечения
строго
типизированных данных.
 Объекты DataAdapter осуществляют взаимодействие между БД и
объектом DataSet, управляя выполнением команд для заполнения
DataSet данными из БД и обновления БД содержимым объекта DataSet.
 Типизированные
объекты
DataSet
–
это
экземпляры
классов,
производных от DataSet. Эти объекты основаны на схеме XML. На
таблицы и поля таких объектов DataSet можно ссылаться по их
дружественным именам.
Download