Книги, научные публикации Pages:     | 1 | 2 | 3 | 4 |   ...   | 5 |

том 1 альманах программиста Тематический сборник материалов Library и Magazine ADO.NET SQL Доступ к из приложений Составитель Ю. Е. Купцевич Москва 2003 fii. P У С P i К УДК 004.45 ББК ...

-- [ Страница 2 ] --

II DataSet хранения // отображаемых SqlService oSql null;

// класса SqlService string sSql // SQL-оператор для PataSet string - // хранимая процедура для заполнения //- Создание экземпляра SqlService со строкой oSql = new SQL-оператора sSql CategoryName, Description sSql Categories ORDER BY Выполнение через объект // и получение DataSet oDs //- Заполнение DataGrid данными из DataSet * new DataSet Определение хранимой процедуры и ее параметров см. след. стр.

и Рис. 3- Файл кода Web-формы 15, 4, //- Выполнение хранимой процедуры через объект // и получение DataSet данными из DataSet new Уничтожение DataSet Прочих отличий ни так и все они следуют из различий в синтакси се: точки с запятой в конце операторов, присвоение (вместо Nothing) при уничтожении объекта и указание типа перед именем в объявлениях переменных (рис. 3). Не забывайте, что чувствителен к регистру букв Ч здесь проще всего сделать ошибку, особенно если вы привыкли к Visual Basic.

Класс SqIService Класс SqIService является компонентом доступа к данным, определяя все его методы, свойства и перечислимые. В начале класса я указываю все используемые пространства имен:

