Кондратьев Денис
visualdesign.ru
Технология ADO.NET, в отличие
от своих предшественников ADO и OLE DB, была разработана специально для
использования в web приложениях, где не бывает постоянных соединений с БД.
Традиционная работа с данными в ADO.NET строится по такой схеме: создается
соединение Connection, затем оно открывается методом Open, создается объект
команда Command, инкапсулирующая SQL команду, она исполняется, а соединение
затем закрывается. Такой подход обеспечивает поточный доступ к результатам
запросов. Т.е. читая данные с помощью DataReader, вы не можете перепрыгнуть
через несколько записей или вернуться к предыдущей. Поточный доступ имеет
максимальную производительность.
ADO.NET была разработана для доступа к
данным без реального соединения с БД. При этом все данные размещаются в
оперативной памяти. Работа с отсоединенными данными в ADO.NET осуществляется с
помощью классов из пространства имен System.Data.

Самый выжный класс при работе с
отсоединенными данными – это DataSet. После того как получены результаты запроса
с помощью объекта DataAdapter и сохранены в DataSet' e, соединение между БД и
объектом DataSet перестает существовать. Изменения в DataSet не сказываются на
БД и наоборот. Класс DataSet включает в себя набор таблиц DataTable и связей
между таблицами DataRelation. Класс DataTable включает набор строк DataRow,
набор столбцов таблицы DataColumn, и наборы отношений ChildRelations и
ParentRelations между столбцами разных таблиц базы данных. Класс DataRow
инкапсулирует информацию о строке в таблице и состоянии строки Deleted,
Modified, New и Unchanged. Класс Constraint используется для сохранения
целостности данных в таблицах.
Преимущества работы с отсоединенными
данными:
1. не требуется постоянное соединение с БД, что нужно,
например, для web приложений;
2. облегчается создание многоуровневых
приложений. Если приложение обращается к БД с помощью объектов уровня DAL, то
бизнес объектам на уровне BLL можно передавать DataSet. Обновления в БД также
могут передаваться с помощью DataSet;
3. облегчается сортировка, поиск,
фильтрация и навигация по данным;
4. облегчается работа с реляционными
данными;
5. есть возможность кешировать изменения. Объект DataSet
позволяет кешировать изменения и затем с помощью DataAdapter передавать все
изменения в БД за 1 раз;
6. тесная интеграция с XML. Содержимое DataSet
можно загружать и сохранять в виде XML документов.
Использование объектов DataSet
Ниже приводится пример использования
объекта DataSet. Все примеры в статье используют БД Northwind БД MS SQL Server.
stringconn ="Provider=SQLSQL;Data Source=(local)\NetSDK;InitialCatalog=Northwind;Trusted_Connection=Yes;";
string
query = "SELECT CustomerID, CompanyName, ContactName, Phone
FROM Customers";
SqlDataAdapter da = new SqlDataAdapter(query, conn);
DataSet ds = new DataSet();
da.Fill(ds,
"Customers");
Вначале создаем строку соединения с БД и SQL запрос
для получения данных. DataAdapter помещает результаты запроса в соответствующую
таблицу DataTable, находящуюся в DataSet. С помощью перегруженного метода Fill
можно помещать данные и напрямую в DataTable. Для одной таблицы можно несколько
раз вызывать метод Fill, однако, если вызывать метод Fill для DataSet без явного
указания имени таблицы, то эти данные будут помещаться в разные таблицы
(например, "Table", "Table1", "Table2",). Для доступа к данным, находящимся в
таблицу есть свойство Rows, возвращающее набор объектов DataRow
DataTable table = ds.Tables[0];
DataRow row = table.Rows[0];
Console.WriteLine("OrderID:
" + row["OrderID"]);
Console.WriteLine("CustomerID: " + row["CustomerID"]);
У объекта DataRow есть свойство Item, возвращающее содержимое
конкретного поля. Можно указать имя поля как в примере кода, или целое число,
соответствующее порядковому номеру столбца. При поиске по индексу данные немного
возвращаются быстрее, чем при поиске по названию столбца.
Изменение содержимого DataTable
Для добавления новой строки в таблицу
существует метод NewRow(), который создает новый объект DataRow, но не добавляет
запись в таблицу. Полем новой записи задаются значения по умолчанию или Null,
если значение по умолчанию не задано.
DataRow row = ds.Tables["Customers"].NewRow();
row["CustomerID"] =
"VASYA";
row["Company"] = "Компания";
row["ContactName"] = "Вася
Пупкин";
row["Phone"] =
"11-22-33";
ds.Tables["Customers"].Rows.Add(row);
Есть еще один
способ добавления записей в таблицу – метод LoadDataRow(). Первый параметр этого
метода – это массив значений, элементы которого соответствуют столбцам таблицы.
Второй – позволяет управлять значением свойство RowState новой записи. Если
передать false, то значение этого свойства будет Added, как и при добавлении
новой записи методом Add.
object[]aValues = {"VASYA", "Компания", "Вася Пупкин",
"11-22-33"};
ds.Tables["Customers"].LoadDataRow(aValues, false);
Редактирование
существующей записи можно следующим образом:
DataRow row = ds.Tables["Customers"][0];
row["ContactName"] = "Вася
Пупкин";
или с помощью метода ItemArray:
object[] items = {"VASYA", "Компания", "Вася Пупкин", "11-22-33"};
DataRow row = ds.Tables["Customers"][0];
Row.ItemArray =
items;
Для удаления записи нужно вызывать метод DataRow.Delete().
При этом удаленная запись фактически не удаляется из DataTable, а помечается как
удаленная – ее свойство RowState принимает значение Deleted. Если же до вызова
метода Delete значение RowState было Added, тотолько в этом случае строка
удаляется из таблицы.
Использование столбцов с автоинкрементном
Для поддержки столбцов с
автоматическим увеличением значения у DataColumn есть свойства AutoIncrement,
AutoIncrementSeed и AutoIncrementStep . Для того, чтобы создать столбец
DataColumn с автоинкрементом нужно установить свойство AutoIncrement в true.
Значение счетчика будет начинаться с AutoIncrementSeed (если это не нарушает
корректность данных в таблице) и увеличиваться на значение AutoIncrementStep.
Для столбцов с автоинкрементном рекомендуется устанавливать свойство ReadOnly в
true. Ниже приведен пример вставки в таблицу столбца с автоинкрементом
DataColumn column = table.Columns.Add("CustomerID",
typeof(Int32));
column.AutoIncrement = true;
column.AutoIncrementSeed =
1;
column.AutoIncrementStep = 1;
Если вы планируете сносить
изменения в БД методом DataAdapter.Update(), то рекомендуется задать свойствам
AutoIncrementSeed и AutoIncrementStep значения -1. Это гарантирует, что ADO.NET
будет генерировать значения метки, которых нет в БД. При вызове метода Update в
БД внесутся уже уникальные значения.
Работа с DataSet в среде Visual Studio .NET
Создавать объекты DataSet
можно как программно, так и с помощью среды Visual Studio. Для второго случая
предназначена вкладка Data в панели инструментов. Для начала нужно перетащить на
web или win-форму объект SqlDataAdapter.

Затем в мастере создать новое подключение к БД, указав БД
Northwind и способ доступа к БД как «Use SQL statements». После этого выберете
таблицу Customers и отметьте все столбцы таблицы.

Повторите процесс для таблицы
Order, используя уже готовое соединение. Затем щелкните правой кнопкой мыши по
панели с созданными DataAdapter 'ами, выберете из контекстного меню команду
Generate DataSet.

Отметьте галочками оба DataAdapter
и нажмите на ОК.

Добавленный объект DataSet
отобразился в панели компонентов. Мы добавили т.н. DataSet со строгим контролем
типов (типизированный DataSet ), о котором будем говорить позже. Структуру
данных можно посмотреть в созданном файле с расширением xsd.

Работа с реляционными данными
Работа с реляционными данными
осуществляется с помощью объектов класса DataRelation, связующие колонки
DataColumn двух таблиц DataTable. Создать такой объект можно следующим образом:
ds.Relationships.Add(new DataRelation("CustomersOrders", ds.Tables["Customers"].Columns["CustomerID"], ds.Tables["Orders"].Columns["CustomerID"]);
Если нужно определить отношение, основанное на нескольких
столбцах, нужно использовать конструктор, принимающий массив объектов
DataColumn:
DataTable tblParent = ds.Tables["ParentTable"];
DataColumn[] colsParent = new DataColumn[] {tblParent.Columns["ParentColumn1"],
tblParent.Columns["ParentColumn2"]};
DataTable tblChild = ds.Tables["ChildTable"];
DataColumn[] colsChild = new DataColumn[] {tblChild.Columns["ChildColumn1"],
tblChild.Columns["ChildColumn2"]};
ds.Relationships.Add(new DataRelation("ParentChild", colsParent, colsChild));
Работа со связанными данными
Основным применением объектов
DataRelation является поиск связанных данных. Однако сам объект DataRelation не
предоставляет такой функциональности, она реализуется методами класса DataRow:
GetChildRow(), GetParentRow() и GetParentRows().
Для поиска дочерних
записей используется метод GetChildRow() соответсвующего объекта DataRow. Ему
передается ему имя объекта DataRelation, определяющего отношение. Этот метод
возвращает дочерние записи в виде массива объектов DataRow :
DataRow row
= ds.Tables["Customers"].Rows[0];
foreach (DataRow rowOrder in row.GetChildRows("CustomersOrders"))
Console.WriteLine(rowOrder["OrderID"] +
rowOrder["OrderDate"] + "n");
Найти родительскую запись с помощью
DataRelation можно методом DataRow.GetParentRow ().
DataRow rowOrder = ds.Tables["Orders"].Rows[0];
Console.WriteLine("t"
+ rowOrder.GetParentRow("CustomersOrders")["ContactName"] +
"n");
В случае отношения «один ко многим» найти все родительские
записи конкретного объекта DataRow можно с методом GetParentRows(), также
принимающего название отношения и возвращающего массив родительских объектов.
Добавление объекта DataRelation в Visual Studio .NET
Для добавления
отношения DataRelation в DataSet со строгим контролем типов нужно перейти к
структуре данных, щелкнув по файлу с расширением xsd, определяющего структуру
DataSet. Щелкнув на родительский DataTable правой кнопкой нужно в контекстном
меню выбрать Add / New Relation.

В диалоговом окне Edit Relation
задаются родительские и дочерние объекты.

При щелчке на ОК, в XML Sheme
Designer появляется графическое представление DataRelation – линия, соединяющая
2 объекта.

Поиск записей
При запросах к БД часто требуется найти запись по ее
первичному ключу. Для поиска записей в DataTable существует метод
DataRowCollection.Find ().
table.PrimaryKey = new DataColumn[] {tbl.Columns["CustomerID"]};
DataRow row = tbl.Rows.Find("VASYA");
if (row == null)
Console.WriteLine("Запись
не найдена");
else
Console.WriteLine(row["CompanyName"]);
Если первичный ключ
состоит из нескольких колонок, то в метод Find() можно передать массив объектов.
Для выполнения поиска по произвольному SQL запросу у DataTable есть метод
Find(). Например, для получения имен всех покупателей, находящихся в Уфе, можно
воспользоваться таким кодом
DataRow[] rows = table.Select("City =
'Уфа'");
foreach (DataRow row in rows)
Console.WriteLine(row["CompanyName"] + "n");
Если нужно,
чтобы возвращаемые данные были отсортированы, можно воспользоваться
перегруженной версией этого метода. Она имитирует раздел ORDER BY SQL -запроса.
DataRow[] rows = table.Select("City = 'Уфа'", "CompanyName
ASC");
Работа с объектами DataSet со строгим контролем типов
Ранее мы уже
создавали объекты DataSet со строгим контролем типов. Рассмотрим, какие новые
функции появились у типизированного DataSet.
Добавление записи
Все классы, соответствующие таблицам DataTable в
DataSet, позволяют добавлять новые записи двумя способами. Метод
New[Имя_таблицы]Row() возвращает новую строку в таблице:
DataSetTest ds = new DataSetTest();
DataSetTest.CustomersDataTable tblCustomers = ds.Customers;
DataSetTest.CustomersRow rowCustomer = tblCustomers.NewCustomersRow();
rowCustomer.CustomerID =
"VASYA";
rowCustomer.CompanyName = "Компания";
rowCustomer.ContactName =
"Вася Пупкин";
rowCustomer.Phone =
"11-22-33";
tblCustomers.AddCustomersRow(rowCustomer);
Второй
способ – это использовать метод Add[Имя_таблицы]Row(), принимающий массив
объектов, соответсвующих полям записи:
DataSetTest ds = new DataSetTest();
DataSetTest.CustomersDataTable.AddCustomersRow("VASYA", "Компания",
"Вася Пупкин", "11-22-33");
Поиск записи
Поиск записи в таблице тоже претерпел изменения.
Например, для поиска в таблице Order Details по составному ключу появился метод
следующий метод
DataSetTest.Order_DetailRow = tblDetails.FindByOrderIDProductID(112233,
456);
Также типизированные DataSet поддерживают русские буквы в
названиях таблиц и полей таблиц.
ds.Статистика.AddСтатистикаRow(15.9, "компания", true );
Передача обновлений в БД
Для передачи изменений в БД используется
объекты DataAdapter. Для создания логики обновления БД можно использовать один
из трех вариантов:
1. вручную сконфигурировать DataAdapter в период
разработки;
2. воспользоваться объектом CommandBulder в период выполнения;
3. использовать в период разработки DataAdapter Configuration Wizard.
Ручное конфигурирование DataAdapter
DataAdapter имеет 3 свойства для
передачи изменений в БД: InsertCommand, UpdateCommand и DeleteCommand. Значения
этих свойства должны быть заданы до вызова метода DataAdapter.Update() с учетом
того, какие изменения были внесены в DataSet. Когда метод Update() добавляет,
обновляет или удаляет строки в таблице, он вызывает соответствующую команду.
Если в ходе ваших действий вы только добавляли строки в таблицу, то и
задать вы должны только значение InsertCommand. Ниже приведен код для такой
ситуации:
string conn = "Provider = SQLSQL; Data Source = (local)\NetSDK;
Initial Catalog= Northwind; Trusted_Connection=Yes;";
string query =
"SELECT CustomerID, CompanyName, ContactName, Phone FROM Customers";
SqlDataAdapter da = new SqlDataAdapter(query, conn);
DataSet table = new DataSet(); da.Fill(table, "Customers");
// добавляем
новые строки в таблицу
...
// создаем команду для вставки новых
записей
query = "INSERT INTO Customers
(CustomerID, CompanyName, ContactName, Phone) VALUES
(?, ?, ?, ?)";
SqlCommand cmd = new SqlCommand(query, conn);
SqlParameterCollection pc = cmd.Parameters;
pc.Add("CustomerID", SqlType.Integer, 0,
"CustomerID");
pc.Add("CompanyName", SqlType.String, 0,
"CompanyName");
pc.Add("ContactName", SqlType.String, 0,
"ContactName");
pc.Add("Phone", SqlType.String, 0,
"Phone");
da.InsertCommand = cmd;
da.Update(table);
Подобный
подход может применяться и при передаче обновлений в БД при помощи хранимых
процедур. Допустим, что имя хранимой процедуры для добавления нового покупателя
это spInsertCustomer. Тогда наш код измениться следующим образом:
OleDbCommand cmd = new OleDbCommand("spInsertCustomer", conn);
cmd.CommandType = CommandType.StoredProcedure;
OleDbParameterCollection pc = cmd.Parameters;
pc.Add("CustomerID", SqlType.Integer, 0,
"CustomerID");
pc.Add("CompanyName", SqlType.String, 0,
"CompanyName");
pc.Add("ContactName", SqlType.String, 0,
"ContactName");
pc.Add("Phone", SqlType.String, 0, "Phone");
Использование CommandBuilder
CommandBuilder генерирует SQL запросы
обращаясь к БД для получения метаданных о таблицах. Для использования
CommandBuilder необходимо выполнение следующих условий: 1. запрос возвращает
данные только из одной таблицы;
2. в таблице определен первичный ключ;
3.
первичный ключ есть в результатах запроса.
Ниже приведен пример
использования CommandBuilder.
string con ="Provider=SQLOLEDB;Data Source=(local)\NetSDK;InitialCatalog=Northwind;Trusted_Connection=Yes;";
string
query = "SELECT OrderID, ProductID, Quantity, UnitPrice
FROM [Order Details]";
OleDbDataAdapter da = new OleDbDataAdapter(strSQL, strConn);
OleDbCommandBuilder cb = new OleDbCommandBuilder(da);
da.InsertCommand
= cb.GetInsertCommand();
da.Update(tbl);
Использование мастера Data Adapter Configuration Wizard
Одно из
предназначений мастера – создание логики обновления базы данных. На четвертом
шаге мастера по команде Advanced Options выводится диалог, в котором можно
указать создавать или нет команды для вставки, обновления и удаления записей в
БД.

На третьем же шаге работы мастер
можно указать способ передачи обновления в БД: с помощью SQL запросов, с помощью
существующей хранимой процедуры или создание новой хранимой процедуры. Никаких
дополнительный действий для создания логики обновления не нужно.
