Основы ADO .NET
DataSet в свободном полете
DataSet myDataSet = new DataSet(); // Пустой объект - представитель класса DataSet. DataTable myTable = new DataTable(); // Пустая таблица создана. myDataSet.Tables.Add(myTable); // И подсоединена к объекту класса DataSet. // Определение структуры таблицы. Это мероприятие можно было // провести и до присоединения таблицы. DataColumn shipColumn = new DataColumn("Ships"); myDataSet.Tables[0].Columns.Add(shipColumn); // Прочие столбцы подсоединяются аналогичным образом. // Таким образом формируются поля данных таблицы. // Внимание! После того как определена структура таблицы, // то есть определены ВСЕ СТОЛБЦЫ таблицы, от имени этой конкретной // таблицы порождается объект-строка. Этот объект сразу располагается // непосредственно в таблице. Для каждой определенной таблицы // метод NewRow() порождает строку // (последовательность значений соответствующего типа). // Для непосредственного // редактирования вновь созданной строки запоминается ее ссылка. // Работать со строкой через эту ссылку проще, чем с массивом // строк таблицы. DataRow myRow = myDataSet.Tables[0].NewRow(); // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Остается заполнить строку таблицы содержательной информацией. // При этом может быть использован любой источник данных. // В данном примере предполагается наличие объекта типа ArrayList // с именем ShipCollection. for (int i = 0; i < ShipCollection.Count; i++) { myRow.Item[Counter] = ShipCollection[i]; } // :::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: // Заполненный объект - представитель класса DataRow добавляется // к набору Rowsкласса DataTable. myDataSet.Tables[0].Rows.Add(myRow);Листинг 18.7.
Естественно, что данная последовательность действий может повторяться сколь угодно много раз, пока не будут созданы и не заполнены все члены объекта DataSet.
Далее будет рассмотрен пример использования объекта DataSet для прочтения информации из текстового файла. При этом используется метод класса string Split(), который обеспечивает выделение из исходной строки подстрок с их последующим преобразованием в символьные массивы. Критерием выделения подстроки является массив символов-разделителей или целочисленное значение, определяющее максимальную длину подстроки.
Применение класса DataSet
using System; using System.Data; namespace mergeTest { class Class1 { static void Main(string[] args) { // Создается объект DataSet. DataSet ds = new DataSet("myDataSet"); // Создается таблица. DataTable t = new DataTable("Items"); // Столбцы таблицы – это особые объекты. // Имя первого столбца – id, тип значения – System.Int32. DataColumn c1 = new DataColumn("id", Type.GetType("System.Int32")); c1.AutoIncrement=true; // Имя второго столбца – Item, тип значения – System.Int32. DataColumn c2 = new DataColumn("Item", Type.GetType("System.Int32")); // Сборка объекта DataSet: // Добавляются объекты-столбцы... t.Columns.Add(c1); t.Columns.Add(c2); // А вот массив столбцов (здесь он из одного элемента) // для организации первичного ключа (множества первичных ключей). DataColumn[] keyCol= new DataColumn[1]; // И вот, собственно, как в таблице задается множество первичных ключей. keyCol[0]= c1; // Свойству объекта t передается массив, содержащий столбцы, которые // формируемая таблица t будет воспринимать как первичные ключи. t.PrimaryKey=keyCol; // А что с этими ключами будет t делать? А это нас в данный момент // не касается. Очевидно, что методы, которые обеспечивают контроль // над информацией в соответствии со значениями ключей, уже где-то // "зашиты" в классе DataTable. Как и когда они будут выполняться – // не наше дело. Наше дело – указать на столбцы, которые для данной // таблицы будут ключевыми. Что мы и сделали. // Таблица подсоединяется к объекту ds – представителю класса DataSet. ds.Tables.Add(t); DataRow r; // В таблицу, которая уже присоединена к // объекту ds DataSet, добавляется 10 rows. for(int i = 0; i <10;i++) { r=t.NewRow(); r["Item"]= i; t.Rows.Add(r); } // Принять изменения. // Так производится обновление DataSet'а. // Сведения о новых изменениях и добавлениях будут фиксироваться // вплоть до нового обновления. ds.AcceptChanges(); PrintValues(ds, "Original values"); // Изменение значения в первых двух строках. t.Rows[0]["Item"]= 50; t.Rows[1]["Item"]= 111; t.Rows[2]["Item"]= 111; // Добавление еще одной строки. // Судя по всему, значение первого столбца устанавливается автоматически. // Это ключевое поле со значением порядкового номера строки. r=t.NewRow(); r["Item"]=74; t.Rows.Add(r); // Объявляем ссылку для создания временного DataSet. DataSet xSet; // ДЕКЛАРАЦИЯ О НАМЕРЕНИЯХ КОНТРОЛЯ ЗА КОРРЕКТНОСТЬЮ ЗНАЧЕНИЙ СТРОКИ. // Вот так добавляется свойство, содержащее строку для описания // ошибки в значении. Наш DataSet содержит одну строку с описанием. // Это всего лишь указание на то обстоятельство, что МЫ САМИ // обязались осуществлять // некоторую деятельность по проверке чего-либо. Чтобы не забыть, // в чем проблема, // описание возможной ошибки (в свободной форме!) добавляем // в свойства строки, // значения которой требуют проверки. t.Rows[0].RowError= "over 100 (ЙЦУКЕН!)"; t.Rows[1].RowError= "over 100 (Stupid ERROR!)"; t.Rows[2].RowError= "over 100 (Ну и дела!)"; // Но одно дело – декларировать намерения, а другое – осуществлять // контроль. // Проблема проверки корректности значения – наша личная проблема. // Однако о наших намерениях контроля за значениями становится // известно объекту – представителю DataSet! PrintValues(ds, "Modified and New Values"); // Мы вроде бы согласились проводить контроль значений. // Даже декларировали некий принцип проверки. // Однако ничего само собой не происходит. // Так вот, // // ЕСЛИ В ТАБЛИЦУ БЫЛИ ДОБАВЛЕНЫ СТРОКИ ИЛИ ИЗМЕНЕНЫ ЗНАЧЕНИЯ СТРОК // И // МЫ ОБЯЗАЛИСЬ КОНТРОЛИРОВАТЬ ЗНАЧЕНИЯ СТРОК В ТАБЛИЦЕ, // // то самое время организовать эту проверку... // Критерий правильности значений, естественно, наш! // Алгоритмы проверки – тоже НАШИ! // Единственное, чем нам может помочь ADO .NET, – это выделить // подмножество строк таблицы, // которые были добавлены или модифицированы со времени последнего // обновления нашего объекта - представителя DataSet'а, if(ds.HasChanges(DataRowState.Modified | DataRowState.Added)& ds.HasErrors) { // И для этого мы воспользуемся методом, который позволяет обеспечить // выделение подмножества добавленных и // модифицированных строк в новый объект DataSet'а. // Use GetChanges to extract subset. xSet = ds.GetChanges(DataRowState.Modified|DataRowState.Added); PrintValues(xSet, "Subset values"); // Insert code to reconcile errors. In this case, we'll reject changes. // Вот, собственно, код проверки. Все делается своими руками. foreach(DataTable xTable in xSet.Tables) { if (xTable.HasErrors) { foreach(DataRow xRow in xTable.Rows) { // Выделенное подмножество проверяем на наличие // ошибочного значения (для нас все, что больше 100, – // уже ошибка!) Console.Write(xRow["Item"] + " "); if((int)xRow["Item",DataRowVersion.Current ]> 100) { // Находим ошибку в строке, сообщаем о ней, Console.WriteLine("Error! – " + xRow.RowError); // Возвращаем старое значение... xRow.RejectChanges(); // Отменяем значение свойства - уведомителя о возможных // ошибках для данной строки... xRow.ClearErrors(); } else Console.WriteLine("OK."); } } } PrintValues(xSet, "Reconciled subset values"); // Сливаем измененные и откорректированные строки в основной // объект – DataSet // Merge changes back to first DataSet. ds.Merge(xSet); PrintValues(ds, "Merged Values"); } } // А это всего лишь вывод содержимого DataSet'а. private static void PrintValues(DataSet ds, string label) { Console.WriteLine("\n" + label); foreach(DataTable t in ds.Tables) { Console.WriteLine("TableName: " + t.TableName); foreach(DataRow r in t.Rows) { foreach(DataColumn c in t.Columns) { Console.Write("\t " + r[c] ); } Console.WriteLine(); } } } } }Листинг 18.8.
Подсоединенные объекты модели ADO .NET. Провайдеры
Поставщик данных для приложения (Провайдер) – объект, предназначенный для обеспечения взаимодействия приложения с хранилищем информации (базами данных).
Естественно, приложению нет никакого дела до того, где хранится и как извлекается потребляемая приложением информация. Для приложения источником данных является тот, кто передает данные приложению. И как сам этот источник эту информацию добывает – никого не касается.
Источник данных ( Data Provider ) – это набор взаимосвязанных компонентов, обеспечивающих доступ к данным. Функциональность и само существование провайдера обеспечивается набором классов, специально для этой цели разработанных.
ADO .NET поддерживает два типа источников данных, соответственно, два множества классов:
- SQL Managed Provider (SQL Server.NET Data Provider) – для работы с Microsoft SQL Server 7.0 и выше. Работает по специальному протоколу, называемому TabularData Stream (TDS) и не использует ни ADO, ни ODBC, ни какую-либо еще технологию. Ориентированный специально на MS SQL Server, протокол позволяет увеличить скорость передачи данных и тем самым повысить общую производительность приложения;
- ADO Managed Provider (OleDb.NET Data Provider) – для всех остальных баз данных. Обеспечивает работу с произвольными базами данных. Однако за счет универсальности есть проигрыш по сравнению с SQL Server Provider, так что при работе с SQL Server рекомендовано использовать специализированные классы.
В следующих разделах приводится описание составных элементов провайдера.
Connection
Объект – представитель класса Connection представляет соединение с источником (базой) данных и обеспечивает подключение к базе данных. Visual Studio .NET поддерживает два класса:
- SQLConnection (обеспечивает подключение к SQL Server 7.0 и выше),
- OleDbConnection (обеспечивает подключение к прочим вариантам БД).
Компонента Connection (независимо от того, представителем какого класса она является) имеет свойство ConnectionString, в котором фиксируется вся необходимая для установления соединения с БД информация. Кроме того, поддерживается ряд методов, позволяющих обрабатывать данные с применением транзакций.
Свойства объекта Connection позволяют:
- задавать реквизиты пользователя;
- указывать расположение источника данных.
Методы объекта позволяют управлять соединением с источником данных.
В процессе соединения с помощью объекта – представителя класса OleDbConnection (аналогично SQLConnection) создается и инициализируется соответствующий объект с использованием одного из вариантов конструктора и строки соединения.
Формирование строки и последовательность действий при инициализации объекта соединения – дело техники. Главное – это чтобы свойство ConnectionString в результате получило бы ссылку на строку символов, содержащую необходимую для установления соединения информацию.
// Объявили и определили объект соединения. private System.Data.OleDb.OleDbConnection oleDbConnection1; this.oleDbConnection1 = new System.Data.OleDb.OleDbConnection(); :::::::::: // Настроили объект соединения. // Для наглядности необходимая для установления соединения // информация представлена серией строк. oleDbConnection1.ConnectionString = @"Jet OLEDB:Global Partial Bulk Ops=2;" + @"Jet OLEDB:Registry Path=;" + @"Jet OLEDB:Database Locking Mode=1;" + @"Data Source=""F:\Users\Work\CS\DB.BD\DBTests\Lights.mdb"";" + @"Jet OLEDB:Engine Type=5;" + @"Jet OLEDB:Global Bulk Transactions=1;" + @"Provider=""Microsoft.Jet.OLEDB.4.0"";" + // Поставщик @"Jet OLEDB:System database=;" + @"Jet OLEDB:SFP=False;" + @"persist security info=False;" + @"Extended Properties=;" + @"Mode=Share Deny None;" + @"Jet OLEDB:Create System Database=False;" + @"Jet OLEDB:Don't Copy Locale on Compact=False;" + @"Jet OLEDB:Compact Without Replica Repair=False;" + @"User ID=Admin;" + @"Jet OLEDB:Encrypt Database=False";Листинг 18.9.
Свойства, методы и события класса OleDbConnection
Текущее состояние соединения кодируется как элемент перечисления ConnestionState. Список возможных значений представлен ниже.
Dispose | Перегружен. Переопределен. Освобождает ресурсы, используемые объектом OleDbConnection |
Finalize (унаследовано от Component ) | Переопределен. Освобождает неуправляемые ресурсы и выполняет другие действия по очистке, перед тем как пространство, которое использует Component, будет восстановлено сборщиком мусора. В языках C# и C++ для функций финализации используется синтаксис деструктора |
GetService (унаследовано от Component ) | Возвращает объект, представляющий службу, которую предоставляет Component или его Container |
MemberwiseClone (унаследовано от Object ) | Создает неполную копию текущего Object |