Imports System Imports Imports Imports Imports Затем определяю перечислимое, нужное вызывающему коду (в данном случае Перечислимое указывает различные типы данных, которые можно передавать методу Так я избавляюсь от не обходимости использовать в вызывающем коде ADO.NET-специфичные пространства имен для SqlDataTypes.

Public ssSDT_Bit End Следующий шаг Ч собственно определение класса SqlService (рис. 4).

Здесь объявлены переменные уровня класса для хранения значений, нуж ных для соединения с источником данных. Я также определяю объект для хранения параметров, передаваемых методу при использовании хранимой Создав разные конструкторы, я предоставляю программе разные создания экземпляра класса SqlService. Чтобы опреде лить несколько я просто указываю для каждого из них раз личное число аргументов и/или аргументов.

4. Класс SqlService Public>

У класса SqlService есть также открытые свойства. Я определил их, чтобы иметь альтернативный способ для задания значений строки подключения из вызывающей программы Ч без использования конструкторов.

и Рис. 5. Конструкторы класса SqIService Конструктор класса да 7/ Sub End Sub '// класса (аргумент - строка да 7/ Sub As ffl_sCoRnectionString Sub V/ Конструктор класса V/ Перегружаемый;

'// Sub As String, AS _ As ByVal As String) Server Database = sDatabase = Password * End Sub (рис. 6) Ч перегружаемый метод, который принимает SQL-опера тор и имя нужной вам таблицы (объект а возвращает DataSet.

Вторая RunSql принимает как аргумент только SQL-оператор и ничего не возвращает (рис. 7). Хотя оба метода выполняют SQL-операто ры, первый можно использовать для выборки данных, а второй Ч для до бавления, обновления и удаления данных. Перегрузка RunSql позволяет создать набор методов с одним именем, и конкретная версия выбирается в зависимости от переданных параметров. В данном случае, если вы пере дадите этому методу только SQL-оператор, будет вызван второй вариант RunSql.

В методе, который возвращает DataSet (рис. 6), я объявляю и создаю свои Затем настраиваю объект вызовом ме тода Connect. (SqlConnection можно считать аналогом объекта в ADO После того как свойства SqlConnection настроены, но соединение еще не открыто, я могу задать свойства объекта (Этот объект можно рассматривать как аналог в ADO 2.x.) Затем я присваиваю созданный в соответствии с пере данным SQL-оператором, объекта SqlDataAdap ter. Это указывает SqlDataAdapcer выполнить SqlCommand при выборке данных. Существуют и другие типы команд (InsertCommand, и но на них я остановлюсь в следующих статьях.

В завершение я метод Fill объекта SqlDataAdapter, запол нить DataSet данными из набора результатов, полученного при выполне нии Рис. 6. возвращающий V/ V/ Перегружаемый: да '// Возвращаемое '// SQL-оператор Public Overloads Function As sTableName As String) As DataSet Dim As SqlCommand New Dim oCn As = '// объявить Dim As SqlDataAdapter New Dim As DataSet = New '//- Подготовить соединение oCn = With Установить Connection и объекта SqlCommand - oCn. = sSql = With With oDa '//- объект для SqlDataAdapter = '//- Выполнить SQL-оператор и заполнить DataSet sTableName) End With Отсоединиться от базы данных.

'//- DataSet Return End Function концепций реализация Рис. 7, для запросов, не возвращающих данные V/ метод "// '// V/ SQL-оператор и ничего не Public Overloads Sub sSql As String) As New Dim As = Nothing '// Подготовить с базой данных oCn = sSql = oCn =.

End от базы Sub Объект SqlDataAdapter (в ADO.NET 1 он назывался SqlDataCom не имеет прямого аналога в архитектуре ADO 1.x. играет роль механизма управления четырьмя командами (SELECT, INSERT, UPDATE и DELETE), которые можно выполнить над DataSet. мож но считать мостиком между объектами и SqlConnection) и объектом (DataSet).

DataSet примерно объекту Recordset в ADO 2.x, так как со держит данные, получаемые в результате выполнения запроса. Между тем DataSet может содержать несколько наборов объектам DataTable. Например, DataSet может содержать три связанных друг с дру гом объекта DataTable (вроде таблиц заказов и за казов). Кроме того, DataSet не знает, из какого в него поступи ли данные, что совсем не похоже на объект Recordset в ADO 2.x. По сути, различий у DataSet и Recordset больше, чем общего. К другим особеннос тям DataSet относятся Extended-свойства, позволяющие задавать нестан дартные свойства объекта;

свойства DataRelation для установления связей между разными объектами DataTable;

и средства работы с XML для экс порта в DataSet и импорта из него данных в формате Если вам не нужен результирующий DataSet и вы просто изменяете используйте тот метод который показан на рис. 7. Он прини мает SQL-оператор и устанавливает SqlConnection, Затем настраиваются свойства объекта метод Пос ледний исполняет SQL-запрос, но при этом информирует что результирующих данных он не ожидает. (Это аналогично вызову метода Connection.Execute в ADO 2.x с параметром adExecuteNoRecords.) Если вы не производите выборку данных, гораздо эффективнее обращаться к методу ExecuteNon Query.

У меня также есть две перегружаемые версии метода RunProc. Первая возвращает DataSet (рис. 8), вторая не возвращает ничего (рис. 9). Пер вый RunProc работает почти так же, как метод RunSql за исключением что устанавливает параметры из List-списка Что бы пройти по списку параметров, я создаю объект oEnumerator как пляр класса Это позволяет организовать цикл, добавляя па раметры в набор Parameters.

Рис. 3. RunProc, Dataset '// Открытый V/ да V/ значение: DataSet V/ Назначение: выполняет хранимую Public As String, As String) As DataSet Din As = New As = Nothing As New oDs As DataSet New : As = Nothing As Nothing Получить параметров Dim oEnumerator As = '//- соединение с базой данных oCn With '//- Настройка и '//- объекта = oCn = = End With см. след. стр.

и Рис. 8. возвращающий Dataset Перебор в Do = "//- текущий Создать Добавить объект к объекту Loop Назначить для = процедуру заполнить '//- от базы DataSet Рис. 9. RunProc для запросов, не возвращающих V/ Открытый метод V/ да V/ Возвращаемое значение:

'// выполняет хранимую процедуру Public Sub As Dim = Dim As Nothing As SqlParameter = Nothing oP As = '//- списка As с данных With см. след. стр.

Рис. для возвращающих данные '//- Для = =. =.

Перебор Parameters Do = Nothing '//- текущий объект Parameter = Создать экземпляр SqlParameter объект SqlParameter к объекту Loop Выполнить хранимую процедуру '//- от данных End Sub Данный цикл выполняется, пока метод MoveNext класса lEnumerator не возвращает false, т. е. когда параметров в ArrayList больше нет. Индекс текущего параметра из списка присваивается переменной оР. Затем я преобразую этот параметр в формат, понятный ADO.NET, т. е. в объект SqlParameter. В завершение я добавляю объект SqlParameter к набору SqlParameters объекта и продолжаю цикл. После того как все добавлены в набор, я выдаю SQL-запрос и заполняю DataSet методы Select Command и объекта SqlDataAdapter так же, как и в Второй метод RunProc (рис. 9) служит для выполнения запросов, не воз вращающих DataSet. Здесь я опять указываю ключевое слово Overloads, сообщая Visual Basic, что этот метод следует вызывать при передаче одно го параметра. При передаче двух параметров вызывается другой метод RunProc (рис. 8).

Метод AddParameter (рис. 10) применяется, когда нужно передать пара метры классу при вызове хранимой процедуры. Этот метод при нимает имя параметра, его тип данных, длину и значение. значения помещаются в локальный который добавляется к Array ADO.NETt концепции и List Ч тому самому который я просматриваю в методе Заметьте: я преобразую полученный тип данных (он соответствует одно му из значений перечислителя в соответствующий тип SqlDBType.

Рис. 10. Метод V/ V/ да '// нет V/ Назначение: параметр процедуры Public Sub As, ByVal As ByVal Integer, ByVal As г As SqlDbType Dim As = Select Case ISqlType = eDataType Case eDataType = Case = eDataType = Сазе eDataType New iSize, End SqIService на С# Версия SqIService на С# аналогична описанной выше Ч различия только в синтаксисе. Полностью код обоих примеров (на Visual Basic и мож но скачать с сайта MSDN Magazine ( Метод RunSql, реализованный на С#, выполняет SQL-запрос и (рис. 11). Обратите внима ние на ключевую особенность: возвращаемое значение здесь указывается 4- типом данных этого открытого метода. Кроме того, метод является пере гружаемым (есть две версии RunSql), но явно указывать это (как делает ся в Visual Basic ключевым словом Overloads) не нужно. В С# для пере грузки метода достаточно определить два метода с разными списками ар гументов.

11. Метод на Dataset //- метед да //- значение: DataSet //- Назначение: выполняет SQL-оператор public DataSet sSql, string sTableName) SqlCommand = SqlCommandO;

oCn null;

= new SqlDataAdapterO;

DataSet oDs к базе данных oCn Настройка и для oCn;

sSql;

//- Назначить объект SqlDataAdapter = Выполнить и DataSet Отсоединиться от данных oDs;

Второй метод RunSql, не возвращающий DataSet, предназначен для запро сов, изменяющих данные. Для этого он как void:

public void sSql) и Метод RunProc на С# выполняет хранимую процедуру и возвращает DataSet (рис. 12). От версии на Visual Basic он опять же отличается толь ко синтаксисом.

12. Метод RunProc на Dataset // Перегружаемый:

Возвращаемое значение: DataSet //- хранимую процедуру public DataSet string { i = null;

= null;

SqlDataAdapter DataSet oDs = null;

Подучить параметров * //- Подготовить соединение с базой данных оСп //- Настройка и //- объекта =, CommandType = CommandType.

Перебор a ArrayList while ( ) { oP = null;

текущий oP = Создать Добавить объект к Назначить объект для процедуру и заполнить DataSet см. след. стр.

Microsoft Рис. 12. Метод на возвращающий Dataset от базы данных //- DataSet oDs;

Второй метод RunProc, не возвращающий DataSet, предназначен для зап росов, изменяющих данные, Поскольку он не значений, то объявляется как void:

public void Выполнение кода Ознакомившись с кодом, остается загрузить страницу WebForm.aspx (рис. 13). (Ее внешний вид одинаков для обеих версий Ч на Visual Basic и С#.) Поскольку данные привязаны к элементу управления DataGrid, вам не нужно заботиться о переборе всех записей и их отображении в HTML.

p ] 3, Confections Desserts, and Dairy Cheeses 6 Prepared fisecutmg a and a Dried Sob's Organic Dried I Рис. 13. WebForm.aspx концепции и реализация Заключение Компонент доступа к данным, описанный в этой статье, выносит от носящийся к в отдельные пространство имен и класс. Это по зволяет разделить ADO.NET-код и бизнес-логику, а также унифицировать доступ к данным через ADO.NET.

это пространство имен можно расширить, включив сюда и пространство имен System.Data.OleDb. Б приведенных примерах исполь зуется пространство имен SqIClient. которое при работе с SQL Server обес печивает большее быстродействие в сравнении с пространством имен Но если вашим приложениям нужен доступ к базам данных под управлением СУБД, отличной от SQL Server, можно запросто создать использующий пространство имен System.Data.OleDb (воз можно, совместно с SqIClient). Выбор за вами, но я рекомендую простран ство имен SqIClient, если вы работаете только с SQL Server.

В следующих публикациях я подробнее расскажу о методиках работы с ADO.NET, в том числе о применении четырех разных команд pater, об использовании объектов DataSet для обмена данными между уровнем представления (presentation layer) и уровнем бизнес-логики (business layer), а также об установлении связей (relations) между несколь кими таблицами, представленными в DataSet объектами Джонни Папа (Johnny Papa) Ч компании по разработке программного обеспечения штат Северная Каролина), автор нескольких книг по ADO, XML и SQL Server. Часто выступает на конфе в том числе на VSLive. С ним можно связаться по адресу Боб Разработка собственных провайдеров данных для Data Access Framework* В сборке Framework содержатся пространства имен, где определены базовые классы, используемые при создании нестандартных данных (custom data providers). Кроме в этих ствах имен определено несколько интерфейсов доступа к данным и базовых позволяющих создавать провайдеры данных, способных ствовать с другими нестандартными провайдерами.

С помощью Connection, DataReader и написать провайдер легче, чем провайдер OLE DB. В статье расска зывается об классах и их реализации, а также о том, как на их основе провайдеры данных разных видов.

Windows-платформы уже давно включают API доступа к данным, ванные на универсальной парадигме (provider consumer). Первым появился ODBC (Open Database Connectivity) API, ориентированный на источники реляционных данных. почти одно значно соответствовал интерфейсу SQL (Call-Level Interface), ленному комитетом стандартизации ANSI SQL. ODBC-драйверы либо инкапсулируют протоколы, специфичные для конкретных баз данных, вроде TDS (tabular data stream), используемого SQL Server, либо образу ют уровень, размещаемый поверх API, специфичных для конкретных по * Публиковалось в Magazine. 2001. №12 (декабрь). Ч Прим. изд.

Разработка собственных для Data Access Framework ЮЗ например поверх (Oracle Call Interface). Затем появился OLE DB Ч универсальный набор и типов, распростра нивший ODBC на источники нереляционных данных. Так как OLE DB позволял работать с источниками любых нереляционных данных, создатели любых видов Ч от плоских (flat) файлов до иерархических и многомерных данных Ч были заинтересованы в объектной модели OLE DB.

Microsoft поставляется со сборкой 11, содержащей про странства имен, которые напоминают OLE DB и ODBC. В ее пространстве имен определен общий набор интерфейсов доступа к данным, а в присутствует несколько абстрактных базовых классов, реализующих общую для всех провайдеров.

Кроме в входят два пространства имен ta.OleDb и которые предоставляют параллельные наборы классов и интерфейсов, называемые в документации провайдера ми данных. Провайдеры данных инкапсулируют в наборах управляемых классов и интерфейсов средства взаимодействия с базами данных и дру гие API-функции доступа к данным. Типы и интерфейсы, предоставляе мые провайдерами данных, наследуются от общего подмножества и обес печивают почти эквивалентную (но не идентичную) функциональность.

Так как не требуется, чтобы у всех провайдеров были одинаковые типы и интерфейсы, разработчики провайдеров могут реализовать функциональ ность, уникальную для источника данных. Кроме того, под держивает модель отсоединенных объектов данных (disconnected object model), основанную на типе DataSet, который является более совершенной версией библиотеки клиентских курсоров ODBC и отсоединенного объек та Recordset (OLE DB).

В документации на SDK Beta 2 поясняется, как разрабатывать про вайдеры какие типы и интерфейсы содержит стандартный про вайдер и как использовать методы интерфейсов, чтобы обеспечить взаимо действие с нестандартными провайдерами данных. Там же дан пример провайдера для доступа к нестандартному хранилищу данных. Информа ции, приведенной в документации, вполне достаточно, чтобы понять, как ре ализуются типы и определенные в System.Data, используются вспомогательные базовые классы из Я покажу простейший пример провайдера данных го стандартный набор типов и интерфейсов. На этого примера мы исследуем объектную модель провайдера данных, отделим базовую функ циональность от расширенной и рассмотрим некоторые расширения, спе цифичные для конкретных провайдеров. Я также расскажу о причинах, по которым доступ к данным предоставляется через провайдеры, и об альтер 104 Microsoft нативах провайдерам данных. что код для этой статьи был напи сан в расчете на Beta 2.

Зачем создавать провайдеры данных?

Во времена OLE DB и ODBC драйверы и провайдеры по разным причинам. Реализовав вы получали возможность рабо тать с наборами данных через стандартные GUI-элементы. Программы генерации отчетов, обмена данными и другие аналогичные приложения использовали один и тот же API. Стандартом обмена данными де-факто стали ADO Recordset и RDS (Remote Data Services), поэтому многие пы разработчиков создавали провайдеры, предоставляющие данные толь ко в виде ADO-объектов Recordset. Появилась даже перспектива создания клиентов, работающих по принципу однажды, используется с чем Провайдеры и создавались, даже если способ дос тупа к данным мало соответствовал или вообще не соответствовал объек тной модели. Так, был разработан ODBC-драйвер для доступа к текстовым файлам через SQL и архитектуру OLE DB Simple Provider.

Различные среды визуальной разработки для Windows, например Visual Basic, предоставляли доступ к данным через элементы управления, связы ваемые с данными (data-bound controls). Чтобы провайдеры и драйверы могли работать с такими элементами управления, вы должны были писать их в соответствии с дополнительным набором правил и реализовать в них поддержку стандартных объектов и интерфейсов, Последней из таких спе цификаций для разработчиков провайдеров OLE DB и элементов управ ления, связываемых с данными, была ActiveX Control Writer's Specifica tion for OLE DB.

Провайдеры ODBC и OLE DB удобны тем, что их можно напрямую ис пользовать в приложениях Microsoft и сторонних фирм. Например, в Microsoft Access можно задействовать любой ODBC-драйвер для прямого доступа к данным Ч тогда Access-приложения работают с ними так, будто получают их из базы данных Access. SQL Server и DB/2 (в Windows NT) позволяют применять источники данных OLE DB в распределенных зап росах. Кроме начиная с SQL Server 7.0, любой провайдер OLE DB можно использовать для импорта, экспорта и преобразования данных че рез DTS (Data Transformation Services). В Crystal Reports ODBC-драйве ры или Recordset служат для получения входных данных, из которых формируется отчет. В других приложениях с помощью ADO или ODBC генерируются или XML-схемы. Эти и другие примеры показывают, что разработка OLE DB или ODBC драйверов весьма перспективна.

Разработка собственных провайдеров данных для Data Access Framework Создание провайдера данных В Visual C++ 6.0 Microsoft ввела набор классов на основе (ActiveX Template Library), позволяющий разрабатывать провайдеры OLE DB. С провайдеров OLE DB поставлялся мастер (wizard), кото рый инфраструктуру ATL и пример провайдера, предоставляющего доступ к структуре (с информа цией о файлах и каталогах) через и интерфейсы OLE DB. Я настолько часто применял этот провайдер в демонстрационных це лях, что даже присвоил ему стандартное имя: Ч провайдер OLE DB для доступа к информации о каталогах. Поскольку в есть клас сы со схожей функциональностью, я переделаю тот пример в провайдер данных и назову его (managed Написать провайдер данных для гораздо проще, чем для OLE DB. Интерфейсы в ADO.NET четко определены;

создавая класс с опреде ленной функциональностью, вы должны реализовать интерфейс. При этом нужно реализовать минимум четыре основных клас са: Connection, Command, и DataAdapter. Вкратце рассмотрим эти классы.

Connection Этот класс обязателен, даже если вы не собираетесь на деле подключаться к источнику данных. Он нужен другим классам, так как предоставляет им базовую функциональность. DataAdapter, например, вызывает методы Open и Close класса Connection при заполнении Data Table, входящего в DataSet.

Command У класса Command как минимум два предназначения. В язы ках, используемых при операциях с команды позволяют напря мую обновлять хранилища данных. Скажем, в SQL такими командами яв ляются INSERT, UPDATE и DELETE. Кроме того, можно передавать коман ды, возвращающие результаты. Пример такой команды в SQL Ч SELECT.

DataReader Класс DataReader используется для обработки результатов, возвращаемых Command. Его методы позволяют перебирать записи одно го или нескольких наборов результатов с перемещением только вперед (forward-only). Кроме того, он предоставляет методы для считывания дан ных из полей этих записей в переменные DataAdapter Класс DataAdapter заполняет DataSet результатами, воз классом Command, а также позволяет обновлять данные с по мощью команд и предоставляет событийную модель для операций обнов ления, требующих тонкой настройки.

Эти обязательные классы показаны на рис. 1;

там же отражены некоторые между ними. но не все Ч класс mand может быть создан не только методом Create Command, но и указанием Connection в одной из перегруженных версий конструкто ра класса Command. Поскольку ряд классов зависит друг от друга, на ил люстрации представлен минимальный набор реализуемых на практике. Для начала я реализовал провайдер, содержащий эти классы и поддерживающий только чтение. Создав базовую функциональность (рис. 1), дополнительные сервисы можно разместить поверх существующих обяза тельных классов или определить через вспомогательные классы. Обо всем этом я и расскажу в статье.

and Рис. 1. Некоторые между классами провайдера данных Класс Connection До реализации класса Connection надо реализовать интерфейс tion. В IDbConnection (рис. 2) шесть открытых методов, среди которых наиболее очевидны Open и Close. Так как в исполняющей среде не концепция детерминированной (deterministic destruction), вы должны явно вызывать метод Close, а не просто освобож дать все указатели на интерфейс, как это можно было бы сделать в OLE DB. Метод ChangeDatabase используется, если ваш источник данных дол жен поддерживать подключение к другой базе данных.

Метод BeginTransaction запускает локальную транзакцию. BeginTransac tion возвращает интерфейс IDbTransaction, через который вы вызываете методы Commit или Rollback. Имеется перегруженная версия BeginTran saction, принимающая в качестве параметра уровень изоляции транзакции (transaction isolation level). Если ваш источник данных не поддерживает локальные транзакции, реализовать метод BeginTransaction не нужно.

Наконец, IDbCreateCommand создает объект Command вашего провайде ра и ссылку на интерфейс IDbCommand, У IDbConnection. четыре открытых свойства. Connec и Database Ч это наиболее часто используемые свойства, от носящиеся к соединениям. Четвертое открытое свойство, State, крайне важно. Оно доступно только для чтения и возвращает значение перечис лимого ConnectionState (табл. 1). Смысл значений Open и Closed данных для Data Access Framework Они если ваш источник данных поддерживает асинх ронную инициализацию, a Fetching применяется, если источник данных поддерживает асинхронную выборку. Broken соответствует состоянию, когда соединение открыто, но не работоспособно, например, если исполь зуемая вами база данных завершает работу по указанию системного адми нистратора.

Рис. 2. Интерфейс public { ;

void void // Свойства // get, set // get, string Database // set State // Табл. 1. Перечислимое ConnectionState Член Описание Broken Связь Это возможно только после открытия соеди нения. Если соединение находится в таком состоянии, его можно а потом открыть снова.

Closed Объект закрыт.

Connecting Выполняется подключение к объекту.

Executing Объект команду.

Fetching Выполняется выборка данных.

Open Объект открыт.

Реализация класса Connection Чтобы реализовать интерфейс IDbConnection, определитесь, к какому ис точнику данных вы будете подключаться и какая вам нужна.

Если требуемая информация соответствует используемой базе данных, тем лучше. Если нет, придется включать дополнительные свойства и поля.

Для получения примитивной информации о каталогах я реализовал стые методы Open и Close (они лишь присваивают значения свойству 108 ADO.NET а формированием строки выбором щей базы данных и созданием транзакций я, естественно, не занимался.

Класс Connection должен разрешать создание своих другими классами. Следует написать минимум два конструктора: не принимающий аргументов и принимающий аргумент типа String (строку подключения).

Так как мой провайдер данных не использует строку подключения, я реа лизую только первый конструктор. Состояние создаваемого объекта Con nection должно инициализироваться значением ConnectionState.Closed;

для этого закрытой переменной _ConnectionState присваивается соответ ствующее значение.

При реализации я придерживался нескольких простых соглашений.

на закрытых полей, к которым обращаются свойства, начинаются со зна ка подчеркивания. Когда не поддерживается или не реализована генерируется исключение вызовом со ответствующего закрытого метода. Реализация включена в набор исходного кода, который можно скачать с сайта MSDN Magazine по ссылке в разделе за де кабрь.

функциональность в Connection Спецификация OLE DB позволяла разработчикам провайдеров реализо вать нестандартные интерфейсы, специфичные для конкретных провайде ров. То же самое можно делать и при создании данных.

Кроме того, вы можете реализовать методы экземпляра (или их перегру женные версии), специфичные для провайдера. Как и в случае OLE DB, универсальный клиент может использовать ли ему методы, спе цифичные для провайдера. Самый надежный способ распознавания держиваемых возможностей в период выполнения Ч приведение типов интерфейсов/классов. Для этого можно использовать ры (marker interfaces).

Специализированные клиенты могут находить специфичные для провай методы, перегруженные версии и свойства через отражение (reflec tion), не зная заранее об их наличии, и получать доступ к ним в соответ ствии с разрешениями. Так как все эти действия производятся в период выполнения, ни один метод не может заблаговременно сообщить потреби телю сведения, касающиеся семантики использования специфичной фун кциональности.

Примером специфичной функциональности класса Connection является набор параметров, передаваемых в строке подключения в случае SqlClient.

Кроме стандартных параметров вроде Data Source, User ID и Password (за Разработка данных Data Access Framework из спецификации OLE SqlClient поддерживает пара метры, специфичные для SQL Server (например, Network Library и размер буфера TDS).

Более сложный пример специфичной функциональности Ч реализация пула соединений, повышающего эффективность совместного использова ния соединений в трехуровневой среде, например в случае Web-сервера. И и SqlClient поддерживают создание пула соединений, но семанти ка этой поддержки различна.

Класс Command Класс Command должен реализовать интерфейс (рис. 3), Основное назначение IDbCommand Ч отправлять команды или запросы хранилищу Команды, изменяющие данные, но не генерирующие набор результатов, передаются методом возвращающим общее число записей, на которые воздействовала команда.

Команды-запросы возвращают наборы записей через класс DataReader.

Метод вовзращает интерфейс IDataReader класса DataReader. Метод ExecuteScalar используется, чтобы получить первое поле первой записи, но применим и для получения скалярного ре зультата. Возможное применение этого метода Ч чтение набора записей, который на самом деле является целым экземпляром объекта или доку ментом.

Для имеется перегруженная версия, прини мающая параметр Этот параметр указывает, что долж но происходить при выполнении команды. Значения перечислимого Com mandBehavior показаны в табл. 2.

Табл. 2. Перечислимое CommandBehavior Член Описание При команды связанный с ней объект Connection закрывается, когда с той же командой объект DataReader Запрос информацию о полях и первичном ключе Запрос возвращает информацию только о полях и не на состояние базы данных Результаты считываются последовательно на уровне полей Запрос один результатов;

выполнение может повлиять на состояние базы данных Ожидается, что запрос вернет одну запись;

может повлиять на состояние базы данных Рис. 3.

public interface public void public int public IDataReader object public string // get, set public int // get, public // get, public // get, set public // get public Transaction // get, set // set Для команд необходимо задавать свойства (саму команду) и Эти свойства являются открытыми. Кроме того, как от мечается в документации, строку CommandText и экземпляр класса Con nection можно указать в перегруженной версии конструктора. Единствен ный CommandType, который нужно поддерживать, Ч это Другие типы служат для работы с хранимы ми процедурами и использования имен таблиц вместо команд (последнее только в OLE DB). Команды можно отменять напрямую (методом Cancel) или по истечении срока ожидания свойством Command Timeout).

Типы команд содержат ссылки на другие типы объектной модели. С объ ектом Command нужно связать объект Connection (как ссылку на IDbCon nection), указав на соединение в конструкторе или напрямую задав соот ветствующее свойство. При необходимости с командой можно сопоставить объект Transaction (как ссылку на IDbTransaction). Transaction также за дается в конструкторе или наследуется от нижележащего объекта Con nection.

Хранимые процедуры или параметризованные запросы используют набо ры параметров. Для работы с параметрами предназначен.NET-тип, явля ющийся набором Набор Parameters Ч это свойство класса Command, который включает и метод Parameter. Наконец, RowSouree указывает, какая версия используется при обновле ниях, выполняемых через класс DataAdapter.

Разработка провайдеров данных Data Реализация класса Command В моем примере провайдера в качестве входных данных использу ется имя каталога. Для этого достаточно типа команды Поскольку мне не нужны транзакции, параметризованные запросы и воз можность отмены команды, основную часть реализации составляют мето ды выполнения команды. и ExecuteReader создают эк земпляр DataReader, вызывают внутренний метод и воз вращают либо число элементов в каталоге (количество обработанных записей), либо DataReader, эти записи.

Рис. 4. ExecuteReader public // должно быть и открыто if == throw new be valid and if & > 0) includes if & > 0} // Обрабатываем или if <(Ь & > 0) { reader new recto return reader;

I { reader = new reade Di recto return reader;

Самый сложный в программировании Ч перегруженный мегод Execute Reader, принимающий параметр типа Command Behavior. Поскольку неко значения можно объединять логической опера цией лили, при реализации метода приходится выполнять побитовую проверку параметра. Хотя можно указать или инфор мация о схеме все равно возвращается вместе с данными (через метод класса DataReader). Особый случай Ч При этом значении вызывается конструктор DataReader, который прини мает объект Connection, командой. А когда вызывается ме тод Close класса DataReader, закрывается и связанный с командой объект Connection. Реализация метода показана на рис. 4.

Специфичная функциональность в Command Провайдеры данных SqlCIient и реализуют почти всю функцио нальность IDbCommand. Так как базы данных во многих случаях быстрее генерируют отдельные результаты, а не наборы, оба провайдера поддержи вают для CommandBehavior значение В обоих провайдерах ре ализован метод Prepare, позволяющий отправить базе данных отдельную команду для анализа запроса и подготовки плана его выполнения. Кроме того, SqlCIient уникальным образом расширяет возможности класса Com mand. Поскольку SQL Server 2000 может возвращать результаты запроса в виде потока SqlCIient содержит открытый метод XmlReader, возвращающий вместо IDataReader.

Класс DataReader У класса DataReader нет открытого конструктора. Этот класс должен реа лизовать интерфейсы IDataReader и IDataRecord. Класс DataReader по зволяет читать данные по одной записи единовременно в направлении только вперед и получать значения полей извлеченных записей как типи зированные или обобщенные типы данных. Определения интерфейсов IDataReader и IDataRecord показаны на рис. 5.

IDataReader дает возможность проходить набор результатов методом Read, который возвращает false, если конец набора записей. При выполнении одной команды может возвращаться несколько наборов ре зультатов. Для перехода к следующему набору результатов используется метод NextResult.

Иногда нужно получить описание информации, в наборе результатов, Ч так называемые метаданные. Метод GetSchemaTable интер фейса IDataReader может возвращать DataTable с метаданными для каж дого набора результатов. Таблица, возвращаемая GetSchemaTable, содер жит как стандартную информацию, так и специфичную для провайдера, Разработка собственных для Data Access Framework Рис. 5. Интерфейсы и IDataRecord public interface - public Close() public public bool // Свойства public Depth int public interface { Х// IDataRecord bool i) byte i) public int i, int int length, int char i) public int i, chart] buffer, int public IDataReader public i) public i) decimal i) double i) Type i) public float i) i) public short i) public int i) public long i) public int public i) public i) public int values) bool i) // IDataRecord public int public object Microsoft предоставляет методы, позволяющие провайдерам возвра щать строго или слабо типизированные данные из каждого поля. Суще ствует ряд строго типизированных (getters), прини мающих порядковый номер поля с отсчетом от нуля, а есть и универсаль ные методы и значения типа object.

IDataRecord всегда возвращает управляемые таким образом, этот интерфейс инкапсулирует проекцию системы типов источника дан ных на систему Кроме того, IDataRecord содержит перечис лители (iterators) полей, доступные как перегруженные версии свойства Item. Эти перечислители возвращают поле по имени или порядковому номеру с отсчетом от нуля.

Реализация Класс DataReader содержит специфичный для провайдера метод Get Directory, который вызывает управляемый метод Провайдер данных предоставляет доступ к под множеству возвращаемой этим методом информации, позволяя получить поля Name, Size, Type (файл или подкаталог) и CreationDate. Name и Туре имеют тип String, Size Ч a CreateDate Ч DateTime. Поскольку в каж дом наборе результатов возвращаются одни и те же метаданные, результа ты помещаются в четыре массива: типов (управляемый тип каждого размеров (размеры этих типов), имен (имена полей) и полей (значения типа object). Хотя элементами массива полей являются объекты, каждое поле Ч это экземпляр соответствующего типа, т. е. в отличие от ADO поля не представляются типом Variant. Поскольку метаданные всегда одни и те же, получение информации о схеме в методе GetSchemaTable. За щищенная реализация класса DataReader и метода Read показана на рис. 6.

При реализации строго типизированных использу ется простое приведение типов. Неправильное приведение типа (напри мер, применение для типа String) вызывает генерацию как и в провайдерах данных SqlClient и скольку все значения данных уже относятся к управляемым типам, преоб разование типов хранилища данных в управляемые типы не требуется.

Свойства Item реализованы с помощью индексаторов (indexers) языка С#.

Провайдер не поддерживает формирование нескольких наборов тов одной командой.

Чтобы элементы управления, связываемые с данными, могли использо вать класс DataReader, этот класс должен реализовать интерфейс Я предпочел реализовать с помощью класса DbEnume rator имен Провайдеры данных Sql Client и OleDb тоже используют этот класс. Чтобы реализовать метод Get Enumerator интерфейса lEnumerable, я возвращаю экземпляр Разработка собственных данных Access Framework конструктором, в который передается экземпляр моего класса DataReader:

{ return new Рис. 6. Реализация // public i if (_le i null) { bool if (notEOF if is { f = "0";

* "Directory";

* return notEOF;

return false;

} // Реализация // и закрытие соединения, * флаг задает * обычное (без закрытия), */ см. след. стр.

AD0.NET Рис, 6. Реализация private null;

internal int internal _ie;

internal Дnames internal Type[] = internal new Максимальный размер в байтах Дsizes 1024, 8, 9, new _fsi = = _ie false;

Специфичная функциональность в DataReader Провайдеры и SqlClient no-разному работают с управляемыми ти пами. У SqlClient есть перечислимое а пространство имен Sys включает типы, специфичные для SQL Server. Помимо строго типизированных (например, пре типы данных SQL Server в в имеется ряд методов-получателей (скажем, GetSqlInt32), которые предоставляют доступ к типам SQL Server из пространства имен без всякий преобразований. В документации SDK утвер ждается, что при использовании типов достигается большее быстродействие, чем при преобразовании в управляемые типы.

Провайдер данных OleDb конвертирует OLE DB DBTYPE в управляемые типы, поскольку типы, используемые хранилищем данных, отображаются провайдером на OLE DB. Для нас представляет интерес только интерфейс методом Data. В OLE DB поддерживается концепция разделов (chapter в соответствии с которой иерархические данные предоставляются через спе циальное поле типа chapter, связывающее родительский и дочерний набо ры записей (rowsets). Data возвращает DataReader, уста навливаемый на дочернюю запись, которая относится к заданной роди тельской. Ту же концепцию можно было бы применить и в провайдере данных для рекурсивного обхода подкаталогов файловой системы, Разработка данных для Data Framework Класс DataAdapter DataAdapter Ч один из немногих классов провайдеров данных, который реализуется на общем базисе. Ваши классы DataAdapter, специфичные для конкретных провайдеров, наследуются от класса DbDataAdapter, который в свою очередь является производным от DataAdapter. Эти классы реали зуют интерфейсы IDataAdapter (в котором методы Fill и Update, используемые при взаимодействии с DataSet) и IDbDataAdapter.

Последний предоставляет доступ к четырем объектам Command (Select Command, UpdateCommand, и DeleteCommand), определя ющим взаимодействие между провайдером и DataSet. Кроме того, класс DataAdapter содержит стандартный набор событий и делегатов, позволя ющих принимать уведомления и влиять на поведение класса до и после обновлений (под обновлениями подразумеваются операции Insert, Delete или Update, выполняемые над хранилищем данных). Иерархия классов и интерфейсов показана на рис. 7.

IDbDataAdapter t Наследование t Наследование I Рис. 7. Иерархия наследования Реализация DataAdapter Реализация DataAdapter провайдера данных DirProv будет ной, поскольку этот провайдер предоставляет данные только для чтения.

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

Базовый класс, DbDataAdapter, содержит почти все необходимые для класса методы (Fill, FillSchema и др.).

работает так, как и должен, потребителю данных заполнять DataSet методом Fill и предоставляя доступ к SelectCommand. Изучая Microsoft код провайдера, вы заметите, что метод Fill класса вызывает передавая флаг поведения а метод этого класса использует флаг поведения Специфичная функциональность в DataAdapter Провайдеры SqlClient и реализуют подклассы и делегаты. В провайдере OleDb реализована перегруженная версия класса Fill, позволяющая заполнить DataSet или DataTable данны ми из классического ADO-объекта Recordset.

Добавление дополнительной функциональности Итак, я рассказал обо всех обязательных объектах в объектной модели провайдера данных. В качестве заключительного аккорда хотел бы отме что провайдер данных полностью готов к работе и может быть расширен для обслуживания трассировки более высокоуровневых вызовов, поступающих в стек данных, управляемых ADO.NET. Весь ис ходный код можно скачать по уже упоминавшейся ссылке с сайта Magazine. В моем провайдере реализован только базовый набор типов и интерфейсов, поэтому, как и в случае провайдеров OLE DB, вы могли бы добавить в него дополнительную функциональность. Для полноты карти ны я кратко опишу доступные расширенные типы, а также определения и реализации базовых классов: типы классы, реализующие па раметры и наборы параметров, класс типы, применяемые при обработке ошибок, и типы разрешений. Обратите внимание, что про вайдерах данных SqlClient и OleDb реализованы специфичные вариации этих типов и перечислимых.

Типы Типы транзакций инкапсулируют семантику локаль ных транзакций, которые могут запускаться внешними по к провайдеру объектами. В состав как OleDb, так и SqlClient входят типы Transaction, реализующие интерфейс IDbTransaction.

Параметры и наборы параметров Эти классы реализуют наборы (collec tions) параметров, используемые хранимыми процедурами и параметризо ванными запросами. Параметры должны преобразовывать в типы базы данных методами, аналогичными методам IDataReader.

meter и Parameter реализуют один и тот же интерфейс IDataPara а наборы параметров Ч интерфейс а также интерфейсы для операций над наборами ICollection и CommandBuilder Этот класс предназначен для того, чтобы упростить формирование используемых по умолчанию команд dateCommand и DeleteCommand класса DataAdapter на основе Разработка собственных данных Access получаемых из базы данных. И в SqlClient, и в есть своя вер сия Command Builder, но общего интерфейса или базового класса у этих версий нет.

Типы, применяемые обработке ошибок При разработке любого API доступа к данным весьма сложно увязать последовательность выполняе мых операций с обработкой ошибок (особенно с учетом их разнообразия).

Как в так и OleDb реализован набор содержащий объекты, которые описывают ошибки. Эти типы не являются расширени ями общего базового типа. Однако они реализуют интерфейсы наборов.

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

Типы разрешений Провайдеры данных должны соответствовать требо ваниям архитектуры защиты в а клиентам нельзя разрешать вызовы защищенных функций лишь что они используют высокоуровне вый API. Поэтому в провайдере данных следует реализовать типы Permis sions и PermissionsAttribute. Базовые классы и DBData на основе которых создаются классы, отвечающие тре бованиям защиты, содержатся в Альтернативные варианты реализации Я создал провайдер данных чтобы исследовать модель провай деров данных в Огромная часть доступной функциональности в этом провайдере не реализована. А каковы альтернативные варианты?

Является ли провайдер данных лучшим выбором? по которым к данным обращаются через провайдер, Ч упрощение обмена данными, интеграция с GUI-компонентами и возможность доступа к данным из сто ронних приложений вроде Crystal Reports.

На момент написания статьи разработчикам было доступно минимум четы ре способа создания провайдеров данных, совместимых с платформой.NET-провайдер данных Неплохой вариант, если источник данных (обычно реляционная или какая-то другая база данных) использует соб ственный протокол, с которым можно работать с помощью набора управ ляемых классов. За примерами далеко ходить не надо. Вспомните Sql Client, в котором реализован анализатор TDS (TDS parser), или OleDb, который инкапсулирует API, основанный на СОМ. Такой вариант наибо лее эффективен, когда источник данных допускает обновление и поддер живает локальные параметризованные команды и нестандартные наборы ошибок. В идеале источник данных должен поддерживать множественные табличные наборы результатов (multiple пакеты команд и обновления, управляе мые на основе команд. Заметьте, что сейчас в модели нет поддержки об новлений по месту (управляемых не на основе команд) и серверных курсоров.

Нестандартный XmlReader или XPathNavigator Этот вариант особенно хорош, если ваши данные имеют иерархическую структуру (точнее, не таб личную) и требуют доступа на основе навигации (курсоров), а не наборов данных. Для источников данных, оптимизированных под выборку подмно жеств т. е. чтение только части иерархии, отлично подходит нестандартная реализация XPathNavigator. Кроме навигации, можно ис пользовать и напрямую работаю щие с данными. Аарон (Aaron написал серию классов XPathNavigator (для файловой системы, и др.), демонстриру ющих такой подход. Они доступны на сайте aarons.

провайдер OLE DB или ODBC-драй вер Хотя в конечном счете такой подход выйдет из употребления, это по-прежнему лучший вариант, когда вам нужно выдавать распределенные запросы к SQL Server, использовать SQL Server напрямую включать объекты в и т. д. Оба API реализуют модель курсоров с поддержкой обновления по месту. Если вы разработаете провайдер OLE то почти со стопроцентной гарантией сумеете интегрировать его с через провайдер данных Программное заполнение DataSet или XmlDocument Используйте с этой целью специфичный для ваших данных. Если вы хотите лишь получить набор данных, оба можно программировать напрямую. Вы не обязаны реализовать навигацию, поддержку обновлений, соединения и команды, если все это вам не нужно.

DataSet можно использовать для обмена данными, предостав ляя функциональность, аналогичную той, которая есть у ADO-объекта Recordset. DataSet можно напрямую задействовать в Web-сервисах, и он поддерживает маршалинг по значению. Но самое интересное Ч как Data Set осуществляет маршалинг по значению. В отличие от ADO-объекта Recordset, использующего двоичный формат (advanced data tablegram, ADTG), DataSet выполняет маршалинг в формате XML, как и принято в XML Ч универсальный формат маршалинга и обмена данными в Поэтому нет необходимости реализовать провай дер данных только для того, чтобы программно заполнять DataSet.

Разработка собственных данных Data Access Framework Ту же функциональность можно на основе нестандартного XmlReader или который обращается к XML infoset, а не к DataSet. XML infoset Ч непрозрачное (opaque) представление данных XML-документа в памяти. Зачастую infoset является наиболее ным представлением XML-документа, в формат XML не требуется, поскольку доступ к данным обеспечивается с помощью модели XML infoset. Кроме того, XML infoset лучше подходит для таких нереля ционных данных, как гомогенные и гетерогенные иерархии или полу структурированные данные (данные в структурированном документе).

Учтите, что вы можете не только предоставлять прямой доступ к данным как к но и программно заполнять DataSet. Как и в случае Recordset, нет необходимости использовать модель Connection DataReader.

Visual Studio упрощает интеграцию элементов управления не только с провайдерами данных, но и с любыми источниками данных.

управления, связываемые с данными, способны работать с любыми типами, реализующими или ICollection. Дизайнеры Visual Studio тоже поддерживают такую интеграцию. Поэтому для поддержки элементов управления создавать провайдер данных не нужно.

В некоторые сторонние программные продукты уже вводится прямая под держка Так, Crystal Reports который будет поставляться с Visual Studio позволяет использовать при построении отчетов клас сы DataSet наряду с ODBC-драйверами и ADO-объектами Recordset.

Заключение Хотя мой провайдер данных не имеет прямого отношения к объектам Connection, Transaction и Parameters или к преобразованию типов источ ника данных в управляемые типы, он позволяет заполнять DataSet. Этого можно было бы добиться и по-другому, но в любом случае этот пример весьма полезен, так как благодаря ему вы изучили архитектуру провайде ров данных.

Боб Ч старший преподаватель в занимающийся программированием более 20 лет. Соавтор учебного курса DevelopMentor Работает над книгой Essential для Addison С ним можно связаться по адресу bobb@develop.com.

Джонни Папа Доступ к данным Выражения в рассматривает основы применения в ADO.NET вычисляемых полей и функцию Compute, подсчитывающую значения. Также рассказы вается о функциях агрегации в объектах получении итоговых значений, выполнении других вычислений по всему объекту DataSet и связывании объектов принадлежащих DataTable. Кроме того, дано несколько практических примеров.

С появлением ADO.NET управлять отношениями в базе данных стало го раздо проще. Вместо получения из данных одного набора за писей теперь можно считывать группы наборов записей, а затем связывать их в объекте DataSet. Связанные DataTable имеют ряд преимуществ и, в частности, позволяют хранить данные в виде иерархи ческой структуры, облегчают обновление данных и дают возможность ис пользовать выражения в полях.

Сегодня я расскажу об основах применения в ADO.NET вычисляемых полей (column-based expressions) и подсчитывающей агрегатные значения. Также будут рассмотрены функции агрегации в объектах DataColumn, получение итоговых значений (totals), выполнение других вычислений по всему объекту DataSet и связывание объектов принадлежащих разным DataTable. Кроме я приведу несколько практических примеров.

Суммирование и вычисление средних значений по группам связанных за писей в SQL-запросах, вероятно, вам отлично знакомо, поскольку такие функции агрегации, как SUM и являются частью стандарта ANSI Публиковалось в Magazine/Русская 2003. №1 (январь). Ч Прим. изд.

Выражений в ADO.NET SQL. SQL также позволяет выполнять вычисления по полям, например умножение цены единицы товара на количество товаров. Благодаря ADO.NET эти возможности доступны не только в источнике данных, но и на промежуточном и более высоких уровнях приложений.

Используя вычисляемые поля, можно создавать свои поля, агрегирующие значения по входящим в DataSet наборам данных, вычислять значения по другим значениям той же записи и даже обращаться через к полям родительских или дочерних объектов DataTable. С появлением в ADO.NET вычисляемых полей и функции Compute стали доступны новые способы управления объединением данных.

Конечно, у вычисляемых полей, функций агрегации и функции Compute в ADO.NET есть свои плюсы и минусы. Выражения, указываемые для вычисляемых полей, могут обращаться к полям как одного DataTable, так и двух объектов DataTable одного и того же DataSet, связанных через DataRelation. Я какие преимущества дают вычисляемые поля в ADO.NET и SQL и в чем между ними разница. Также рассмотрю опера ции, связанные с применением объекта DataRelation. (Об объектах Data Relation см. мою рубрику за ноябрь 2002 г. на Здесь же я покажу, как создавать объекты содержащие выражения, применять фун кции агрегации в объектах DataSet и операторах SQL. Кроме того, вы уз наете, как группировать данные и обращаться в дочернем DataTable к по лям родительского DataTable, а также выполнять вычисления по полям наборов данных DataSet с помощью функции Compute.

SQL-выражен ия SQL-выражения бывают разных видов и служат разным целям, в частно сти для форматирования строк, вычисления пользовательских функций и математических расчетов. Если в SQL-операторе объединяются имя и фа милия, вычисляется сумма позиции заказа или содержится пользователь ская функция SQL Server 2000, извлекающая дату заказа, значит, в этом операторе есть выражение.

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

Так, если хранить в записи количество, цену и сумму, может что количество равно 10, цена за единицу Ч $7.00, а сумма почему-то Такого быть не должно, но при хранении избыточных данных Microsoft вполне вероятно. При работе с базами данных, как пра вило, не хранят информацию, которую можно получить на основе имею щейся (в нашем примере это сумма позиции заказа).

В таких случаях применяются SQL-выражения. Сумму вы получаете, со здав в SQL-операторе вычисляемое поле, где цена умножается на количе ство (рис. 1). Если есть скидка, ее тоже можно учесть при вычислении Рис. 1. Математические выражения в SQL USE GO :

(UnitPrice * Quantity) AS * Quantity) * (1 - AS [Order BY Следующий SQL-код иллюстрирует конкатенацию строк в SQL-выраже ниях. Здесь из имени и фамилии формируется строка, содержащая имя и фамилию (с буквами верхнего регистра), причем сначала идет фамилия:

USE pubs GO SELECT AS AS + ' + AS + ' + AS FROM authors ORDER BY SQL-выражения позволяют форматировать строки и выполнять математи ческие операции, возвращая результаты в наборе Но, их в этих целях, остерегайтесь ошибок. Если DataSet заполнен SQL-опе ратором с рис. 1 и, например, в первой записи изменится поле с количе ством товара, то вычисляемые поля останутся прежними. Так, если коли чество равно 10, а цена Ч $7.00 и мы поменяем количество на 5, то в поле сохранится считанное из базы данных значение ( * $7.00), т. е. нарушится синхронность данных. Таким образом, основная проблема в том, что выражения не переносятся из SQL-оператора в ADO.NET-объект DataSet.

Выражения в объектах Выражения можно определять и в ADO.NET-объектах DataColumn, созда вая вычисляемые поля. Вместо вычисления суммы в SQL-операторе вы создаете DataColumn, содержащий сумму. Одно из отличий между жением в SQL-операторе и выражением в DataColumn заключается в том, что если одно из полей, участвующих в изменится, Data Column с вычисляемым полем тоже автоматически изменится, тогда как DataColumn с результатом вычисления SQL-выражения (например, пока занного на рис. 1) Ч нет.

Рис. 2 демонстрирует, как заполнить DataTable в DataSet SQL-оператором, а затем создать DataColumn, содержащий выражение, вычисляемое по другим полям объектов DataTable этого DataSet. Затем представление по умолчанию (default view) этого DataTable связывается с объектом Data Grid Ч grdOrderDetail.

Рис. 2. Заполнение DataSet и добавление выражений private void // if * new + "Initial //- команду * "SELECT + [order details] od ШЕЙ JOIN p ON + = " BY new = см. стр.

Рис. 2. Заполнение DataSet и добавление выражений //- с DataAdapter и DataSet // new DataSet * new вычисляемые // * Связываем с // = В этом коде создается DataSet, заполняемый информацией о позициях заказа. Затем в DataTable этого DataSet добавляется вычисляемое поле Ч Extended Price с типом данных decimal. В выражении вычисляется произ ведение полей с количеством и ценой. В таких выражениях можно обра щаться к любому DataColumn объекта DataTable, для которого определе но выражение. Значения полей берутся для если в первой записи указаны количество 10 и цена $7.00, то значением поля суммы будет $70,00.

В выражениях могут применяться любые другие поля DataTable, а также литеральные значения. Например, наше выражение, считающее сумму, можно изменить, чтобы учитывалась скидка. Для этого код добавления поля надо модифицировать так;

* Quantity) * (1 Попробуйте изменить значение любого из объектов DataColumn Ч Unit Price, Discount или Quantity. В отличие от поля с SQL-выражением зна чение DataColumn ExtendedPrice сразу изменится. Эта возможность очень удобна для приложений, работающих с корзиной покупателя:

тель вносит в нее изменения, сохраняет их и смотрит, какая сумма заказа получилась.

в Выражения применяются и для вычисления значений других логи ческих или строковых. Так, в следующем фрагменте показано добавление поля, указывающего, получает ли автор скидку.

"Discount > Это выражение можно создав составное выражение;

в нем про веряется несколько условий, к которым применяются операции AND, OR или NOT В выражениях используются и другие операции, такие как LIKE или IN.

Выражения также позволяют вычислять строковые значения, например, объединять в одну строку содержимое полей DataTable с именем и фами лией. Взгляните, как выполняется конкатенация ProductName и + '-' + Х Функции Чтобы поле содержало выражение с более сложной логикой, можно задей ствовать функции. В выражениях применяются такие функции, как Len, Convert, Trim и Substring. Каждая из них позволяет более гибко создавать выражения. Функция Len вычисляет длину строки:

Функция аналогичная в Visual Basic Ч это указываемый в выражении оператор If. Она принимает три аргумента и вычисляет пер вый из них, чтобы равен он true или false. Если при вычислении первого аргумента получилось true, функция возвращает второй аргу мент, в ином случае Ч третий. Вот как легко вставить в выражение конст рукцию упакованную в функцию < is Функция IsNull вычисляет первый аргумент и сравнивает его значение с Если результат сравнения Ч false, возвращается значение первого аргумента, в ином случае (т. е. когда первый аргумент равен Ч значение второго аргумента. Это когда требуется, чтобы вместо были пустые строки или подставляемое значение (placeholder value), как, например, здесь:

Microsoft Функция Trim удаляет из строки ведущие и концевые символы пробелов.

Функция Convert изменяет тип данных для значения в выражении, явля ющегося первым аргументом, на тип данных, передаваемый во втором ар гументе. Функция как и ее аналог в возвращает часть строки (подстроку) передаваемого в нее строкового значения. Substring позволяет урезать слишком длинные строки, чтобы ограничить показыва емое количество символов (см. фрагмент кода ниже). Кроме эта фун кция применяется для получения любых других частей строк и при необ ходимости используется в сочетании с другими функциями.

Агрегация и отношения функций в выражения позволяет создавать вычисляемые поля с достаточно сложной логикой. А как быть, если требуется создать поле, вычисляющее значения по нескольким записям? Используйте функции агрегации.

Допустим, у вас есть DataSet, в котором связываются таблицы Order и Order Details базы данных Northwind из SQL Server. Создание вычисляе мых полей с выражениями, содержащими функции агрегации, оказывает ся довольно простым делом. В коде на рис. 3 показано создание DataSet, в котором в родительский DataTable помещаются сведения о заказах, а в дочерний DataTable Ч о позициях заказов. Эти объекты DataTable связы ваются друг с другом через объект DataRelation Details). Об объектах DataRelation и о том, как с их помощью связывать иерархические и реляционные структуры данных ADO.NET, см. мою рубрику в за ноябрь 2002 г.

Рис. 3, Отношение и выражения private void С / / Создаем подключение // "Initial * см. стр.

в Рис. 3. Отношение и //- Создаем для // string sSQL = "SELECT FROM Orders + ORDER BY CustomerlD, DESC.

= new. = Связываем e и заполняем DataSet // DataSet new DataSet();

SELECT для string sSQL л "SELECT " [order od INNER JOIN p od.ProductID BY od.OrderlD, SqlCommand = new oCn);

I/ //- SqlCommand с и DataSet // = //- объекты // V Добавляем выражения для OrderDetail * Quantity) см. след. стр.

5- Microsoft Рис, З. и выражения > "ProductID '-" * < 10, a few (string), 1, //- для Order Обратите как на 3 вычисляемые емые в объект DataTable Order. В первом вычисляемом поле Ч объекте OrderTotal Ч подсчитывается итоговая сумма по всем пози циям текущего заказа. Поле содержит выражение, суммирующее значения вычисляемого поля Ч объекта Data Table что, как видите, функции агрегации позволяют ра ботать с полями таблиц, с которыми устанавливаются отношения через и даже другие вычисляемые поля.

В ADO.NET есть и другие функции агрегации, в том числе Sum, Max, Min, StDev, и Count. На рис. З демонстрируется, как применять фун кцию Avg для вычисления среднего количества по всем позициям заказа.

В таких случаях приходится использовать ключевые слова Parent и Child, чтобы обращаться к данным, доступным через DataRelation:

Функция Child принимает в качестве аргумента имя DataRelation, по ко торому определяется дочерний набор записей. Этот необязательный аргу мент необходим, только когда есть несколько DataRelation, связывающих исходный DataTable с дочерними наборами записей. Если у DataTable в только один дочерний DataTable, можно упростить синтаксис, убрав имя так как имеется только один объект DacaRelation:

Обращение к полям родительского набора записей и функция Compute Функция Parent работает так же, как Child, но, обращается к родительскому DataTable, поднимаясь по цепочке отношений. Эти две функции позволяют реализовать в функциональность, аналогичную GROUP BY в SQL.

Эти ключевые слова позволяют выполнять группирование и передавать между наборами записей значения, не изменяющиеся при переходе от ро дительского набора записей к дочернему. Меня часто спрашивают, как за ново объединить родительский и дочерний DataTable в один чтобы показать оба набора записей в одном DataGrid (без иерархии). Ис пользуя ключевое слово Parent, можно из дочернего DataTable ся к полям родительского DataTable и отображать в DataGrid только до черний DataTable. Так, если требуется показать дату заказа для каждой записи DataTable из примера на рис. 3, можно Data с ключевым словом Parent:

Это позволяет передавать значения из полей родительских наборов запи сей в поля дочерних (roll up and down) без вычислений. клю чевые слова Parent и Child, можно обращаться к полям родительской таб лицы и показывать их в DataGrid. При этом вы получаете единый двух мерный набор записей, аналогичный получаемому из SQL-оператора.

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

Еще одно средство, которое стоит рассмотреть, Ч функция Compute объек та DataTable, выполняющая вычисления с использованием функций агре гации применительно к текущему DataTable и заданного фильтра. Допус тим, вам нужно вычислить общее количество заказов с итоговой суммой не менее $1000. Ниже показан пример подходящего кода. Первый аргу мент функции Compute Ч функция агрегации, подсчитывающая число значений удовлетворяющих условию фильтра:

ADO.NET //- Показ общего числа с суммой, большей или равной $ int iCnt = >= = + " orders at least $1000";

Второй аргумент Compute Ч фильтр, ограничивающий круг записей, для которых вычисляется функция агрегации, только записями, отвечающими условию фильтра. Так что подсчитываются записи, для которых Order Total равно $1000.00 и более. Compute, благодаря поддержке фильтрации, отлично подходит для быстрых вычислений по DataTable. Например, вы могли бы легко определить число клиентов, заказавших товар х, и число клиентов, заказавших товар у, Ч не надо ни писать циклы, ни выполнять запросы к базе данных.

Важно помнить, что объекты с вычисляемыми полями нельзя изменять вручную. Эти поля связаны с выражениями, поэтому их нельзя переопределить, не удалив выражение. Кроме того, вычисляемым полям не соответствуют никакие поля в источнике данных Ч базе данных или XML-файле. Так что, если вам нужно сохранить измененную информацию в базе данных, имейте в виду, что выражения в ней не сохраняются.

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

Джонни Папа (Johnny Papa) Ч компании по разработке программного обеспечения штат Каролина), нескольких книг по ADO, XML и SQL Server. Часто выступает на конфе ренциях, в том числе на VSLive. С ним можно связаться по адресу datapoints@lancelotweb.com.

Дино Эспозито Двоичная сериализация Автор поясняет, какими способами можно сериализовать объекты ADO.NET.

Однако по-настоящему являются только DataSet и DataTable, в которых реализован интерфейс В связи с этим в статье показывается, как расширить объекты ADO.NET, чтобы они поддерживали Автор рассматривает этих объектов как особый случай.NET-сериалиэации объектов в период выполнения и демонст рирует, что за счет переопределения, а не использования суррогатных типов можно добиться значительного повышения скорости обработки ADO.NET объектов форматирующими Одно из важнейших преимуществ ADO.NET по сравнению с ADO заклю чается в уровне с XML. в ADO Ч не более чем формат ввода-вывода. Более того, формат ADO XML негибок и не поддается никакой адаптации. Поскольку в ADO.NET, к счастью, интегрирован гораздо теснее, им можно пользоваться при ADO.NET-объектов. Такой уровень интеграции делает возможным ис пользование программного интерфейса двойного назначения, который позволяет рассматривать один и тот же набор данных и как иерархичес кий, и как реляционный. В этой статье я сосредоточусь на первом аспек те, т. е. на сериализации ADO.NET-объектов. Если вас интересует второй аспект, начните с характеристик класса описанного в документации Сериализацию ADO.NET-объектов можно выполнять двумя функцио нально разными способами. через стандартный механизм * Публиковалось в Редакция. 2002. №6 (декабрь). Ч Прим. изд.

Microsoft ADO.NET Microsoft Framework, основанный на форматирующих объектах (formatters), например с применением форматирующего объекта SOAP (SOAP formatter) или двоичного (binary formatter). через встроенные методы содержимое объ в XML-документ в соответствии с заданной схемой.

Важно отметить, что по-настоящему являются только DataSet и DataTable, в которых реализован интерфейс ISerializable. Именно поэтому они доступны любым форматирующим объектам Framework. Однако лишь DataSet предоставляет дополни тельные методы (вроде явно сохранять его. со держимое в К счастью, добавить такие методы к другим наподобие DataTable, View и DataRow не слишком трудно.

Для начала я покажу, как расширить чтобы они под держивали встроенную Затем я рассмотрю этих объектов как особый случай объектов в период выполнения, Основы такой (и многое другое) жены Джеффри Рихтером (Jeffrey Richter) в серии статей Сериализация в период выполнения из рубрики Там он показал, как переоп ределить способ, которым тип может сам себя. Именно пе реопределение, а не использование суррогатных типов Ч ключ к значи тельному повышению скорости обработки ADO.NET-объектов формати рующими объектами.

Сериализация объектов DataTable Единственный в пространстве имен объект DataSet предос тавляет набор методов для своей сериализации и десериализации в XML формат, определяемый классом (class-defined XML format). Лично я назы ваю этот формат нормальной формой ADO.NET XML. Метод класса DataSet сохраняет содержимое всех дочерних и отношений (relations) в различные выходные потоки.

В классе DataTable такого метода для сохранения содержимого в XML нет.

Значит, чтобы сохранить в XML отдельный объект DataTable, не включен ный как дочерний в какой-либо родительский объект DataSet, без трюка не обойтись.

При сохранении объекта DataSet в XML все включенные в него объекты DataTable преобразуются но соответствующие методы не доступ ны извне. Чтобы обойти это препятствие, просто создайте временный пу Редакция. 2002. №2, №1(7), №3(9). - Прим. изд.

Двоичная стой объект DataSet, добавьте к нему таблицу, а затем его в XML. На рис. 1 приведен статический метод, который принимает объект DataTable и его в дисковый файл, используя режимы записи, поддерживаемые объектами DataSet. У метода Write DataTable несколько перегруженных версий, по мере возможности имитирующих метод объекта DataSet. Входной объект DataTable в коде па рис. 1 включа ется во временный объект DataSet, которому присвоено имя DataTable.

Конечно, в своей реализации вы можете изменить это имя или (что гораз до лучше) разрешить пользователю изменять его. Заметьте, что имя слег ка влияет на конечный XML-вывод. Дело в том, что имя DataSet факти чески корневой узел конечного XML-документа:

DataSet ds = new if == null) else Рис. 1. Класс public I static writer) i writer, XmlWriteMode.

public static void dt, stm) stm, public WriteDataTableCDataTable dt, stm, { DataSet public static void dt, { public static void dt, string XmlWriteMode см. след. стр.

Microsoft Рис. 1. Класс DataSet mode);

public static void dt, string DataSet mode);

private static DataSet dt) // временный DataSet DataSet ds = new // что этот DataTable // принадлежит какому-нибудь DataSet == null) else return ds:

Также заметьте, что объект DataTable нельзя связать более чем с одним объектом DataSet;

одновременно. Если DataTable принадлежит родитель скому объекту, тогда его свойство DataSet не null. В этом случае временный объект DataSet. используемый для таблицы, сле дует связать с копией таблицы в памяти. Метод Сору просто создает пол ную копию указанного объекта DataTable. При желании для сериализации этого объекта можно реализовать интерфейс ISerializable (см. врезку Ре ализация Библиотеку классов содержащую перегружен ные версии метода Write см. в исходном коде ( rosoft.com/msdnmag/code02.aspx в разделе за декабрь). В клиентском при ложении эта библиотека так:

writer = new writer);

// Показать результат сериализации = В этом фрагменте кода метод записывает свое содержимое в строку. Класс принимает данные через интерфейс записи текста (text writer), а затем возвращает их в виде строки.

Ну и хватит об объектах DataTable. Посмотрим, как содер жимое представления, которое находится в памяти (и, возможно, отфиль тровано).

Внутреннее устройство объекта DataView Класс DataView является представлением объекта Data Table. Связь между ними подчиняется правилам стандартной Ч модели (document/view model). Б соответ ствии с этой объект DataTable выступает в роли документа, а DataView Ч представления. В любой момент может существовать несколь ко представлений одних и тех же данных. Важно, что каждое ние является отдельным объектом с набором свойств, мето дов и событий. Кроме того, создание представлений не приводит к дубли рованию или репликации данных.

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

Используя View-свойства RowFilter и Row State Filter, вы можете су зить набор строк, попадающих в конкретное представление. Свойство Sort позволяет применять к строкам в представлении выражение, обеспечива ющее сортировку. Внутренняя архитектура объекта DataView представле на на рис. 2. Когда какие-либо критерии фильтрации заданы, DataView получает от нижележащего DataTable обновленный индекс строк, удовлет воряющих этим критериям. Индекс представляет собой массив абсолют ных позиций. Физические не копируются, на них даже не делаются ссылки до тех пор, пока клиент не запросит их. Связь между объектами DataTable и DataView можно установить передачей DataTable конструктору класса DataView:

public table);

Кроме того, вы могли бы создать новое представление и связать его с таб лицей позже, с помощью свойства Table объекта DataView:

DataView = new = Фильтр индексов Выборка Ссылка на Рис. 2. Архитектура Связь между этими объектами на самом деле двусторонняя: вы можете получить объект DataView из любой таблицы благодаря ству Оно неотфильтрованный объект DataView, инициализированный для работы с этой таблицей:

DataView = Получаемый таким образом объект представления содержит столько эле ментов, сколько строк в таблице. Обращаться к содержимому этого Data View можно через самые программные интерфейсы, в том числе наборы списки и перечислители (enumerators). В час тности, перечислители позволяют через метод перебирать записи в представлении, используя привычный оператор foreach. В следу фрагменте кода обращение ко всем строкам, вклю ченным в представление:

DataView myView new in { // (dereference) объект Dataflow = Когда клиентское приложение обращается к конкретной строке в пред ставлении, класс ожидает, что она находится во внутреннем кэше строк (рис. 2). Строка, уже присутствующая в кэше, упаковывается в промежу точный объект DataRowView и возвращается клиенту. DataRowView Ч это оболочка ссылки на объект DataRow, содержащий реальные данные. Data RowView служит программируемым интерфейсом, который управляет объектом строки и ссылается на него как на единое целое. При необходи мости вы можете обратиться к нижележащему объекту DataRow из экзем пляра DataRowView через свойство Row представления.

Если строки, запрошенной во внутреннем кэше нет, класс Data View загружает ее из исходной таблицы. Точнее, кэш строк DataView очи Двоичная сериализация щается и заполняется при первом запросе. Этот кэш может оказаться пус тым либо потому, что еще ни разу не использовался, потому, что вы ражение сортировки или критерии фильтрации Всякий раз, когда фильтр или выражение изменяется, кэш Если какая-либо строка запрашивается впервые и кэш пуст, View быстро заполняет его массивом объектов View, каждый из кото рых ссылается на исходный объект DataRow.

Сериализация объектов Расширим класс приведенный на рис. 1, и включим в него перегруженные методы для объекта Data View, Идея в чтобы создать копию исходного DataTable со строками, которые удовлетворяют критериям (рис. 3).

Рис. 3. Копирование public>

Сначала создается временный DataTable с той же структурой, что и у таб лицы, для которой создан сохраняемый объект DataView. Затем временная копия заполняется строками, на которые ссылается представление. Нако нец, таблица в вызовом уже определенных методов играет ключевую роль в коде на рис. 3, созда вая новый объект в контексте таблицы, для которой он вызван.

На DataRow, как и на другие не могут ссылаться сра зу два объекта-контейнера. Применение ImportRow логически эквивален тно копированию строки и ее клона к в виде Этот метод сохраняет все значения свойств Ч исходные и текущие. В от личие от метода который добавляет новые строки со значения ми по умолчанию, ImportRow сохраняет текущее состояние строки.

Сериализация объектов DataRow Два уже рассмотренных примера демонстрируют стандартный способ со хранения в Его суть в создании иерархии роди тельских объектов Ч от до DataSet, Так, чтобы вать один автономный объект DataRow, вам потребуется добавить его к временному DataTable, который в свою очередь надо добавить к рабочему DataSet.

в период выполнения Как я уже упоминал, есть два базовых способа ADO.NET объектов: через собственный XML-интерфейс объекта, если таковой име ется, или с помощью в Framework форматирующих объектов. До сих пор я рассматривал методы для сериализации ADO.NET объектов в в том числе API-расширения, необходимые для поддер жки этой функциональности объектами, отличными от DataSet. Теперь обсудим, как сериализовать ADO.NET-объекты, используя стандартный в Framework механизм сериализации объектов в период выполнения.

Главное отличие между методами вроде и форматирующими объектами Framework в том, что в первом случае объект сам управ ляет своей С другой стороны, когда в дело вступают фор матирующие объекты, у каждого сериализуемого объекта есть два пути.

Он может просто объявить себя и безучастно взирать на то, как форматирующий объект будет извлекать значимые данные, подлежащие сериализации. Такой тип сериализации поддерживается вклю чением атрибута в каждый класс индивидуально. Для пере числения всех свойств, отвечающих за состояние объекта, в процессе се риализации используется API отражения (reflection), поддерживаемый Framework.

Второй вариант требует реализации интерфейса тогда сериа объект отвечает за передачу форматирующим объектам всех данных, которые нужно сериализовать. Однако, как только объект переда ет эти данные, он теряет контроль над процессом. Класс, не помеченный Двоичная атрибутом и не реализующий интерфейс нельзя. Так вот, никакие классы объявляют себя сериализуемыми, а упомянутый интерфейс реализуют лишь DataSet и DataTable. Например, объект или сериализовать нельзя. как я уже говорил, обойти это препятствие иногда позво ляют суррогатные типы;

при этом сравнительно простые типы можно сделать сериализуемыми. Но сначала вспомним основные аспекты в период выполнения.

Двоичная в период выполнения Framework предоставляет два предопределенных форматирующих объекта в пространстве имен дво ичный и форматирующий объект SOAP. Их классы Ч соответственно BinaryFormatter и Первый генерирует более компактный код, а второй рассчитан на более взаимодействие и создает опи сание класса в формате SOAP.

Следующий код иллюстрирует, что нужно сделать для объ екта DataTable с применением двоичного форматирующего объекта:

BinaryFormatter = new dat = new Метод Serialize форматирующего объекта заставляет последний записы вать содержимое объекта в двоичный поток. Метод Dese rialize выполняет обратную операцию: считывает ранее созданный двоич ный поток, воссоздает объект и его:

DataTable table new BinaryFormatter bin = BinaryFormatterO;

reader = new table = (DataTable) Пока все замечательно. Однако при запуске этого кода произойдет кое-что неожиданное. Если вы попытаетесь сериализовать объект DataTable или DataSet с двоичного форматирующего то двоичный файл, конечно, получите Ч только он окажется весьма большим, с тонной XML-данных. К сожалению, XML-данные занимают много места в двоич ном файле. таких файлов может потребовать нескольких секунд и гораздо больше памяти, чем нужно на самом деле. В результате, выбрав двоичную в стремлении полу чить более компактный файл, вы не достигнете своей Двоичная се риализация применительно к ADO.NET-объектам оказывается не столь 142 ADO.NET эффективной, какой могла бы быть. В чем же причина такого довольно странного поведения?

с помощью объектов (только классы DataTable и DataSet), корректно реализуют интерфейс ISerializable, а значит, именно они за предоставление данных. Этот интерфейс состоит из единственного метода Get ObjectData, результат работы которого принимает форматирующий объ ект и сбрасывает в выходной поток.

Классы DataTable и DataSet спроектированы так, что сообщают о себе форматирующим объектам на основе XML-данных. В частности, они ис пользуют документ т. е. простой и многословный Дво ичный объект эту достаточно длинную стро ку и добавляет ее в поток. Таким эти объекты всегда передаются между доменами в виде это здорово. Увы, если вам нужно более компактное представление сохраненных таблиц, обычная периода выполнения, поддерживаемая для ADO.NET объектов не годится (по крайней мере, без модификации).

Следующий псевдокод иллюстрирует этих объектов форма тирующим объектом void context) { :

Класс передает свои данные форматирующему добавляя элемен ты в объект методом Например, объект DataSet себя, добавляя несколько элементов для схемы и данных.

Объект DataTable делает то же самое, но использует временный объект DataSet, чтобы получить свою и данные, выраженные в виде XML строк, Информация, записанная в затем сбрасывается в двоичный или SOAP-поток в зависимости от типа форматирующего объекта.

Нестандартная двоичная сериализация ADO.NET-объектов Есть только один способ оптимизировать двоичное представление объек та DataTable (или DataSet) Ч сопоставить класс промежуточному объек ту, сериализациеи которого вы можете управлять. Теоретически этого Двоичная сериализация можно добиться несколькими методами. Например, создать пользователь ские классы Ч оболочки функциональности классов DataTable и DataSet, Ч реализующие свой алгоритм в период выполнения. Или ис пользовать созданный для решения таких про блем, Ч суррогатные типы (surrogate types). Суррогатный тип Ч это берущий на себя работу по сериализации и десериализации данного типа независимо от того, он или нет. Суррогатный тип для конк ретного класса регистрируется выбранном форматирующем объекте, а далее обрабатывается, как Форматирующий объект распознает суррогатный тип и его вместо исходного переопределенного типа. Но по причинам, связанным с архитектурой, суррогаты не работают с объектами DataTable (я это позже).

Нестандартная двоичная сериализация ADO.NET-объектов требует от вас создать пользовательский класс, поддерживающий нужный тип сериали зации, предоставить класс с данными и, выпол нить сам процесс сериализации или десериализации.

Если вы пишете пользовательский класс, полностью управляющий сери DataTable (или DataSet), вам придется создать либо своего рода теневой класс (ghost>

Создание теневого сериализующего класса Если вам нужно сохранять только строки и столбцы объекта DataTable, создание теневого класса не займет много времени. Вот пример структу ры такого класса:

[Serlalizable] public>

Полностью исходный код этого класса приведен на рис. 4. Serializer содержит три объекта ArrayList, хранящие имена столбцов, их типы и строки данных. Заметьте, что класс ArrayList Ч В приложении объект DataTable с использованием сервисов теневого класса:

void string { bin = new dat = // Создать теневого // и заполнить его данными GhostDataTable ghost = new // объект Рис. 4. Класс using System;

using // Рабочий класс, двоичную public>

// Свойства ArrayList colTypes;

protected ArrayList dataRows;

см. след. стр.

Рис. 4. Класс // пользовательский объект таблицы для void dt) { // Вставить о столбцах и col in о строках row in // объект DataTable, заполненный данными fiatsTable dt // Добавить столбцы 1=0;

col new ));

Добавить отроки 1=0;

row = = return dt;

} 146 Microsoft ADO.NET Реализация Другой, более элегантный и эффективный способ объек та DataTable в двоичный поток в создании про изводного класса и в нем интерфейса Этот позволяет классу контролировать свою В чем его преимущества? Применяя вы обра к данным только раз. В противоположность этому теневой класс копирует данные DataTable в свойства и только оттуда они в поток, Как видите, в конечном итоге данные дважды. Кроме решение на основе интерфейса ISerializable будет большей мере совместимым со ADO.NET.

Класс (пример его структуры показан ниже) может ис пользовать те же приемы для решения своей задачи, что и теневой се риализующий класс,>

приводившейся ссылке.

Самое важное, на что здесь следует обратить внимание: как объект Data Table сопоставляется с классом GhostDataTableSerializer. Сопоставление осуществляется в методе Load.

В нем рабочие массивы (colNames и colTypes) заполняются именами и ти пами столбцов, Внутренний массив содержит массив объектов, соответствующих всем значениям в строке.

Двоичная public void dt) { col in { in Свойство ItemArray объекта DataRow Ч массив объектов. Оно особенно полезно, так как позволяет обращаться к содержимому всей строки как к единому, монолитному блоку данных. Внутренний аксессор чтения (get accessor) этого свойства реализован в виде простого цикла, и сохраняющего один столбец за другим. Аксессор записи (set accessor) еще ценнее, так как он автоматически группирует все изменения в вызо вы пары и при необходимости генерирует события, связанные с модификацией столбцов.

что вызов AcceptChanges (рис. 4) сбрасывает состояние всех строк в Unchanged, препятствуя достижению одной из основных задач се риализации Ч сохранению состояния объекта. Если вы прибегнете к ка ким-нибудь уловкам и откажетесь от вызова этой функции, все строки будут помечены как Added. Другими словами, состояние строк не сохра нится, и чтобы добиться нужной цели, теневому при дется возиться еще и с информацией о состоянии.

Рис. 5 иллюстрирует работу приложения-примера. Как видите, объект DataTable, сериализованный с помощью теневого класса, на 80% чем такой же объект, сериализованный обычным способом с использова нием того же двоичного сериализатора. Возьмем, к примеру, объект Data Table, получаемый в результате выполнения запроса:

SELECT FROM [Order Details] a Рис. 5. в действии Таблица содержит пять столбцов и более 2000 записей. Если ее двоичным форматирующим объектом как объект DataTable, она займет полмегабайта. А при использовании промежуточного теневого се конечный файл будет примерно на 83% меньше.

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

SELECT photo FROM Здесь экономия составит всего 25%, и это максимум, чего я добился в сво их тестах. думаю, очевидна. сами содержат двоичные данные, поэтому экономия невелика.

данных После того как двоичные данные десериализованы, вы должны преобразо вать экземпляр теневого класса обратно в объект DataTable. Посмотрим, как это делается в приложении-примере:

DataTable { bin = new reader = new ghost = return } Метод Save пользуется информацией, сохраненной в массивах теневого класса, для строк и столбцов к только созданному объек ту DataTable (рис. 6).

А как насчет суррогатных типов?

Сериализация объекта DataTable с помощью суррогатного класса проста, эффективна и не создает никаких проблем. Однако из-за того, что проис ходит при десериализации, суррогаты непрактичны для ADO.NET-объек тов. Ниже показан фрагмент кода, который вы, возможно, захотите напи сать для сериализации объекта DataTable через суррогатный класс Data TableSurrogate:

Двоичная 6. и столбцов public DataTable { DataTable dt // Добавить 1=0;

Count;

new // строки = = dt;

ss = dts = new new = ss;

dt);

Сначала вы создаете селектор суррогата (surrogate selector) и добавляете суррогатный тип в цепочку суррогатов. Затем связываете селектор с фор матирующим объектом. Суррогатный класс реализует интерфейс ISerializa Surrogate, состоящий из двух методов: GetObjectData и Set Object Data.

Первый из них добавляет элементы в набор как показа но на рис. 7. На рис. 8 приведена возможная реализация метода, ющего поведение теневого класса, рассмотренного ранее.

Что касается то результаты практически одинаковы.

Как я уже говорил, причины, по которым суррогаты не годятся для сериа лизации ADO.NET-объектов, связаны с архитектурой этих объектов. Они крайне сложны, и их конструкторы играют очень важную роль, Чтобы объект DataSet или DataTable работал корректно, вы должны один из его предопределенных конструкторов. А исполняющая среда при Microsoft обработке суррогатов не пользуется конструкторами для создания экзем пляров объектов. Вместо этого она обращается к статическому методу GetUninitializedObject класса и новый экземпляр объек та инициализируется нулями. Кроме того, поскольку конструкторы не за пускаются, экземпляр может оказаться в рассогласованном состоянии, и вроде бы безопасные операции могут вызывать неожиданные сбои.

Рис. 7. класс public>

{ void DataTable dt = (DataTable) obj;

info);

public object obj, selector) DataTable dt (DataTable) obj;

8. Имитация сериализатора dt. info) { ArrayList ArrayList dataflows = // Вставить информацию о (имена типы) // в рабочие массивы col in // информацию о строках рабочие массивы row in row.

см. след. стр.

Двоичная Рис. 8. Имитация теневого // в структуру с // необходимой для Например, конструктор объекта DataTable по умолчанию выполняет длин ный список операций по инициализации членов. И не все внутренние чле ны этого объекта инициализируются значениями null. На самом деле этот объект опирается на множество внутренних массивов и других вспомога тельных инициализируемых конструктором. Метод очищает блок памяти и не инициализирует ни один из таких объектов. С другой стороны, суррогатный класс вряд ли посвящен в тон кости внутренней реализации замещаемого им класса. То есть, чтобы точ но знать, что нужно для корректной инициализации объекта вы должны сначала изучить его исходный код, Теперь допустим, вы сделали это (с помощью.NET-декомпилятора). Тогда вам придется вовсю пользо ваться механизмом отражения для доступа к внутренним и закрытым чле нам объекта DataTable, Короче говоря, теневой дает практичес ки ту же экономию дискового пространства, но гораздо проще в создании.

Заключение Под занавес хочу еще раз отметить, что объективные трудности с исполь зованием суррогатных типов в процессе возника ют по двум причинам. Первая Ч упомянутые объекты крайне сложны и связаны с другими объектами. Однако, согласно Джеффри Рихтеру, есть и другая причина, которая, надеюсь, скоро будет устранена, Ч ошибка в коде форматирующего объекта. Она приводит к тому, что SetObjectData не воз вращает новые типы. После того как ошибку вы сможете легко избегать использования системой метода Get Uninitialized, вызывая конструктор объекта из SetObjectData и возвращая созданный объект форматирующему объекту. Подробнее об этой ошибке см. статью в рубрике в сентябрьском выпуске журнала за 2002 г. ( Дино (Dino Ч консультант и преподаватель из Рима.

Автор книг Web Solutions with and и Applied XML Programming for (обе выпущены издательством Microsoft Press в 2002 г.). С ним можно связаться по адресу dinoe@wintellect.com.

Дхаван Разработка распределенных приложений в В этой статье описывается, как выполнять локальные и транзакции в приложениях Microsoft Введение Транзакция Ч это операций, выполняемых как единое целое. Благодаря объединению взаимосвязанных операций в транзакцию гарантируется согласованность и целостность данных в системе, несмотря на любые ошибки, которые могли возникнуть в ходе транзакции. Для ус пешного выполнения транзакции необходимо успешное завершение всех ее операций.

У транзакции есть начало и конец, определяющие ее границы (transaction boundaries), внутри которых транзакция может охватывать различные про цессы и компьютеры. Все ресурсы, используемые в ходе данной транзак ции, считаются участвующими в этой Для поддержания цело стности используемых транзакция должна обладать свойствами ACID: Atomicity (атомарность). Consistency (целостность), Isolation (изо ляция) и Durability (отказоустойчивость). Подробнее об основах ки транзакций см. Processing Transactions ( Control. Building Applications with Library. 2001. November. Ч Прим. изд.

Разработка в Microsoft Framework SDK и в Microsoft Platform SDK.

В этой статье показывается, как выполнять локальные и распределенные транзакции в приложениях Microsoft Локальные и распределенные транзакции Локальной называется транзакция, областью действия которой является один ресурс, поддерживающий транзакции, база данных Microsoft SQL Server, очередь сообщений и др. Например, отдельно взятая СУБД может вводить в действие правила ACID, когда в ее распоряжении имеют ся все данные, участвующие в транзакции. В SQL Server предусмотрен внутренний диспетчер транзакций (transaction manager), предоставляю щий функциональность для фиксации (commit) и отката (rollback) тран закций.

Распределенные транзакции могут использовать гетерогенные ресурсы, поддерживающие транзакции, включать самые разнообразные например выборку информации из базы данных SQL Server, считывание сообщений Message Queue Server и запись в другие базы данных. Програм мирование распределенных приложений упрощается программным обес печением, способным координировать фиксацию и откат, а также восста новление данных, хранящихся в различных ресурсах. Одной из таких тех нологий является Microsoft DTC (Distributed Transaction Coordinator).

DTC реализует протокол двухфазной фиксации (two-phase commit proto col), гарантирующий непротиворечивость результатов транзакции во всех ресурсах, участвующих в этой транзакции. DTC поддерживает только при ложения, в которых реализуются совместимые с ним интерфейсы управ ления транзакциями. Эти приложения называются диспетчерами ресурсов (Resource Managers). [Дополнительную информацию по этой теме см. в Distributed в Framework Developer's В настоящее время существует довольно много таких приложений MSMQ, Microsoft SQL Server, Oracle, Sybase и др, Транзакции баз данных Вызов хранимой процедуры (stored procedure), которая заключает необхо димые операции в операторы BEGIN TRANSACTION и COMMIT/ROLL BACK TRANSACTION, дает наилучшую производительность, позволяя выполнить транзакцию с разовым обменом данными е сервером (single round-trip). Кроме того, транзакции баз данных могут быть вложенными, т. е. внутри активной транзакции можно начать выполнение новой тран закции, 154 Microsoft В следующем фрагменте кода оператор BEGIN TRANSACTION начинает новую транзакцию. Транзакцию можно завершить двумя способами: либо фиксацией изменений в базе данных оператором COMMIT TRANSAC TION, либо (при какой-либо ошибки) отменой всех изме нений оператором ROLLBACK TRANSACTION.

CREATE PROCEDURE Prod AS -- Начинаем транзакцию TRANSACTION -- Выполняем операции транзакции Ч Проверяем If О О - Откатываем транзакцию ROLLBACK - Фиксируем транзакцию COMMIT TRANSACTION Показанная ниже хранимая процедура принимает в качестве входного па раметра XML-представление информации о заказе (order). Для выполне ния соответствующих вставок в таблицы Orders и хранимая процедура загружает и анализирует XML с помощью системной хранимой процедуры Как видно из исходного кода, все опе рации хранимой процедуры включены в явно выполняемую транзакцию, поэтому при неудачном завершении любой операции все внесенные изме нения отменяются (откатываются).

Заметьте, что процедура устанавливает флаг в указы что SQL Server должен откатить транзакцию, если выполнить какой-нибудь оператор не удастся.

CREATE = NULL, int Output AS SET NOCOUNT ON DECLARE INT DECLARE Указываем, что SQL Server должен автоматически откатывать текущую -- оператор генерирует ошибку периода Ч выполнения error) SET XACT_ABORT ON - Начинаем транзакцию BEGIN TRANSACTION - Загружаем и анализируем содержимое входного приложений в -- информации о заказе, а затем помещаем его в EXEC Выбираем заголовок заказа Orders -- и его в таблицу Orders INSERT SELECT ShipToName, ShipToAddressId, OrderStatus WITH ( Customerld int nvarchar(23) nvarchar(40) ShipToAddressId int OrderStatus int -- Выбираем заказа, только что вставленного в таблицу Orders Ч для использования при вставке позиций (order details) SELECT = - Выбираем заказа из Details -- и вставляем их в таблицу INSERT Quantity) SELECT as Orderld, UnitPrice, Quantity FROM (Itemld int UnitPrice Quantity - Присваиваем значение выходному параметру Select = - транзакцию COMMIT TRANSACTION EXEC RETURN GO Хотя такой подход обеспечивает хорошую производительность, при его использовании приходится программировать на Transact SQL, а это слож нее, чем на языке, совместимом с Транзакции вручную Транзакции, выполняемые вручную (manual позволяют явно управлять границами транзакции с помощью команд начала и конца тран закции. В этой модели также поддерживаются вложенные транзакции, т. е.

вы можете начинать новую транзакцию в рамках активной транзакции.

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

Транзакции выполняемые вручную Транзакции вручную поддерживают оба провайдера данных Microsoft ADO.NET, которые предоставляют набор объектов, позволяющих вать соединение с хранилищем данных, начинать транзакцию, фиксиро вать или откатывать ее и, наконец, закрывать соединение. В примерах мы будем использовать управляемый SQL (ADO.NET SQL managed provider).

Для выполнения операций в рамках единой транзакции нужно объект начать с объекта SQLCon nection, добиться, чтобы все операции над базой данных проходили в этой транзакции, и или отменить транзакцию. Объект SQLTran saction предоставляет целый ряд свойств и методов для управления тран закцией. При успешном выполнении всех операций транзакции вы може те зафиксировать изменения в базе методом Commit. Для отката изменений применяется метод Rollback объекта SQLTransaction.

Примечание Для выполнения SQL-команды в транзакции свойство Transaction объекта Command необходимо установить на уже начатую транзакцию.

Visual Basic Dim conn as Dim as Dim txn As SQLTransaction conn = New cmd = New Открываем соединение Начинаем txn Настраиваем Transaction на транзакцию, где выполняется ' SQL-команда Txn Разработка приложений в Visual C# Conn = New = SQLCommand;

// Открываем соединение // Начинаем транзакцию Txn // Настраиваем свойство Transaction на где // SQL-команда - Txn;

В представленном ниже, мы выполняем в рамках транзакции две SQL-команды. Первая вставляет заголовок заказа (order header) в табли цу Orders и возвращает только что заказа. Этот Order Id используется во второй команде, которая вставляет позиции этого за каза в таблицу OrderDetails. Транзакция отменяется, если хотя бы одна из двух команд терпит неудачу;

при этом строки в базу данных не добавля ются.

Visual Basic Dim conn As SqlConnection Dim As Dim tran As Создаем новое соединение conn = New ' Открываем соединение ' Создаем Command cmd = New Создаем транзакцию = ' Настраиваем свойство Transaction на выполняется SQL-команда = Try ' Вставляем заголовок Настраиваем свойства Command.

With cmd = = = conn ' Добавляем входные и выходные параметры = Устанавливаем = 158 Microsoft ADO.NEt ' Выполняем команду. ExecuteNonQueryO ' Получаем добавленного заказа = ' Очищаем параметры для следующей команды End With ' Вставляем позиции заказа. Настраиваем свойства Command.

With = = = conn ' параметры. = = ' Устанавливаем значения параметров = Orderld = ' Выполняем команду, ExecuteNonQueryO ' Повторяем показанные выше строки для каждой позиции End With ' Фиксируем Catch Откатываем Finally Код очистки. Закрываем соединение.

End Как видите, две команды выполняются как часть одной транзакции. Если одна из них терпит неудачу, транзакция отменяется, и любые изменения в базе данных откатываются. Заключив код в блок /catch/finally, вы га рантируете корректное выполнение транзакции: она фиксируется в самом конце блока try после успешного выполнения обеих SQL-команд. Любое исключение перехватывается в блоке catch, где транзакция отменяется и изменения, внесенные в ходе этой транзакции, откатываются.

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

Транзакции выполняемые вручную Framework предусматривает два вида поддержки транзакций внутреннюю (для транзакций вручную) и внешнюю (для автоматических транзакций). В первом случае в рамках транзакции возможен прием или передача нескольких сообщений. Во втором Ч сообщения участвуют в транзакциях DTC (Distributed Transaction Coordinator).

Транзакции MSMQ, выполняемые вручную, поддерживаются классом и обрабатываются исключительно ядром MSMQ.

Подробности см. в статье Дункана (Duncan Mackenzie) Reli able Messaging with MSMQ and ( Автоматические транзакции Поддержка автоматических транзакций в Framework опирается на службы MTS/COM+. СОМ+ использует DTC в качестве диспетчера и координатора транзакций в распределенной среде. Это позволяет прило жениям выполнять транзакции, охватывающие разнообразные опе рации над множеством ресурсов, например вставку заказа в базу данных SQL Server, запись сообщения в очередь MSMQ (Microsoft Message Queue), отправку сообщения электронной почты и считывание информации из базы данных Oracle.

Предоставляя модель программирования на основе декларативных тран закций (declarative transactions), COM+ резко упрощает выполнение тран в которых участвуют гетерогенные ресурсы. Но учтите, что за это приходится расплачиваться снижением производительности, связанным с издержками взаимодействия DTC и СОМ;

кроме того, поддержка вложен ных транзакций отсутствует.

Страницы ASP.NET, методы Web-сервисов и можно помечать как присваивая им атрибут декларативной транзакции (declarative transaction attribute).

ASP.NET Page Microsoft ADO.NET assembly Public>

Visual Basic Public>

Х Disabled. Указывает, что объект никогда не в транзакции СОМ+. Для поддержки транзакций объект может обращаться к DTC напрямую.

Х Указывает, что объект никогда не создается в транзак ции.

Х Supported. Указывает, что объект выполняется в контексте транзакции своего создателя. Если объект сам является корневым или если его созда тель не выполняется в транзакции, объект создается вне транзакции.

Х Required. Указывает, что объект выполняется в контексте ции своего создателя. Если объект сам является корневым или если его создатель не выполняется в транзакции, при создании такого объекта создается новая транзакция.

Разработка в * Указывает, что объекту нужна транзакция и что при его создании создается новая транзакция.

В следующем коде содержится настроенный на выполнение в СОМ+. Кроме того, атрибутам сборки необходи мые для конфигурирования свойств Visual Basic System Imports Imports Imports Детали регистрации.

Имя в том виде, в каком оно присутствует ' в каталоге ' Строгое имя (strong name) для сборки (assembly) Public>

using using using // Детали регистрации, // Имя в том в каком оно присутствует // в каталоге СОМ+ [Assembly;

// Строгое имя для сборки [Assembly:

public>

Для преобразования сборки в библиотеку типов, регистрации библиотеки типов и ее установки в заданное можно использовать утилиту командной строки Кроме того, эта утилита настраи вает свойства, добавленные в сборку программным способом. Например, если в сборке указано Server), ути лита создаст серверное приложение. Если вызванная сборка еще не уста новлена в исполняющая среда создаст и зарегистрирует библиоте ку типов, а затем установит ее в СОМ+-приложение, созданное для сборки, можно просмотреть и настроить в оснастке Component Ser vices.

Процесс создания, регистрации и использования обслуживаемых компо нентов (serviced components) подробно рассматривается в разделе Wri ting Serviced Components в Framework Develo per's Guide.

В следующем коде показывается класс, сконфигуриро ванный для запуска под управлением в котором в рамках транзак ции выполняются две SQL-команды. Первая вставляет заголовок заказа в таблицу заказов и возвращает добавленного заказа. Этот используется второй командой при вставке позиций заказа в таблицу OrderDetails. Транзакция отменяется, если не удалось выполнить хотя бы одну из двух команд;

при этом записи в базу данных не добавляются.

Visual Public>

With = = = conn ' Добавляем входные и выходные параметры, параметры команды End With ' Вставляем заказа, Настраиваем свойства Command, With cmd = = = conn Добавляем параметры команду.

Повторяем строки для каждой позиции End With ' Фиксируем транзакцию Catch ' Откатываем транзакцию Finally ' Код очистки Try End Sub Используя класс Enterprise Services. можно получить информацию о контексте Этот класс предоставляет мето ды SetComplete и SetAbort, явным образом фиксировать и откатывать транзакцию. Легко догадаться, что метод Complete вызывается в самом конце блока когда все операции выпол нены успешно и нужно зафиксировать транзакцию. Все исключения пере в блоке catch, где транзакция отменяется с помощью Context Кроме того, с помощью (attribute>

можно добиться, чтобы обслуживаемый компонент автоматически определял, фиксировать или откатывать транзакцию. Ком понент за фиксацию если вызов метода завершился успешно. Если вызов метода привел к генерации исключения, транзакция автоматически отменяется;

явный вызов ContextUtil.SetAbort не нужен.

Чтобы воспользоваться этой возможностью, вставьте атрибут lete> перед методом класса:

Visual Basic Public>

В системах, где нужно выполнять транзакции, использующие и другие ресурсы, единственно возможный выбор Ч применение транзакций или СОМ+. DTC координирует все диспетчеры ресурсов, участвую щие в распределенной транзакции, а также управляет деятельностью, свя распределенных в с транзакциями. Пример распределенной транзакции MSMQ и SQL Server см. статье Дункана Reliable Messaging with MSMQ and ( Заключение При использовании каждой из технологий работы с транзакциями прихо дится идти на компромисс между производительностью приложения и удобством сопровождения кода. Запуск реализованной в хранимой проце дуре транзакции базы данных обеспечивает наилучшую производитель ность, так как требуется лишь один двусторонний обмен информацией с базой данных. Кроме того, обеспечивается гибкость управления транзак циями, поскольку явно указываются начало и завершение транзакции. Но, хотя этот способ дает хорошую производительность и гибкость, приходит ся программировать на Transact SQL. что не так легко, как па языках Транзакции, выполняемые вручную с помощью объектов ADO.NET, просты в программировании. Благодаря явному применению инструкций начала и завершения транзакции, возможно гибкое ние границами транзакции. Платой за эту легкость и гибкость является снижение производительности за счет дополнительных двусторонних об менов информацией с базой данных при выполнении транзакции.

транзакция Ч единственно возможный выбор, когда тран закция использует несколько диспетчеров ресурсов, поддерживающих транзакции, например данных SQL Server, очереди сообщений MSMQ и т. д. Они значительно упрощают разработку приложений и предъявля ют более низкие требования к квалификации программиста. из-за того, что всю работу по координации выполняет служба СОМ+, возмож ны дополнительные издержки.

программиста Джонни Папа Пять способов подстегнуть производительность SQL* Здесь показывается, как увеличить либо производительность, либо масшта бируемость приложений на основе SQL Вы узнаете о тонкостях использования SQL-операторов JOIN и CROSS JOIN. Также рассматриваются ситуации, где замена HAVING на WHERE и GROUP BY на DISTINCT дает ощутимый выигрыш в производительности.

Иногда, чтобы заставить приложение работать гораздо быстрее, достаточ но подкрутить пару Только надо знать Ч где! Рано или поздно сталкиваешься с ситуацией, когда какой-нибудь в приложе нии действует вовсе не так, как было задумано: или не возвращает нужные данные, или выполняется слишком долго. Кому понравится, если ваше корпоративное приложение будет задерживать результаты запросов? По мните, как ваши родители и слушать не хотели почему вы явились домой так поздно? Пользователей тоже не волнует, почему их запросы выполняются столько времени. (Прости, мама. В моем запросе было слишком много операторов LEFT Они хотят, чтобы прило жения отвечали а аналитические данные в отчетах появлялись моментально. Меня самого раздражает, если какая-нибудь Интернет-стра ница грузится дольше десяти секунд. (Ну хорошо, дольше пяти.) Для решения этих проблем важно разобраться в их причинах. С чего на чать? Главная причина обычно связана со структурой базы данных и зап росов. Сегодня я продемонстрирую четыре способа, позволяющих увели чить либо производительность, либо масштабируемость приложений на Публиковалось в MSDN Magazine/Русская Редакция. 2002. №1 Ч Прим. изд.

170 Microsoft SQL основе SQL Server. Я рассмотрю применение операторов LEFT JOIN и CROSS JOIN, а также получение значения IDENTITY. Но учтите, что уни версальных решений нет. Настройка базы данных и запросов требует вре мени, анализа и тестирования. Хотя все предлагаемые мной способы дока зали свою эффективность, в вашем приложении какие-то из них окажут ся лучше, чем другие.

Получение IDENTITY из запроса INSERT Начну, пожалуй, с задачи, по которой мне задают массу вопросов: как по лучить значение IDENTITY после выполнения SQL-оператора INSERT?

Зачастую проблема не в том, как написать соответствующий запрос, а в том, где и когда его выполнять. В SQL Server, чтобы получить значение IDENTITY, созданное в результате выполнения последнего SQL-операто ра применительно к активному соединению с базой данных, используйте;

SELECT Это совсем простой SQL-оператор, но важно иметь в виду вот что: если последним был не оператор INSERT или если он был выполнен на другом соединении, ожидаемого результата вы не получите. Этот код надо выпол нять сразу после INSERT и на же соединении, например так:

INSERT INTO Products VALUES SELECT Выдав такой запрос по единственному соединению с базой данных North wind, вы получите значение IDENTITY для нового товара Ч Chalk (мел).

Например, в приложении на Visual использующем ADO, вы могли бы написать:

Set = INTO Products (ProductName) VALUES = oRs(O) Этот код указывает SQL Server не возвращать число строк, выполнить оператор INSERT и вернуть значение созданное для новой строки, Оператор SET NOCOUNT ON приводит к тому, что возвращае мый объект Recordset содержит одну строку и один столбец со значением IDENTITY. Без этого вы получили бы сначала пустой объект Recordset (так как INSERT не возвращает никаких данных), а потом еще один объект Recordset, на этот раз со значением IDENTITY. Тут можно запутаться Ч особенно если не знаешь, что оператор INSERT тоже щает Recordset. Дело в том, что SQL Server следит за количеством Пять способов производительность SQL и, если оно меняется из-за вставки строки, интерпретирует новую строку как объект Recordset. А реальные данные помещаются во второй Recordset.

Хотя вы могли бы до второго объекта Recordset Next Recordset, гораздо проще (и эффективнее) делать так, чтобы возвра щался только один Recordset.

Только что показанный способ решает задачу, но требует дополнительно го кода в SQL-выражении. Другой способ добиться того же результата Ч ввести SET NOCOUNT ON перед а в триггер FOR INSERT для таблицы поместить SELECT как показано ниже. Тогда любой оператор INSERT, адресованный данной автоматически вернет значение IDENTITY.

CREATE TRIGGER ON Products FOR INSERT AS SELECT GO Триггер срабатывает, только когда INSERT выполняется применительно к таблице Products, поэтому он всегда возвращает IDENTITY при успешном завершении запроса. Этот способ позволяет унифицировать получение значений IDENTITY в рамках всего приложения.

Встраиваемые представления и временные таблицы Иногда запросы должны объединять данные с другими данными, которые можно получить только через GROUP BY с последующей выдачей стан дартного запроса. Например, если вам нужна информация о последних пяти заказах, вы сначала что они собой представляют. Для это го выдается SQL-запрос, возвращающий идентификаторы заказов. Полу ченные данные помещаются во временную таблицу, после чего она объе диняется с таблицей Products, и вы можете узнать, какие товары и в каком количестве проданы по этим заказам:

CREATE TABLE INT NOT NULL, NOT NULL) INSERT INTO (OrderlD, SELECT TOP FROM Orders о ORDER BY DESC SELECT AS FROM t INNER JOIN [Order od ON INNER JOIN Products p ON GROUP BY p.ProductName ORDER BY TABLE Microsoft SQL Server Здесь создается временная таблица, которая заполняется данными, объе диняется с другой таблицей и. наконец, удаляется. Этот запрос вызывает массу операций ввода-вывода, однако его можно переписать так, чтобы вместо временной таблицы встраиваемое представление (inline view) Ч запрос, выполняющий объединение в блоке FROM. При этом вы получаете тот же результат, но без интенсивного ввода-вывода:

SELECT AS FROM ( SELECT TOP Orders о ORDER BY o.OrderDate DESC t INNER JOIN Details] od ON = INNER JOIN Products p ON = GROUP BY p.ProductName ORDER BY Этот запрос не только но и короче. Кроме того, временные таблицы расходуют уйму Если данные нужны лишь для объединения, встраиваемые представления предпочтительнее.

Старайтесь не использовать LEFT JOIN и NULL Конечно, бывают ситуации, когда без LEFT и значений NULL не обойтись. Но их применение далеко не всегда оправданно. Изменив струк туру SQL-запросов, можно добиться того, что запрос, который выполнял ся за несколько минут, будет давать результат за несколько секунд. Иног да приходится формат данных в запросе так, как это нужно ва шему приложению. Кроме применения типа данных TABLE, снижающего потребление ресурсов, есть много других возможностей запрос. В частности, в SQL широко используется такой удобный как LEFT JOIN. Он позволяет извлекать все строки из таблицы и строки из таблицы, и не совпадающие с первой. На пример, если вам надо получить список всех и их заказов, то LEFT JOIN способен показать перечень клиентов, как заказы, так и не сделавших их.

Этим инструментом нередко злоупотребляют. LEFT JOIN приводит к се рьезным издержкам, потому что сравнивает данные с NULL ющими данными). В каких-то случаях без LEFT JOIN не обойтись, но цена его может быть очень высока. LEFT JOIN требует го раздо больше ресурсов, чем INNER JOIN, и, если вам удастся так перепи сать запрос, чтобы избавиться всех LEFT JOIN, вы получите колоссаль ный выигрыш (рис. 1).

способов SQL Возвращаются в JOIN Рис. 1. Запрос Один из способов ускорить выполнение запроса с LEFT JOIN Ч создать тип данных и вставить все строки из первой таблицы (находящей ся по левую сторону LEFT JOIN), а затем добавить в TABLE значения из второй таблицы. Хотя этот процесс проходит в два этапа, он может сэко номить массу времени по сравнению со стандартным LEFT JOIN. Но луч ше всего пробовать разные запросы и измерять время их выполнения так вы найдете, какой запрос в ваших условиях обрабатывается быстрее остальных.

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

Кроме того, параллельно с вашим запросом могут обрабатываться другие запросы, выданные к тем же таблицам. запрос может быть приоста новлен, пока другие запросы блокируют нужные таблицы.

Чтобы избавиться от задержек, вызываемых LEFT JOIN, надо соответ ствующим образом проектировать базу данных. какие-то това ры относятся к определенным какие-то Ч нет. Если в табли це товаров хранится идентификатор категории, а конкретный товар ни под какую категорию не то в соответствующее поле можно было бы записать значение NULL. Тогда, чтобы получить список всех товаров и их категорий, вам пришлось бы выполнить LEFT JOIN. Но можно создать категорию со значением No Category и тем самым указать отношение внешнего ключа (foreign key relationship), запрещающее значения NULL.

Теперь вы сможете получать те же результаты через INNER JOIN. Хотя SQL Server такой подход связан с добавлением лишних данных, он позволяет избе жать вызовов ресурсоемких LEFT JOIN. Распространив эту на всю базу данных, вы существенно ускорите обработку запросов. Помните:

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

Декартовы произведения Здесь я дам совет, в разрез с общепризнанным мнением: в опре деленных ситуациях декартово произведение (Cartesian product) очень полезно. Почему-то декартовы произведения (CROSS JOIN) пользуются дурной славой, и разработчиков зачастую призывают вообще отказаться от них. Во многих случаях CROSS JOIN действительно приводят к неэффек тивному использованию ресурсов. Но их надо применять с умом Ч как и любой другой инструмент SQL. Например, декартово произведение очень удобно в запросе, который возвращает по всем клиентам за каждый месяц, в том числе ничего не заказавшим в каком-либо месяце.

Рассмотрим SQL-код на рис. 2. Заметьте: если бы выполнялось стандарт ное внутреннее объединение (INNER JOIN) таблиц Customers и Orders с группированием по месяцам и суммированием продаж, то возвращались бы данные только по тем месяцам, в которые клиент делал заказы. То есть вы получили бы нулевое значение для тех месяцев, когда клиент ниче го не заказывал. Если вам построить график, отражающий объем продаж по каждому клиенту, то для наглядности на этом графике должны быть нулевые значения по тем месяцам, когда продаж не было. SQL-код на рис. 2 пропускает месяцы, в которые объем продаж равен нулю, так как в Orders нет соответствующих строк (предполагается, что вы не храните того, чего не было).

Код на рис. 3 длиннее, зато возвращает данные и по тем месяцам, когда никаких продаж не было. Сначала мы получаем список всех месяцев шедшего и помещаем их в первую таблицу с типом данных TABLE Затем получаем список всех компаний, что-либо закупав ших в течение этого периода, и помещаем его во вторую таблицу с тем же типом данных В этих таблицах хранятся все данные, не обходимые для создания набора результатов за исключением реальных цифр, отражающих объемы продаж.

Все месяцы перечислены в первой таблице строк), а все клиенты, что либо закупившие в этот период. Ч во второй (в моем случае Ч компа ния). Не все клиенты делали покупки в каждом из 12 месяцев, поэтому INNER JOIN или LEFT JOIN пропускал бы тех клиентов, которые в дан ном месяце ничего не покупали.

Пять способов подстегнуть SQL Рис. 2. Выборка по всем клиентам и объемам продаж on * -1. + 1) AS + + AS AS DATETIME SET -1 * 12, AS + Х CASE < THEN 000 + AS ELSE AS AS * AS e Orders о ON = JOIN ON AND BY AS * < 000 + AS AS BY Декартово произведение всех клиентов и по всем месяцам.

Фактически первая таблица умножается на вторую, а результатом являет ся набор строк, число которых равно произведению количеств строк в пер вой и второй таблицах. Таким образом, я получаю таблицу @tblFinal с строками. Далее я обновляю таблицу @tblFinal данными о ежемесячных Microsoft SQL Server объемах продаж по каждому клиенту в течение выбранного периода и про извожу выборку конечного набора строк.

3. Декартово на практике DECLARE TABLE DECLARE TABLE { VARCHARC7), CustomerlD DECLARE SET = SET + AS + + AS + ' AS = -1 * Ч Заполнить первую таблицу всех SET О WHILE № < 12) = -1 * INSERT SELECT AS + CASE WHEN < THEN + AS ELSE AS END AS SET = 91 + Ч клиентов, что-либо в этот период, таблицу INSERT INTO DISTINCT с.

Customers с INNER Orders о ON *. BETWEEN см. след. стр.

Пять способов SQL Рис, 3, Декартово на практике INTO CROSS JOIN С UPDATE f INKER JOIN AS Х CASE < + AS ELSE AS AS * AS raSales Customers с JOIN Orders о ON =.

ON = WHERE BETWEEN GROUP BY AS + '-;

WHEN < THEN + AS ELSE AS ) on * AND = SELECT f ORDER BY CROSS JOIN следует использовать с осторожностью, так как его выпол нение требует очень много ресурсов. Например, результат, достигаемый запросом CROSS JOIN, в котором применяются блоки WHERE, DIS TINCT или GROUP BY, отфильтровывающие большинство строк, можно 178 Microsoft SQL получить гораздо более эффективным внутренним объединением (INNER JOIN). Декартовы произведения очень полезны, когда вам нужны сово купные данные, например для построения графика. Но для других задач декартовы произведения лучше не использовать Ч в большинстве случа ев внутренние объединения гораздо эффективнее.

Всякая всячина Вот еще несколько приемов, производительность SQL запросов. Допустим, вы хотите сгруппировать всех продавцов по регионам и суммировать объемы их продаж, причем вам нужны только продавцы, помеченные в базе данных как активные. Вы могли бы группировать про давцов по регионам и использовать HAVING для исключения неактивных продавцов, но то же самое можно сделать с помощью Блок WHERE уменьшает число строк, подлежащих группированию, и поэтому он эффективнее, чем HAVING. Фильтрация строк по какому-либо крите рию в HAVING приводит к группированию и тех которые были бы исключены в WHERE.

Другой прием, направленный на увеличение производительности, Ч при менение ключевого слова DISTINCT вместо блока GROUP BY в тех слу чаях, когда нужно найти список различающихся строк (distinct list of data rows). Тогда запросы с ключевым словом DISTINCT будут эффективнее.

GROUP BY следует использовать при вычислении функции суммирова ния вроде SUM, COUNT, МАХ и т. д. Кроме ключевое слово DIS TINCT нежелательно, когда точно известно, что данный запрос будет все гда возвращать уникальные строки. В этой ситуации применение DISTINCT просто излишне.

Как видите, для оптимизации запросов и реализации специфических биз нес-правил можно использовать самые разные способы. Весь фокус в том, чтобы проверять их в своих условиях и отбирать подходящие. Самое глав ное: тестировать, тестировать и еще раз тестировать. В рамках этой рубри ки я намерен продолжить исследование концепций SQL Server, проекти рования баз данных, способов эффективной индексации и парадигм защи ты в SQL Server.

Джонни Папа (Johnny Papa) Ч вице-президент компании Investigations по технологиям штат Северная Каролина). Автор книги Professional ADO 2.5 RDS Programming with ASP 3.0 2000).

Часто выступает на конференциях. С ним можно связаться по адресу datapoints@lancelotweb.com.

и Дэниел Уильяме Сценарии в SQL Преобразование данных и предоставление отчетов SQL Server 2000 через Провайдеры программных сервисов (application service ASP) часто отправляют информацию клиентам автоматически, а не по запросу Ч например, какой-то компании нужны ежедневные объемах продаж ее продукции в розничной сети. SQL Server идеально подходит для ведения баз данных такого но, чтобы получать данные в формате, удобном для клиентов, вы должны писать сценарии (scripts). В этой статье вы увидите, как с помощью DTS Transformation Ч набора мощных средств SQL Server Ч автоматизировать получение и форматирование данных SQL Server 2000 и упростить их доставку вашим пользователям.

Технология доставки информации (push), также называемая Web-вещани ем (Web casting), автоматизирует поиск и получение данных. Эти опера ции инициируются не пользователями, а Web-сервером или базы данных. Исходя из заданных соответствующее приложение доставки (push application) автоматически выполняет поиск в базе данных и самостоятельно определяет время и место информации Ч за частую прямо на рабочий стол пользователя по электронной почте. Это не только удобный способ получения важной информации, которую не так то легко получить каким-то иным образом, но и средство, существенно влияющее на повседневную работу.

Публиковалось в Magazine/Русская Редакция. 2002. №2 (июль). Ч Прим.

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

Организации, которым нужна централизация и консолидация данных, могут использовать средства (Data Transformation Services), постав ляемые с SQL Server 2000. Они позволяют выбирать и преобразовывать данные из разных источников и передавать заданным адресатам. С их мощью можно не только выполнять просто разовую передачу данных, но и создавать сложные пакеты, управляемые рабочим процессом driven). Кроме того, средства предоставляют фический интерфейс и относительно простую в программировании модель.

DTS-пакет (DTS package) Ч это комбинация соединений, заданий, преоб разований и ограничений, определенных для рабочего процесса. Каждый пакет может одну или несколько операций (steps) или заданий (tasks), выполняемых последовательно или параллельно. В процессе сво его выполнения пакет подключается к источникам данных, копирует ин формацию и объекты баз данных, преобразует данные и оповещает о со бытиях других пользователей или другие процессы. Пакеты можно редак тировать, защищать паролем, выполнять по расписанию и выбирать в зависимости от версии. Пакеты легко создаются с помощью DTS Designer, о котором мы еще поговорим.

VBScript или JScript позволяет создать задание, которое выполняет фун недоступные в других Например, вы можете:

Pages:     | 1 | 2 | 3 | 4 |   ...   | 5 |    Книги, научные публикации