![]() |
Технология Клиент-Сервер 2003'1 |
||||||
|
Настоящая статья посвящена созданию приложений с помощью универсального механизма доступа к данным ADO.NET, который является составной частью платформы Microsoft .NET Framework. В этой статье мы обсудим отличия ADO.NET от других универсальных механизмов доступа к данным, рассмотрим поддерживаемые этим механизмом архитектуры приложений, а также обсудим классы ADO.NET, реализующие доступ к данным при различных архитектурах приложений.
Доступ к данным из клиентских приложений может осуществляться несколькими способами.
Подавляющее большинство систем управления базами данных содержит в своем составе библиотеки, предоставляющие специальный прикладной программный интерфейс (Application Programming Interface, API) для доступа к данным этой СУБД. Обычно такой интерфейс представляет собой набор функций, вызываемых из клиентского приложения, либо набор классов, которые могут быть в нем использованы. В приложениях, использующих настольные СУБД, эти функции и методы классов обеспечивают чтение и запись файлов базы данных. В приложениях, использующих серверы баз данных, эти функции и классы инициируют передачу запросов серверу баз данных и получение от сервера результатов выполнения запросов или кодов ошибок, интерпретируемых клиентским приложением. Библиотеки, содержащие API для доступа к данным серверной СУБД, обычно входят в состав ее клиентского программного обеспечения, устанавливаемого на компьютерах, где функционируют клиентские приложения, работающие с этой СУБД.
В течение последних нескольких лет в Windows-версиях клиентского программного обеспечения наиболее популярных СУБД (в частности, СУБД производства компаний Microsoft и Oracle) присутствуют также и COM-серверы, предоставляющие объекты для доступа к данным и метаданным этих СУБД.
Функции, классы или COM-объекты, реализующие клиентский API, как правило, хорошо документированы. Их использование зачастую является наиболее эффективным с точки зрения производительности способом манипуляции данными в приложении. Однако в этом случае созданное приложение сможет взаимодействовать только с СУБД этого производителя. Замена этой СУБД на другую (например, с целью расширения объема хранимых данных или смены архитектуры информационной системы) повлечет за собой переписывание значительной части кода клиентского приложения — в большинстве случаев клиентские API и объектные модели не подчиняются никаким стандартам и различны для разных СУБД.
Другой способ манипуляции данными в приложении базируется на применении так называемых универсальных механизмов доступа к данным. Универсальный механизм доступа к данным обычно реализован в виде библиотек и дополнительных модулей, называемых драйверами или провайдерами. Библиотеки содержат некий стандартный набор функций или классов, обычно соответствующий той или иной спецификации, а дополнительные модули, специфичные для той или иной СУБД, реализуют непосредственное обращение к функциям или классам клиентского API этой СУБД.
Отметим, что достоинством универсальных механизмов является возможность применения одного и того же абстрактного API (в виде функций, COM-серверов и т.п.) для доступа к разным типам СУБД. Поэтому приложения, использующие универсальные механизмы доступа к данным, могут быть относительно легко модифицированы в случае смены СУБД. При этом нередко модификация затрагивает не столько код приложения, сколько настройки доступа к данным, содержащиеся в реестре или внешних файлах. Однако подобная универсальность достигается за счет отказа от использования уникальной функциональности, специфичной для конкретной СУБД, а в ряде случаев – снижением производительности приложений и некоторым усложнением их поставки. В этом случае в состав дистрибутива нужно включать библиотеки, ответственные за реализацию универсальных механизмов, драйверы для используемых СУБД, а также обеспечивать настройки, необходимые для их правильного функционирования.
Можно указать следующие наиболее популярные универсальные механизмы доступа к данным:
Open Database Connectivity (ODBC);
OLE DB/ActiveX Data Objects (ADO);
Java Database Connectivity (JDBC).
JDBC – это интерфейс доступа к базам данных, входящий в состав платформы Java. Он позволяет использовать как "чистые" драйверы, написанные на языке Java и работающие на уровне call-level API (поддерживаются СУБД Oracle, Sybase, Informix, DB2 и т.п.), и JDBC-ODBC Bridge для использования существующих ODBC-драйверов.
Перечисленные выше механизмы являются промышленными стандартами, поддерживаемыми всеми ведущими производителями СУБД. Ниже мы кратко остановимся на их особенностях. Это позволит понять, какой выигрыш можно получить посредством применения ADO.NET.
ODBC (Open Database Connectivity) – это набор интерфейсов для доступа к реляционным данным, удовлетворяющий стандарту CLI (Call Level Interface) ANSI/ISO, разработанный компанией Microsoft. Для доступа к данным конкретной СУБД с помощью ODBC, кроме собственно клиентской части этой СУБД, нужен ODBC Administrator (приложение, позволяющее определить, какие источники данных доступны для данного компьютера с помощью ODBC, и описать новые источники данных), и ODBC-драйвер для доступа к этой СУБД.
ODBC-драйвер представляет собой динамически загружаемую библиотеку (DLL), которую клиентское приложение может загрузить в свое адресное пространство и использовать экспортируемые ей функции для доступа к источнику данных. Понятно, что для каждой используемой СУБД нужен собственный ODBC-драйвер, так как ODBC-драйверы используют функции клиентских API, специфические для каждой конкретной СУБД.
С помощью ODBC можно манипулировать данными любой СУБД и даже данными, не имеющими прямого отношения к базам данных. Примером могут служить данные в файлах электронных таблиц Microsoft Excel. Всё, что нужно для того, чтобы получать данные из Excel – это иметь соответствующий ODBC-драйвер. При этом данные должны быть такими, чтобы их можно было либо представить в табличном виде, либо преобразовать в таковой. Для манипуляции данными можно использовать непосредственные вызовы ODBC API; кроме того, драйверы ODBC доступны и с помощью других универсальных механизмов доступа к данным, таких как OLE DB и ADO.
Говоря об ODBC, нельзя не отметить, что спецификация ODBC подразумевает несколько стандартов на ODBC-драйверы (обычно в этом случае употребляются термины Level 1, Level 2 и т.д.). Эти стандарты отличаются различной функциональностью, которая должна быть реализована в таком драйвере. Например, драйверы, соответствующие стандарту Level 1, не обязаны поддерживать работу с хранимыми процедурами, а некоторые ODBC-драйверы не поддерживают двухфазное завершение транзакций (применяемое в том случае, когда требуется согласованное изменение данных в нескольких различных серверных СУБД).
Отметим, однако, что, несмотря на широкую популярность ODBC, компания Microsoft не рекомендует использовать этот универсальный механизм доступа к данным при разработке новых приложений, поскольку дальнейшее развитие этого стандарта компанией не планируется.
OLE DB и ADO являются составными частями универсального механизма доступа к данным Microsoft (Microsoft Universal Data Access). Они позволяют осуществлять доступ как к реляционным, так и к нереляционным источникам данных, таким, например, как файлы, сообщения электронной почты, папки Exchange и OLAP-кубы.
Microsoft ActiveX Data Objects (ADO) — это набор библиотек, содержащих COM-объекты, реализующие прикладной программный интерфейс для доступа к данным и используемые в клиентских приложениях. ADO базируется на библиотеках OLE DB, предоставляющих низкоуровневый интерфейс для доступа к данным. OLE DB предоставляет доступ к данным с помощью COM-интерфейсов. Основное назначение ADO – обеспечение простого универсального механизма доступа к данным. В первую очередь этот механизм предназначен для разработчиков на Visual Basic и скриптовых языках типа VBScript и JScript. Программисты, более уверенно чувствующие себя в языках С и C++ и технологии COM, могут использовать OLE DB непосредственно, минуя ADO.
Для доступа к источнику данных с помощью OLE DB требуется, чтобы на компьютере, где используется клиентское приложение, был установлен OLE DB-провайдер для данной СУБД. OLE DB-провайдер представляет собой DLL, загружаемую в адресное пространство клиентского приложения, и используемую для доступа к источнику данных. Для каждого типа СУБД нужен собственный OLE DB-провайдер, так как эти провайдеры базируются на функциях клиентских API, которые, естественно, специфичны для каждой СУБД.
Среди OLE DB-провайдеров для разных источников данных имеется специальный провайдер Microsoft OLE DB Provider for ODBC Drivers. Этот провайдер использует не API клиентской части какой-либо СУБД, а интерфейс ODBC API, поэтому он применяется вместе с ODBC-драйвером для выбранной СУБД.
Отметим, что OLE DB и ADO на сегодняшний день являются самыми популярными способами доступа к данным. Библиотеки, реализующие эти механизмы, входят в состав таких популярных продуктов, как Microsoft Office и Microsoft Internet Explorer, а также включены в состав всех операционных систем семейства Microsoft Windows, начиная с Windows 2000.
Многопользовательская работа с корпоративными данными подчиняется некоторым общим правилам и состоит, как правило, из применения стандартного набора программных компонентов и сервисов, включающих базу данных и набор пользовательских приложений, используемых для просмотра и изменения данных на рабочих станциях пользователей. Немаловажной составной частью любой информационной системы является также набор сервисов, обеспечивающих бизнес-логику ее функционирования, таких как проведение расчетов, генерация отчетов, и др.
Классические многопользовательские системы, использующие наиболее широко распространенную сейчас архитектуру «клиент/сервер», как правило, содержат клиентские приложения, содержащие презентационную логику, значительную часть бизнес-логики, а также средства доступа к данным. В ряде случаев (хотя и не всегда) это усложняет технические требования, предъявляемые к аппаратной части клиентской рабочей станции, и в конечном итоге приводит к повышению стоимости эксплуатации и сопровождения такой информационной системы.
Одним из способов решения указанной проблемы является изменение архитектуры приложения путем создания сервисов промежуточного звена (middleware services), реализующих доступ к данным и бизнес-логику информационной системы. В этом случае на долю клиентского приложения остается предоставление презентационных сервисов (такие приложения иногда называют «тонкими» клиентами), и при корректном проектировании приложений повышается их масштабируемость, надежность, а совокупная стоимость владения оказывается ниже, чем при использовании «классической» двухзвенной архитектуры. Отметим, что в многозвенной архитектуре нередко применяются кэшируемые наборы данных, манипуляция которыми не требует наличия постоянного соединения с базой данных.
Многозвенную систему можно построить разными способами с применением различных технологий. Доля информационных систем, использующих подобную архитектуру, в последнее время начала заметно расти, и это не могло не найти отражения в эволюции механизмов доступа к данным, и, в частности, в том, какие принципы и концепции положены в основу платформы Microsoft .NET и технологии ADO.NET. (Подробнее о платформе .NET и ее архитектуре можно прочесть в статьях «Переход на .Net: взаимодействие старого и нового» («Технология Клиент-Сервер», 2001, N 3))
ADO.NET – это следующий шаг в эволюции универсальных механизмов доступа к данным, ориентированный на поддержку распределенных приложений, в том числе использующих мобильные устройства в качестве клиентских рабочих мест. Типичная модель подобного приложения основана на применении кэшируемых наборов данных, представляющих собой хранящуюся в оперативной памяти базу данных, содержащую таблицы, связи между ними, индексы, ограничения, и т.п. В ADO.NET (по сравнению с ADO) поддержка подобных наборов данных существенно расширена.
Одним из ключевых, по сравнению с ADO, изменений является отсутствие класса Recordset, на смену которому пришла комбинация классов DataTable, DataSet, DataAdapter и DataReader. Класс DataTable представляет собой коллекцию строк из одной таблицы, и с этой точки зрения он схож с классом Recordset. Класс DataSet представляет собой коллекцию объектов типа DataTable, вместе со связями между ними и ограничениями, что позволяет связывать вместе несколько таблиц. В целом, DataSet – это содержащаяся в памяти реляционная структура со встроенной поддержкой языка XML. Встроенная поддержка языка XML означает, что данные DataSet могут быть выгружены/загружены в XML с сохранением всех связей и ограничений.
Одной из наиболее важных характеристик класса DataSet является то, что он ничего не знает об источнике данных, который использовался для его заполнения. С этой точки зрения DataSet – это отсоединенное представление данных, которое может передаваться от компонента к компоненту через различные звенья многозвенного приложения. DataSet можно превратить в поток XML-данных и использовать его для передачи данных между гетерогенными платформами.
Класс DataAdapter используется в ADO.NET для передачи данных между DataSet и источником данных. Класс DataAdapter также поддерживает расширенные возможности пакетного обновления данных, которые ранее были реализованы в классе Recordset.
Ниже мы рассмотрим пространства имен и классы, реализующие функциональность ADO.NET, а также некоторые примеры их применения.
Классы, реализующие механизм доступа к данным ADO.NET, содержатся в нескольких пространствах имен, перечисленных ниже.
System.Data – содержит набор основных классов ADO.NET, в том числе классы для поддержки кэшируемых наборов данных;
System.Data.Common – содержит классы и интерфейсы, наследуемые провайдерами данных .NET;
System.Data.SqlClient – провайдер данных SQL Server .NET Data Provider;
System.Data.OleDb – провайдер данных OLE DB .NET Data Provider;
System.Data.SqlTypes – классы и структуры для поддержки типов данных Microsoft SQL Server;
Используемые ADO.NET классы, интерфейсы и перечисления, предназначенные для поддержки обработки XML-данных, находятся в пространстве имен System.Xml.
Концепция кэшируемых наборов данных ADO.NET реализована в классе DataSet, предоставляющем доступ к кэшу в оперативной памяти, содержащему таблицы, связи между ними и ограничения на значения данных. Заполнять этот кэш можно данными, полученными во время работы приложения в режиме соединения с источником данных, например, в результате выполнения SQL-запросов (в том числе и к разным источникам данных) или чтения XML-документов. Содержимое кэша легко сохранить в виде XML-данных, которые можно передать другому приложению или сервису. Если же в кэшировании данных нет необходимости (например, в случае генерации отчета), лучше использовать классы xxxReader, предоставляющие доступ на чтение к однонаправленным наборам данных.
Провайдеры в .NET Framework используются в качестве "моста" между приложением и источником данных. Провайдер данных позволяет получить результаты запроса, выполнить команды или внести изменения в данные.
Библиотека классов .NET Framework версии 1.0 включает два управляемых провайдера данных: SQL Server .NET Data Provider и OLE DB .NET Data Provider.
Провайдер данных SQL Server .NET Data Provider предназначен для работы с базами данных Microsoft SQL Server 7.0 и более поздних версий и оптимизирован для доступа к этой СУБД в многозвенных приложениях. В однозвенных приложениях следует использовать этот провайдер с Microsoft Data Engine (MSDE).
Провайдер данных OLE DB .NET Data Provider предназначен для доступа к источникам данных OLE DB, а именно, к данным Microsoft SQL Server 6.5 и более ранних версий, а также Oracle, Sybase, DB2 – в случае создания многозвенных приложения, Microsoft Access – в случае создания однозвенных приложений, и некоторых других СУБД. Отметим, что этот провайдер не поддерживает провайдера OLE DB для ODBC-источников (вместо этого можно использовать управляемый провайдер для ODBC) и провайдер OLE DB для Microsoft SQL Server 7 и более поздних версий. Кроме того, этим провайдером данных не поддерживаются интерфейсы OLE DB 2.5 и провайдеры OLE DB, использующие эти интерфейсы.
Помимо этого, компанией Microsoft созданы управляемые провайдеры данных для СУБД Oracle (Microsoft .NET Data Provider for Oracle) и для ODBC-источников (ODBC .NET Data Provider). В случае использования провайдера для СУБД Oracle требуется установка Oracle 8i Release 3 (8.1.7) Client на компьютере, осуществляющем непосредственное обращение к СУБД. Драйвер для ODBC-источников совместим со всеми ODBC-драйверами, но его работа тестировалась только с драйверами Microsoft SQL ODBC Driver, Microsoft ODBC Driver for Oracle и Microsoft Jet ODBC Driver. Провайдеры для СУБД Oracle и для ODBC-источников доступны для загрузки с сайта компании Microsoft. Эти провайдеры входят в состав Microsoft .NET Framework 1.1 и поставляются в составе Microsoft Visual Studio.NET 2003. Независимые компании типа DataDirect Technologies начали разработку провайдеров для других СУБД – Sybase и т.п.
Для каждого из провайдеров данных в соответствующем пространстве имен содержатся реализации классов для доступа к данным. Их мы рассмотрим ниже.
К классам для доступа к данным относятся xxxConnection, xxxCommand, xxxDataReader и xxxDataAdapter. Имена классов из пространства имен System.Data.SqlClient начинаются с префикса «Sql» (SqlConnection, SqlCommand, SqlDataReader, SqlDataAdapter), а имена классов из пространства имен System.Data.OleDb — с префикса «OleDb» (OleDbConnection, OleDbCommand, OleDbDataReader, OleDbDataAdapter).
Перечисленные выше классы имеют следующее назначение:
XxxConnection – отвечает за установку соединения с источником данных.
XxxCommand – выполняет команду (SQL-запрос или хранимую процедуру) в источнике данных.
XxxDataReader – считывает однонаправленный нередактируемый набор данных из источника. Экземпляр соответствующего класса можно получить с помощью метода ExecuteReader класса xxxCommand, как правило, в результате SQL-запроса типа SELECT.
XxxDataAdapter – использует классы Connection, Command и DataReader для заполнения данными кэша, доступного через класс DataSet, и обновления источника данных в соответствии с изменениями, сделанными в этом кэше.
Классы XxxConnection и XxxCommand применяются во всех типах приложений ADO.NET, класс XxxDataAdapter – в приложениях, использующих кэшированные наборы данных, а класс XxxDataReader - в приложениях, применяющих некэшируемые наборы данных.
К классам для работы с кэшируемыми данными относятся упомянутый выше класс xxxAdapter, предназначенный для заполнения кэша данными, а также классы DataSet, DataTable, DataColumn, DataRow и DataRelation.
Класс DataSet предоставляет доступ к кэшу, содержащему данные, полученные из базы данных. Из наиболее интересных свойств этого класса следует отметить коллекцию Tables объектов типа DataTable для описания таблиц и коллекцию Relations объектов типа DataRelation для описания связей между таблицами. Каждый объект DataTable, в свою очередь, обладает коллекциями Rows и Columns объектов типа DataRow и DataColumn, представляющих строки и столбцы, принадлежащие данной таблице (рис. 1).
Рисунок 1. Класс DataSet и связанные с ним объекты.
Одна из основных особенностей класса DataSet заключается в том, что ему неизвестен источник данных, использующийся для заполнения кэша, доступ к которому он предоставляет. Кроме того, упомянутый набор данных можно представить в виде XML-данных и затем передать их приложению, функционирующему на другой платформе.
Как было сказано выше, для двухстороннего обмена данными между объектом DataSet и источником данных применяется класс DataAdapter. Из наиболее часто применяемых свойств этого класса следует отметить свойство TableMappings, описывающее соответствие таблиц в кэше, доступ к которому предоставлен каким-либо экземпляром класса DataSet, реальным таблицам в базах данных. Из наиболее часто применяемых методов этого класса следует отметить метод Fill, заполняющий кэш данными из источника, и Update, инициирующий выполнение SQL-запросов INSERT, UPDATE или DELETE для каждой строки кэшированного набора данных. Сами тексты запросов содержатся в свойствах SelectCommand, InsertCommand, UpdateCommand, DeleteCommand, первое из которых не должно быть пустым – на его основе автоматически задаются значения свойств InsertCommand, UpdateCommand и DeleteCommand.
Из других классов, наиболее часто применяемых при создании приложений, следует отметить класс DataRelation, предназначенный для описания связей между объектами DataTable, содержащимися в кэше объекта DataSet, и класс DataView, предназначенный для различных способов представления одних и тех же данных (в определенной степени – аналогов представлений, имеющихся в обычных базах данных).
Ниже мы рассмотрим, какие классы применяются в различных типах приложений, и приведем некоторые примеры, иллюстрирующие их использование.
Во всех примерах, приводимых в данной статье, будет использоваться СУБД Microsoft SQL Server 2000 и база данных Northwind, входящая в комплект поставки этого продукта. В качестве языка программирования мы будем использовать Visual Basic .NET. В приводимых ниже примерах намеренно опущен код, связанный с обработкой исключений, которые могут возникнуть при работе с источниками данных – это сделано для того, чтобы код примеров был более компактным. Общие вопросы обработки исключений рассматриваются в конце данной статьи.
Первый из примеров будет использовать класс SqlConnection и его свойства для получения данных о сеансе связи пользователя с базой данных, а именно, о состоянии соединения, размере пакетов данных, версии сервера и имени рабочей станции, на которой запущено данное приложение:
Imports System Imports System.Data.SqlClient Module Cons Sub Main() Dim Conn As SqlConnection Conn = New SqlConnection("server=MAINDESK;" & _ "database=Northwind;uid=sa;pwd=;") Conn.Open Console.WriteLine("State = " & Conn.State.ToString) Console.WriteLine("PacketSize = " & Conn.PacketSize.ToString) Console.WriteLine("ServerVersion = " & Conn.ServerVersion.ToString) Console.WriteLine("WorkstationID = " & Conn.WorkstationID.ToString) Console.ReadLine() Conn.Close() Console.ReadLine() End Sub End Module
Результат работы данного приложения представлен на рисунке 2.
Рисунок 2. Применение класса SqlConnection.
Отметим, что доступ к OLE DB-источникам данных осуществляется точно таким же образом; основная разница заключается в применении другого пространства имен и, соответственно, класса OledbConnection вместо SqlConnection:
Imports System Imports System.Data.OleDb Module Cons Sub Main() Dim Conn As OleDbConnection Dim DBPath As String = "C:\Data\" Dim ConnStr As String = "Provider=Microsoft.Jet.OLEDB.4.0;" & _ "Data Source=" & DBPath & "Northwind.mdb" Console.WriteLine(Connstr) Conn = New OleDbConnection(ConnStr) Conn.Open Console.WriteLine("State = " & Conn.State.ToString) Console.WriteLine("DataSource = " & Conn.DataSource.ToString) Console.WriteLine("Provider = " & Conn.Provider.ToString) Console.WriteLine("ServerVersion = " & Conn.ServerVersion.ToString) Conn.Close() Console.ReadLine() End Sub End Module
Заметим, что классы SqlConnection и OleDbConnection, несмотря на сходное назначение, обладают разным набором свойств – это соответствует разным типам источников данных.
Результат работы данного приложения представлен на рисунке 3.
Рисунок 3. Применение класса OleDbConnection.
Ознакомившись с применением классов SqlConnection и OleDbConnection, используемых во всех типах приложений ADO.NET, мы переходим к рассмотрению двух важных категорий подобных приложений. Первая из них основана на применении некэшируемых наборов данных, вторая – на применении кэшируемых наборов данных.
Если нам нужно выполнить запрос или обратиться к хранимой процедуре, которые не возвращают наборов данных, следует использовать класс xxxCommand и его метод ExecuteNonQuery, а для возврата единственного значения – метод ExecuteScalar. Последнее иллюстрируется ниже:
Imports System Imports System.Data.SqlClient Module Cons Sub Main() Dim Conn As SqlConnection Dim Cmd As SqlCommand Dim Count As String Conn = New SqlConnection("server=localhost;" & _ "database=Northwind;uid=sa;pwd=;") Conn.Open() Cmd = New SqlCommand("SELECT COUNT(*)AS NumOfEmp" & _ " FROM Employees", Conn) Count = Cmd.ExecuteScalar().ToString Console.WriteLine("The number of Employees is " & _ Count) Conn.Close() Console.ReadLine() End Sub End Module
В приведенном примере мы создали соединение с базой данных и экземпляр класса SqlCommand, содержащий запрос на вычисление числа записей в таблице, после чего выполнили этот запрос. Результат выполнения данного приложения изображен на рисунке 4.
Рисунок 4. Применение класса SqlCommand.
Приложения, использующие некэшируемые наборы данных, нуждаются в постоянном соединении с базой данных на время их обработки. Подобные приложения получают некэшируемый однонаправленный набор данных, записи которого считываются и обрабатываются приложением последовательно по одной, начиная с первой и заканчивая последней, при этом перемещение по набору данных в обратном направлении невозможно без выполнения повторного запроса. Обычно такие приложения функционируют по следующему алгоритму:
устанавливается соединение с базой данных;
выполняются команды или запросы;
данные обрабатываются с помощью одного из классов xxxReader;
соединение закрывается.
При реализации этой последовательности действий следует использовать один из классов xxxDataReader (SqlDataReader, OleDbDataReader и т.д.) для получения данных, соответствующий класс xxxConnection – для обеспечения соединения с базой данных и xxxCommand – для выполнения команд в источнике данных.
Классы типа xxxDataReader предназначены для однонаправленного чтения набора данных, полученного в результате выполнения запроса к базе данных или вызова содержащейся в ней хранимой процедуры. Следующий пример иллюстрирует применение этого класса для последовательного вывода данных, полученных в результате запроса:
Imports System Imports System.Data.SqlClient Module Cons Sub Main() Dim Conn As SqlConnection Dim Cmd As SqlCommand Conn = New SqlConnection("server=localhost;" & _ "database=Northwind;uid=sa;pwd=;") Conn.Open Cmd = New SqlCommand("SELECT * FROM Employees", Conn) Dim DR As SqlDataReader = Cmd.ExecuteReader() While DR.Read() Console.WriteLine(DR("FirstName") & " " & _ DR("LastName")) End While DR.Close() Conn.Close() Console.ReadLine() End Sub End Module
Результат работы данного приложения представлен на рисунке 5.
Отметим, что последовательное однонаправленное чтение данных нередко применяется при генерации отчетов и в некоторых других типах приложений.
Завершая обсуждение применения некэшируемых наборов данных, отметим, что данный способ манипуляции данными удобен в тех случаях, когда в клиентском приложении не требуется редактирования данных; в этом случае отсутствие кэширования благоприятно с точки зрения производительности приложения. Отметим, однако, что в этом случае требуется постоянное соединение с сервером баз данных не только на время выполнения запроса, но и на время получения и обработки клиентским приложением данных с сервера.
Рисунок 5. Применение класса SqlDataReader.
Применение кэшируемых наборов данных удобно тем, что при обработке данных в клиентском приложении ресурсы сервера не используются. Однако при этом приложению одновременно доступны все данные, полученные в результате запроса, и перемещаться по ним можно в любом направлении – как от первой записи к последней, так и наоборот. Подобные возможности обычно применяются в приложениях, предназначенных для редактирования данных, а иногда и в некоторых других типах приложений.
Обычно приложения, основанные на обработке кэшируемых данных, работают по следующему алгоритму:
устанавливается соединение с базой данных;
доступный с помощью класса DataSet кэш в оперативной памяти заполняется данными, полученными в результате запроса;
соединение разрывается;
данные в кэше обрабатываются клиентским приложением;
повторно устанавливается соединение с базой данных;
производится обновление данных в БД;
соединение разрывается.
Для реализации описанной выше последовательности действий следует использовать классы xxxConnection и xxxDataAdapter, содержащий набор команд, предназначенных для получения данных из источника и помещения их в кэш, доступ к которому предоставляет класс DataSet.
Следующий пример реализует приведенный нами сценарий. В нем кэш объекта DataSet заполняется результатами запроса к базе данных, а затем часть содержимого этого объекта выводится на экран:
Imports System Imports System.Data Imports System.Data.SqlClient Module Cons Sub Main() Dim DA As SqlDataAdapter Dim DS As New DataSet() Dim Row As System.Data.DataRow DA = New SqlDataAdapter("SELECT * FROM Customers", _ "server=localhost;database=Northwind;uid=sa;pwd=;") DA.Fill(DS, "Customers") Console.WriteLine(DS.Tables(0).Columns(1).ToString) For Each Row In DS.Tables(0).Rows Console.WriteLine(Row.Item(1).ToString) Next Console.ReadLine() End Sub End Module
Результат выполнения данного приложения изображен на рисунке 6.
Рисунок 6. Применение классов DataSet и SqlDataAdapter.
Как было сказано выше, класс DataSet позволяет хранить в кэше несколько таблиц с описанием связей между ними, подобно тому, как хранятся данные и сведения о связях в обычных базах данных. Для описания связей применяется класс DataRelation. Приведенный ниже пример иллюстрирует его применение:
Imports System Imports System.Data Imports System.Data.SqlClient Module Cons Sub Main() Dim DA As SqlDataAdapter Dim DR As SqlDataAdapter Dim DS As New DataSet() Dim Row As System.Data.DataRow Dim RowCh As System.Data.DataRow Dim Rel As System.Data.DataRelation Dim PC As DataColumn Dim CC As DataColumn DA = New SqlDataAdapter("SELECT * FROM Customers", _ "server=localhost;database=Northwind;uid=sa;pwd=;") DA.Fill(DS, "Customers") DR = New SqlDataAdapter("SELECT * FROM Orders", _ "server=localhost;database=Northwind;uid=sa;pwd=;") DR.Fill(DS, "Orders") PC = DS.Tables("Customers").Columns("CustomerID") CC = DS.Tables("Orders").Columns("CustomerID") Rel = New DataRelation("CustomersOrders", PC, CC, True) DS.Relations.Add(Rel) Console.Writeline() For Each Row In DS.Tables("Customers").Rows Console.WriteLine(Row.Item(1).ToString) Console.WriteLine("--------------------------------") For Each RowCh In Row.GetChildRows(Rel) Console.WriteLine(RowCh.Item("OrderID").ToString &_ " " & RowCh.Item("ShippedDate").ToString() & " " _ & RowCh.Item("ShipCity").ToString()) Next Console.ReadLine() Next End Sub End Module
В приведенном выше примере мы создаем два объекта SqlDаtaAdapter для главной и подчиненной таблицы, два объекта DataColumn для связываемых колонок этих таблиц, затем создаем объект DataRelation, используя эти объекты в качестве параметров его конструктора, и, наконец, выводим на экран записи главной таблицы вместе со связанными с ней записями подчиненной таблицы. Результат выполнения данного приложения изображен на рисунке 7.
Рисунок 7. Применение класса DataRelation.
Нередко в приложениях, использующих кэшированные результаты запроса, применяется различное представление одних и тех же данных, например, использующих другой порядок сортировки или разные условия фильтрации данных. Как правило, получение подобных представлений не должно требовать повторного обращения к базе данных. Этой цели отвечает применение класса DataView, представляющего, по существу, описание правил представления данных из имеющегося объекта DataTable. Одному компоненту DataTable может соответствовать несколько объектов DataView.
Рассмотрим простейший пример применения данного класса:
Imports System Imports System.Data Imports System.Data.SqlClient Module Cons Sub Main() Dim DA As SqlDataAdapter Dim DS As New DataSet() Dim Row As System.Data.DataRow Dim DV As DataView Dim i As Integer DA = New SqlDataAdapter("SELECT * FROM Customers", _ "server=localhost;database=Northwind;uid=sa;pwd=;") DA.Fill(DS, "Customers") DV = New DataView(DS.Tables("Customers")) DV.RowFilter = "Country = 'France'" For i = 0 To DV.Count - 1 Console.WriteLine(DV.Item(i)(1).ToString) Next Console.ReadLine() End Sub End Module
В данном примере мы создаем объект DataTable и объект DataView, реализующий представление только тех данных, которые удовлетворяют условию, указанному в его свойстве RowFilter – в нашем примере это значение поля Country, равное France Результат работы данного приложения показан на рисунке 8.
Рисунок 8. Применение класса DataView.
Отметим, что, помимо фильтрации данных, возможна их сортировка, например:
DV.Sort:= “CompanyName”
или
DV.Sort:= “Country, City”
Следует отметить, что для управления различными видами представления таблиц, содержащихся в объекте DataSet, можно использовать класс DataViewManager. Это бывает удобно в случае, когда один и тот же набор визуальных элементов управления (которые более подробно будут рассмотрены во второй части данной статьи) связан с несколькими таблицами одного и того же объекта DataSet.
Как было сказано выше, обновление источника данных в соответствии с изменениями, произошедшими в кэше объекта DataSet, осуществляется с помощью вызова метода Update, инициирующего выполнение SQL-запросов INSERT, UPDATE или DELETE для каждой строки кэшированного набора данных. Сами тексты запросов (команды) содержатся в свойствах SelectCommand, InsertCommand, UpdateCommand, DeleteCommand, и для запросов к одной таблице последние три из них генерируются на основании значения свойства SelectCommand, которое содержит команду SELECT или имя хранимой процедуры, используемых для выборки данных из источника данных. Для автоматической генерации значений свойств InsertCommand, UpdateCommand и DeleteCommand применяется класс CommandBuilder, конструктор которого принимает в качестве параметра экземпляр класса DataAdapter.
Простейший пример применения класса CommandBuilder приведен ниже:
Imports System Imports System.Data Imports System.Data.SqlClient Module Cons Sub Main() Dim DA As SqlDataAdapter Dim DS As New DataSet() Dim Row As System.Data.DataRow Dim CB As SqlCommandBuilder DA = New SqlDataAdapter("SELECT * FROM Customers", _ "server=localhost;database=Northwind;uid=sa;pwd=;") DA.Fill(DS, "Customers") CB = New SqlCommandBuilder(DA) DS.Tables("Customers").Rows(90).Item("CompanyName") = _ "My Company Ltd" DA.Update(DS, "Customers") Console.WriteLine( _ DS.Tables("Customers").Columns(1).ToString) For Each Row In DS.Tables("Customers").Rows Console.WriteLine(Row.Item(1).ToString) Next Console.WriteLine Console.WriteLine(CB.GetUpdateCommand.CommandText) Console.ReadLine() End Sub End Module
В данном примере мы создаем экземпляр класса CommandBuilder и с его помощью изменяем данные в одной из записей таблицы, содержащейся в кэше объекта DataSet. Результат работы данного приложения приведен на рисунке 9.
Рисунок 9. Применение класса CommandBuilder.
Есть два способа применения транзакций при работе с ADO.NET. При необходимости использования транзакции можно применять "ручные" транзакции – команды языка SQL (BEGIN TRANSACTION и COMMIT/ROLLBACK TRANSACTION), хранимые процедуры, содержащие эти команды, или средства ADO.NET. Вторым способом реализации транзакций является использование автоматических транзакций на основе COM+.
В случае использования средств ADO.NET мы можем самостоятельно устанавливать границы транзакции и управлять ее началом и завершением. Для выполнения операций внутри транзакции мы создаем либо объект SqlTransaction, либо объект OleDbTransaction и ассоциируем его с экземпляром объекта Connection.
Объект SqlTransaction, который мы будем использовать в наших примерах, предоставляет ряд свойств и методов. Для начала транзакции мы используем метод BeginTransaction, для завершения – метод Commit, а для отката транзакции – метод Rollback. Рассмотрим следующий пример:
Imports System Imports System.Data Imports System.Data.SqlClient Module Cons Sub Main() Dim Conn As SqlConnection Dim Cmd As SqlCommand Dim Trans As SqlTransacton Conn = New SqlConnecton("ConnectionString") Cmd = New SqlCommand Conn.Open ' ' Начало транзакции ' Trans = Conn.BeginTransaction ' ' Ассоциируем команду с транзакцией ' Cmd.Transaction = Trans Try ' ' Выполняем операции над данными ' Trans.Commit Catch ' ' Откат транзакции ' Trans.Rollback Finally Conn.Close End Try End Sub End Module
Использование блока try/catch/finally в приведенном выше примере позволяет нам корректно выполнить транзакцию. Транзакция завершается при безошибочном выполнении всех операций в блоке try. Исключительная ситуация перехватывается блоком catch. В этом случае происходит откат транзакции, отменяющий все произведенные в ней действия.
Автоматические транзакции базируются на сервисах, предоставляемых COM+, и требуют использования так называемых "обслуживаемых" компонентов (serviced components). Чтобы сконфигурировать класс для использования автоматических транзакций, необходимо унаследовать его от класса ServicedComponent, который находится в пространстве имен System.EnterpriseServices. Далее, используя атрибут Transaction, нужно указать необходимые свойства транзакции (TransactionOption, IsolationLevel, TimeOut). Такие объекты нужно снабдить строгими именами, зарегистрировать в GAC и COM+. Для некоторых методов можно указать атрибут AutoComplete – если при выполнении таких методов произойдет исключение, транзакция будет автоматически аварийно завершена. В случае возникновения исключений сведения об этом передаются непосредственно вызывающему коду (см. в следующем разделе). Например:
[AutoComplete] Sub DemoMethod() Try ' Открыть соединение, выполнить операцию над данными Catch ex As SqlException throw ' повторить исключительную ситуацию – исключение передается ' по цепочке, бит consistent устанавливается в false Finally ' Закрыть соединение End Try End Sub
Для методов, не отмеченных атрибутом AutoComplete, необходимо использовать методы ContextUtil.SetAbort для аварийного завершения транзакции и ContextUtil.SetComplete для ее нормального завершения. Например:
Sub OtherDemoMethod() Try ' Открыть соединение, выполнить операцию над данными ContextUtil.SetComplete Catch ex As SqlException ContextUtil.SetAbort Finally ' Закрыть соединение End Try End Sub
Класс DataAdapter сигнализирует о проблемах с помощью стандартного механизма исключительных ситуаций.
Для обработки ошибок, связанных с работой провайдеров, используется коллекция Errors класса SqlException или OleDbException. Оба класса унаследованы от класса Exception, определенного в пространстве имен System. При этом класс SqlException наследуется напрямую, а OleDbException – через класс ExternalException, что позволяет перехватывать ошибки, связанные с COM Interop.
Общими свойствами этих классов являются свойства Message и Source. Класс OleDbException также поддерживает свойства:
NativeError – возвращает целочисленный код ошибки, специфичный для используемой СУБД;
SQLState – возвращает 5-символьный код ошибки в стандарте ANSI SQL.
NativeError – содержит код COM HRESULT, генерируемый OLE DB.
Класс SqlDbException также поддерживает свойства:
Class – возвращает уровень серьезности ошибки;
LineNumber – возвращает номер строки SQL-запроса, в котором содержится ошибка;
Number – возвращает номер, позволяющий узнать тип ошибки;
Procedure – возвращает имя хранимой процедуры, вызывавшей ошибку;
Server – возвращает имя сервера, сгенерировавшего ошибку;
State – возвращает дополнительную информацию об ошибке.
Для обработки исключений, связанных с доступом к источникам данных, используются блоки try/catch. Например, при работе с провайдером SQL Server .NET Data Provider можно использовать следующий код:
Try ' код для доступа к данным Catch ex As SqlException ' обработка исключения End Try
Внутри блока catch можно поместить код, который будет отображать значения свойств из коллекции SqlErrorsCollection.
Copyright © 1994-2016 ООО "К-Пресс"