Продолжается подписка на наши издания! Вы не забыли подписаться?

Microsoft ADO.NET

Алексей Федоров,
Наталия Елманова

Основные концепции

Классы для доступа к данным

Настоящая статья посвящена созданию приложений с помощью универсального механизма доступа к данным ADO.NET, который является составной частью платформы Microsoft .NET Framework. В этой статье мы обсудим отличия ADO.NET от других универсальных механизмов доступа к данным, рассмотрим поддерживаемые этим механизмом архитектуры приложений, а также обсудим классы ADO.NET, реализующие доступ к данным при различных архитектурах приложений.

Механизмы доступа к данным

Доступ к данным из клиентских приложений может осуществляться несколькими способами.

Подавляющее большинство систем управления базами данных содержит в своем составе библиотеки, предоставляющие специальный прикладной программный интерфейс (Application Programming Interface, API) для доступа к данным этой СУБД. Обычно такой интерфейс представляет собой набор функций, вызываемых из клиентского приложения, либо набор классов, которые могут быть в нем использованы. В приложениях, использующих настольные СУБД, эти функции и методы классов обеспечивают чтение и запись файлов базы данных. В приложениях, использующих серверы баз данных, эти функции и классы инициируют передачу запросов серверу баз данных и получение от сервера результатов выполнения запросов или кодов ошибок, интерпретируемых клиентским приложением. Библиотеки, содержащие API для доступа к данным серверной СУБД, обычно входят в состав ее клиентского программного обеспечения, устанавливаемого на компьютерах, где функционируют клиентские приложения, работающие с этой СУБД.

В течение последних нескольких лет в Windows-версиях клиентского программного обеспечения наиболее популярных СУБД (в частности, СУБД производства компаний Microsoft и Oracle) присутствуют также и COM-серверы, предоставляющие объекты для доступа к данным и метаданным этих СУБД.

Функции, классы или COM-объекты, реализующие клиентский API, как правило, хорошо документированы. Их использование зачастую является наиболее эффективным с точки зрения производительности способом манипуляции данными в приложении. Однако в этом случае созданное приложение сможет взаимодействовать только с СУБД этого производителя. Замена этой СУБД на другую (например, с целью расширения объема хранимых данных или смены архитектуры информационной системы) повлечет за собой переписывание значительной части кода клиентского приложения — в большинстве случаев клиентские API и объектные модели не подчиняются никаким стандартам и различны для разных СУБД.

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

Отметим, что достоинством универсальных механизмов является возможность применения одного и того же абстрактного API (в виде функций, COM-серверов и т.п.) для доступа к разным типам СУБД. Поэтому приложения, использующие универсальные механизмы доступа к данным, могут быть относительно легко модифицированы в случае смены СУБД. При этом нередко модификация затрагивает не столько код приложения, сколько настройки доступа к данным, содержащиеся в реестре или внешних файлах. Однако подобная универсальность достигается за счет отказа от использования уникальной функциональности, специфичной для конкретной СУБД, а в ряде случаев – снижением производительности приложений и некоторым усложнением их поставки. В этом случае в состав дистрибутива нужно включать библиотеки, ответственные за реализацию универсальных механизмов, драйверы для используемых СУБД, а также обеспечивать настройки, необходимые для их правильного функционирования.

Можно указать следующие наиболее популярные универсальные механизмы доступа к данным:

JDBC – это интерфейс доступа к базам данных, входящий в состав платформы Java. Он позволяет использовать как "чистые" драйверы, написанные на языке Java и работающие на уровне call-level API (поддерживаются СУБД Oracle, Sybase, Informix, DB2 и т.п.), и JDBC-ODBC Bridge для использования существующих ODBC-драйверов.

Пользователям средств разработки компании Borland и некоторых генераторов отчетов также хорошо известны собственные универсальные механизмы, разработанные этой компанией – BDE (Borland Database Engine) и пришедший ему на смену dbExpress. Однако сфера их применения, в отличие от ODBC, OLE DB и ADO, ограничена в основном приложениями, созданными с помощью средств разработки компании Borland.

Перечисленные выше механизмы являются промышленными стандартами, поддерживаемыми всеми ведущими производителями СУБД. Ниже мы кратко остановимся на их особенностях. Это позволит понять, какой выигрыш можно получить посредством применения ADO.NET.

ODBC

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

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.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

Пространства имен

Классы, реализующие механизм доступа к данным ADO.NET, содержатся в нескольких пространствах имен, перечисленных ниже.

Используемые 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 применяются во всех типах приложений 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, предназначенный для различных способов представления одних и тех же данных (в определенной степени – аналогов представлений, имеющихся в обычных базах данных).

Ниже мы рассмотрим, какие классы применяются в различных типах приложений, и приведем некоторые примеры, иллюстрирующие их использование.

Установка соединения с базой данных.
Классы xxxConnection.

Во всех примерах, приводимых в данной статье, будет использоваться СУБД 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

Если нам нужно выполнить запрос или обратиться к хранимой процедуре, которые не возвращают наборов данных, следует использовать класс 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.

Применение некэшируемых наборов данных. Классы xxxDataReader.

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

При реализации этой последовательности действий следует использовать один из классов 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.

Применение кэшируемых наборов данных

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

Обычно приложения, основанные на обработке кэшируемых данных, работают по следующему алгоритму:

Для реализации описанной выше последовательности действий следует использовать классы xxxConnection и xxxDataAdapter, содержащий набор команд, предназначенных для получения данных из источника и помещения их в кэш, доступ к которому предоставляет класс DataSet.

Применение классов DataSet и xxxDataAdapter

Следующий пример реализует приведенный нами сценарий. В нем кэш объекта 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.

Применение класса DataRelation

Как было сказано выше, класс 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

Нередко в приложениях, использующих кэшированные результаты запроса, применяется различное представление одних и тех же данных, например, использующих другой порядок сортировки или разные условия фильтрации данных. Как правило, получение подобных представлений не должно требовать повторного обращения к базе данных. Этой цели отвечает применение класса 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.

Применение класса CommandBuilder

Как было сказано выше, обновление источника данных в соответствии с изменениями, произошедшими в кэше объекта 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 также поддерживает свойства:

Класс SqlDbException также поддерживает свойства:

Для обработки исключений, связанных с доступом к источникам данных, используются блоки try/catch. Например, при работе с провайдером SQL Server .NET Data Provider можно использовать следующий код:

Try
  ' код для доступа к данным
Catch ex As SqlException
  ' обработка исключения
End Try

Внутри блока catch можно поместить код, который будет отображать значения свойств из коллекции SqlErrorsCollection.


Copyright © 1994-2016 ООО "К-Пресс"