а л ь м а н а х программиста
Тематический сборник материалов
MSDN» Library и MSDN» Magazine
Microsoft
ADO.NET
licrosoft
SQL Server
Доступ к данны
из приложений
КРУССШ
Microsoft*
Microsoft5
TM
Составитель Ю. Е. Купцевич
Москва 2003
fii. P У С С I А 1 P E 1 i К Ц 1 1
УДК 004.45
ББК 32.973.26-018.2
А57
А57 Альманах программиста, том I: Microsoft ADO.NET, Microsoft SQL Server, доступ к
данным из пршюжений/Сост. Ю.Е.Купцевич. — М.: Издательско-торговый дом «Русская
Редакция», 2003. - 400 с.: ил.
ISBN 5-7502-0234-8
Альманах представляет собой тематический сборник статей из журнала MSDN
Magazine/Русская Редакция и Microsoft MSDN Library. Издание адресовано широкому
кругу программистов, интересующихся современными и перспективными информаци-
онными технологиями. Первый том альманаха, посвященный работе с базами данных,
состоит из трех тематических рубрик, содержащих 20 статей.
УДК 004.45
ББК 32.973.26-018.2
Microsoft ADO.NET 7
Алекс Макмен. Крис Брукс, Стив Басби, Эд Джезирски
Руководство по архитектуре доступа к данным на платформе .NET 9
Джонни Папа
Доступ к данным ADO.NET: концепции и реализация 81
Боб Бьючмин
ADO.NET Разработка собственных провайдеров данных
для .NET Data Access Framework 102
Джонни Папа
Доступ к данным Выражения в ADO.NET 122
Дино Эспозито
На переднем крае Двоичная сериализация
ADO.NET-объектов 133
Прийя Дхаван
Управление транзакциями Разработка распределенных
приложений в .NET 152
Уважаемый читатель!
Руководство по архитектуре
доступа к данным
на платформе .NET
Введение
При разработке уровня доступа к данным .NET-приложения следует ис-
пользовать модель доступа к данным Microsoft ADO.NET. ADO.NET обла-
дает богатыми возможностями и удовлетворяет требованиям доступа к
данным, предъявляемым многоуровневыми слабосвязанными Web-прило-
жениями и Web-сервисами. Как и многие другие объектные модели с под-
держкой богатой функциональности, ADO.NET позволяет решать одни и
те же задачи несколькими способами.
* Alex Mackman, Chris Brooks, Steve Busby, and Ed Jezierski .NET Data Access Architecture
Guide//MSDN Library. Microsoft Corporation. 2001. October. - Прим. изд.
Microsoft ADC.NET
Введение в ADO.NET
ADO.NET — модель доступа .NET-приложений к данным. Ее можно ис-
пользовать для доступа к реляционным СУБД, таким как SQL Server 2000,
и ко многим дополнительным источникам данных, для работы с которы-
ми предназначен провайдер OLE DB. В известной степени ADO.NET от-
ражает новейшие эволюционные достижения в развитии технологии ADO.
Однако в ADO.NET появился ряд серьезных изменений и новшеств, выз-
ванных слабосвязанной природой Web-приложений и тем фактом, что по
сути они отсоединены от баз данных. Сравнение ADO и ADO.NET см. в
статье «ADO.NET for the ADO Programmer» в MSDN.
Руководство по архитектуре доступа к данным на платформе -NET 1.1
DataSet
Table Объект
I—| Column
Constraints
*—| Constraint |
4 Row
Relation
Рис. 1. Объектная модель DataSet
Неуправляемые клиенты
Управляемые клиенты .NET
AOO.NET
DataSet
Дополнительная информация
При использовании пула соединений в SQL Server .NET Data Provider
имейте в виду следующее.
Однако, так как SQL Server .NET Data Provider работает с пулом соедине-
ний на внутреннем уровне, при использовании этого провайдера нет необ-
ходимости разрабатывать собственный объектный механизм поддержки
пула. Таким образом, вы избежите сложностей, связанных с включением
ресурсов в транзакции вручную (manual transaction enlistment).
20 Microsoft ADO.NET
Управление защитой
Хотя пул соединений повышает общую масштабируемость приложения,
он лишает вас возможности управлять защитой базы данных. Это объяс-
няется тем, что для поддержки пула нужно, чтобы все строки подключе-
ния были одинаковыми. Если вы хотите отслеживать операции над базой
данных, выполняемые каждым из пользователей, подумайте о добавлении
для каждой операции параметра, через который передается идентифика-
ция пользователя (имя и пароль); тем самым вы сможете вручную регист-
рировать действия, выполняемые пользователями в базе данных.
Дополнительная информация
При аутентификации доступа к SQL Server средствами Windows учиты-
вайте соображения, изложенные в следующих разделах.
Производительность
Тестирование производительности .NET Beta 2 показало, что при аутенти-
фикации средствами Windows на открытие соединения из пула уходит
больше времени, чем при аутентификации средствами SQL Server. Одна-
ко, хотя аутентификация Windows работает медленнее, снижение произ-
водительности незначительно по сравнению со временем, требуемым для
выполнения запроса или хранимой процедуры. В итоге преимущества
аутентификации Windows обычно перевешивают небольшое уменьшение
производительности.
Чтобы решить эту проблему, вместо обычных учетных записей, под кото-
рыми пользователи входят в Windows, используйте для подключения к
SQL Server ограниченный набор учетных записей Windows, каждая из
которых соответствует определенной роли.
Применение конфигурационных
XML-файлов приложения
Для хранения строк подключения к базе данных можно воспользоваться
элементом <appSettings> в разделе нестандартных параметров конфигура-
ционного файла приложения. В этом элементе могут присутствовать про-
извольные пары «имя-значение», как показано в приведенном ниже фраг-
менте кода.
<configuration>
<appSettings>
Odd key="DBConnStr"
value="server=(local);Integrated Security=SSPI;database=northwind"/>
</appSettings>
</configuratlon>
Преимущества
• Простота в распространении. Строка подключения передается вмес-
те с конфигурационными файлами с помощью обычного средства раз-
вертывания хсору, применяемого в .NET.
• Простота в программном доступе. Благодаря свойству AppSettings
класса ConfigurationSettings считать строку подключения к базе дан-
ных в период выполнения приложения очень легко.
• Поддержка динамического обновления (только в ASP.NET). Если
администратор обновляет строку подключения в файле Web.config,
данное изменение вступает в силу при следующем обращении к стро-
ке подключения — для компонента, не поддерживающего состояния
(stateless component), это произойдет скорее всего при следующем
обращении клиента к компоненту для выполнения запроса к базе
данных.
Недостатки
• Проблема с защитой. Хотя DLL интерфейса ISAPI (Internet Server
Application Programming Interface) ASP.NET не допускает прямого об-
ращения клиентов к файлам с расширением .config, а для еще больше-
го ограничения доступа можно использовать разрешения файловой
системы NTFS, вас все равно может не устроить хранение параметров
подключения в виде незашифрованного текста на Web-сервере, взаи-
модействующем с клиентами. Для большей безопасности храните стро-
ки подключения в конфигурационном файле в зашифрованном виде.
Руководство по архитектуре доступа к данным на платформе .NET
Дополнительная информация
• Нестандартные (custom), или пользовательские, параметры приложе-
ния можно считывать через статическое свойство АррSettings класса
System.Configuration.ConfigurationSettings. Это демонстрирует приве-
денный ниже фрагмент кода, где предполагается, что вы считываете
показанный ранее нестандартный ключ DBConnStr:
using System.Configuration;
private string GetDBaseConnectionStringO
{
return ConfigurationSettings.AppSettings["DBConnStr"];
)
Применение UDL-файлов
OLE DB .NET Data Provider позволяет указывать в строках подключений
имена UDL-файлов (Universal Data Link). Вы можете передавать строку
подключения как аргумент конструктора объекта OleDbConnection или
присваивать ее свойству ConnectionString этого объекта,
Преимущества
• Стандартный подход. Возможно, вы уже используете UDL-файлы для
управления строками подключения.
Недостатки
• Меньшая производительность. Строки подключения со ссылками на
UDL-файлы читаются и анализируются при каждом открытии соеди-
нения.
• Проблема защиты. UDL-файлы хранятся как простой текст. Вы може-
те защищать их с помощью разрешений NTFS, но тогда возникают те
же проблемы, что и с файлами .config.
• SqlClient не поддерживает UDL-файлы. Этот подход не поддержива-
ется SQL Server .NET Data Provider, применимым для доступа к SQL
Server версии 7.0 и выше.
26 Microsoft ADO.NET
Дополнительная информация
• Вы должны позаботиться, чтобы у администраторов был доступ к фай-
лу для чтения/записи, а у учетной записи, под которой запускается
приложение, — доступ только для чтения. Рабочий процесс Web-при-
ложения AS.P.NET по умолчанию выполняется под учетной записью
SYSTEM, но ее можно переопределить через элемент < process Model >
конфигурационного файла компьютера (Machine.config). Кроме того,
данную учетную запись можно подменить другой (тоже зарегистриро-
ванной в системе) через элемент <identity> файла Web.config.
• Работая с Web-приложениями, убедитесь, что DDL-файл не находит-
ся в виртуальном каталоге, — в ином случае появится потенциальная
возможность скачивания этого файла через Web.
• Дополнительную информацию об этих и других особенностях ASP.NET,
связанных с безопасностью, см. по ссылке http://msdn.rnicrosoft.com/
library /en-us/dnbda/html/authaspdotnet.asp.
Преимущества
• Безопасность. Доступ к определенным разделам реестра можно конт-
ролировать через списки управления доступом (access control lists,
ACL). Для большей безопасности используйте шифрование.
• Простота программного доступа. В .NET есть классы, позволяющие
читать строки из реестра.
Недостатки
• Проблемы с развертыванием. Соответствующие параметры реестра
придется распространять вместе с приложением, что не позволит в
полной мере использовать преимущества хсору.
Преимущества
• Нет.
Руководство по архитектуре доступа к данным на платформе .NET 27
Недостатки
• Дополнительные усилия в программировании. Такой подход требует
дополнительных усилий в программировании и создает сложности в
поддержке одновременного доступа.
• Проблема развертывания. Файл придется копировать вместе с ос-
тальными файлами приложения ASP.NET. Не помещайте файл в ката-
лог приложения ASP.NET или его подкаталог, чтобы этот файл нельзя
было скачать через Web.
Преимущества
• Администрирование. Администратор может легко настраивать строку
подключения через оснастку Component Services консоли ММС.
Недостатки
• Проблемы с безопасностью. СОМ+ Catalog считается небезопасным
хранилищем данных (хотя доступ к нему можно ограничить с помо-
щью ролей СОМ+), поэтому помещать в него строки в виде незашиф-
рованного текста нельзя.
• Проблемы с развертыванием. Элементы СОМ+ Catalog должны рас-
пространяться вместе с .NET-приложением. Если вы используете дру-
гие сервисы масштаба предприятия, например распределенные тран-
закции или поддержку объектных пулов, то хранение строки подклю-
чения к базе данных в этом каталоге не приводит к дополнительным
издержкам при развертывании, поскольку тогда СОМ+ Catalog все
равно нужно развертывать для поддержки этих сервисов.
• Компоненты должны быть обслуживаемыми. Строки инициализации
можно использовать только для обслуживаемых компонентов. Не на-
следуйте класс своего компонента от ServicedComponent (что делает
компонент обслуживаемым) лишь для того, чтобы получить возмож-
ность работать со строками инициализации.
28 Microsoft ADO. NET
Дополнительная информация
• Подробнее о том, как настраивать .NET-класс на конструирование объ-
екта (object construction), см. в приложении «Как включить поддержку
конструирования объектов .NET-класса» (http://msdn.microsoft.com/
library /en-us/dnbda/html/#daag_howtoenableobjectconstruction).
• Подробнее о разработке обслуживаемых компонентов см. по ссылке
http://msdn.microsoft.com/library/en-us/cpguidnf/html/cpconwritmg-
servicedcomponents.asp.
try
conn.0pen();
cmd . ExecuteNonQue ry( ) ;
Руководство по архитектуре доступа « данным на платформе .NET 29
catch (Exception e)
finally
conn.CloseO;
Обработка ошибок
Ошибки ADO.NET генерируются и обрабатываются через нижележащую
поддержку SEH — неотъемлемую часть .NET Framework. Благодаря это-
му ошибки при выполнениии кода доступа к данным обрабатываются точ-
но так же, как и ошибки, возникающие в любом другом месте приложения.
Исключения обнаруживаются и обрабатываются по стандартному для
.NET синтаксису и стандартными приемами.
.NET-исключения
Провайдеры данных .NET транслируют ошибки, специфичные для баз
данных, в стандартные типы.исключений, которые вы должны обрабаты-
30 Microsoft ADO.NET
Exception Exception
CT OleDbException
SQL Server .NET OLEDB.I\
Data Provider Data Prov lei
Рис. З. Иерархия исключений провайдеров данных .NET
try
{
// Код доступа к данным
\
catch (SqlException sqlex) // самое специфичное исключение
I
>
catch (Exception ex) // самое универсальное
// (наименее специфичное) исключение
'.
;
Руководство по архитектуре доступа к данным на платформе .NET 31
cmd.Parameters.Add("@ProductID", ProductID );
SqlParameter paramPN =
cmd.Parameters.Add("@ProductName", SqlDbType.VarChar, 40 );
paramPN.Direction = ParameterDirection.Output;
cmd.ExecuteNonQueryO;
// Блок finally выполняется до того, как метод возвращает управление
return paramPN.Value.ToStringO;
32 Microsoft ADO.NET
Дополнительная информация
• Полный список членов класса SqlException см. по ссылке http://
msdn.microsoft.com/library/en-us/cpref/html/frlrfSystemDataSqlClient-
Sql Except ion Members Topi c. asp.
• Дополнительные сведения о разработке собственных исключений,
протоколировании .NET-исключений и включении их в оболочки, а
также о различных подходах к передаче исключений см. по ссылке
http://msdn.microsoft.coni/library /default. asp?url=/library/en-us/dnb-
da/html/exceptdotnet.asp.
Дополнительная информация
• Чтобы избежать «зашивки» текста сообщений в код, добавьте свои со-
общения в таблицу sysinessag'es, вызвав системную хранимую процеду-
ру sp_addmessage или воспользовавшись Enterprise Manager в SQL
Server. Тогда вы сможете ссылаться на нужное сообщение, передавая
его идентификатор функции RAISERROR. Идентификаторы ваших
сообщений должны быть больше 50 000;
RAISERROFU 50001, 16, 1, §ProductID )
2-5947
Microsoft ADO.NET
;•
finally
<
conn.CloseO;
Производительность
В этом разделе рассматривается несколько типичных сценариев доступа к
данным, и для каждого из них дается подробное описание наиболее масш-
табируемого и производительного решения, реализуемого в виде кода, ко-
торый использует ADO.NET. Там, где это имеет смысл, сравниваются про-
изводительность, функциональность и трудоемкость разработки. Вот эти
сценарии.
Обработка XML
XML-сериализаций
(Web-сервисы /
гетерогенные
платформы)
Гибкое связывание
с данными
Связывание с данными
Все эти три объекта могут выступать в качестве источников данных для
элементов управления, связываемых с данными (data-bound controls), но
DataSet и DataTable способны работать с более широкой группой элемен-
тов управления, чем SqlDataReader. Это объясняется тем, что DataSet и
DataTable реализуют интерфейс IListSource (возвращающий IList), тогда
как SqlDataReader реализует интерфейс lEnumerable. Некоторые элемен-
ты управления Windows Forms поддерживают связывание с данными, если
их источники реализуют IList,
Применение DataSet
Используйте DataSet, заполняемый объектом SqlDataAdapter, если:
Дополнительная информация
Используя SqlDataAdapter для генерации DataSet или DataTable. имейте
в виду следующее.
Применение SqIDataReader
Используйте SqIDataReader, получаемый при вызове метода ExecuteRea-
der объекта SqlCommand, если:
Дополнительная информация
При использовании SqIDataReader имейте в виду следующее.
Применение XmlReader
Используйте XmlReader, получаемый при вызове метода ExecuteXml-
Reader объекта SqlCommand, в следующих случаях.
Дополнительная информация
Если вы работаете с XmlReader, учтите следующее.
Дополнительная информация
• Пример кода, где показывается, как использовать выходные парамет-
ры хранимой процедуры, см. в приложении: «Использование выход-
ных параметров хранимой процедуры для чтения одной записи».
42 Microsoft ADO.NET
Применение SqIDataReader
Используйте SqIDataReader, когда:
Дополнительная информация
• Если известно, что запрос вернет только одну запись, то при вызове
метода ExecuteReader объекта SqlCommand указывайте значение Coni-
mandBehavior.SingleRow. Некоторые провайдеры, например OLE DB
.NET Data Provider, используют это значение для оптимизации произ-
водительности. Этот провайдер при задании CommandBehavior.Single-
Row выполняет связывание с данными через интерфейс IRow (если он
доступен), а не через более ресурсоемкий IRowset. При работе с SQL
Server .NET Data Provider этот аргумент ни на что не влияет.
• Используя объект SqIDataReader. всегда считывайте выходные пара-
метры типизированными методами-аксессорами объекта SqIDataRea-
der, например GetString или GetDecimal. Это позволит избежать лиш-
них преобразований типов.
• Пример кода, в котором показывается, как с помощью объекта SqI-
DataReader считывать одну запись, см. в приложении «Использование
SqIDataReader для чтения одной записи».
Кроме того, вам может понадобиться просто проверить, есть ли в базе дан-
ных определенная: запись. Например, когда на Web-сайте регистрируется
новый пользователь, вы должны проверить, нет ли в базе указанного им
имени. Это частный случай чтения одного поля. Здесь достаточно вернуть
булево значение.
Руководство по архитектуре доступа к данным на платформе .NET 43
Дополнительная информация
• Если запрос, выполняемый через ExecuteQuery, возвращает несколько
полей и/или записей, то способы, рассмотренные в этом подразделе, —
только первое поле первой записи,
• Пример кода, в котором показывается, как работать с ExecuteScalar, см.
в приложении «Использование ExecuteScalar для чтения одного поля».
• Пример кода, иллюстрирующий, как считывать одно поле через выход-
ной параметр или возвращаемое значение хранимой процедуры, см. в
приложении «Использование выходного параметра или возвращаемо-
го значения хранимой процедуры для чтения одного поля»,
• Пример кода, демонстрирующий, как с помощью объекта SqlData-
Reader считать одно поле, см. в приложении «Использование SqlData-
Reader для чтения одного поля*-.
Конфигурирование сервера
По умолчанию экземпляры SQL Server прослушивают порт 1433. Однако
именованным экземплярам (named instances) SQL Server 2000 номер пор-
та назначается динамически при первом запуске. Администратор вашей
сети скорее всего не захочет открывать диапазон номеров портов на бран-
дмауэре, поэтому, если вы используете именованный экземпляр SQL
Server в сети с брандмауэром, настройте с помощью Server Network Utility
свой экземпляр на прослушивание определенного порта. Тогда админист-
ратор сети настроит брандмауэр так, чтобы тот пропускал трафик на за-
данный IP-адрес и порт, прослушиваемый экземпляром сервера.
Если вы изменили номер порта для экземпляра SQL Server 2000, исполь-
зуемого по умолчанию, учтите, что неудачное изменение конфигурации
клиента приведет к ошибке соединения. Если у вас несколько экземпля-
ров SQL Server, используйте последнюю версию стека доступа к данным
MDAC (версию 2.6) для динамического распознавания и согласования по
протоколу UDP (User Datagram Protocol) через UDP-порт 1434. Хотя в
среде разработки все это может функционировать нормально, вы должны
понимать, что на практике брандмауэры обычно блокируют трафик согла-
сования по протоколу UDP.
Конфигурирование клиента
Для подключения к SQL Server настройте клиент на использование сете-
вой библиотеки TCP/IP. Кроме того, убедитесь, что эта библиотека на
клиентской стороне использует правильный порт назначения.
Задание порта
Если ваш экземпляр SQL Server настроен на прослушивание порта, отлич-
ного от порта 1433 по умолчанию, вы можете указать номер порта для под-
ключения следующими способами:
Распределенные транзакции
Если вы занимаетесь разработкой обслуживаемых компонентов, использу-
ющих распределенные транзакции СОМ+ и сервисные функции Microsoft
DTC (Distributed Transaction Coordinator), вам может понадобиться на-
стройка брандмауэра на пропуск трафика DTC между разными экземпля-
рами DTC и между DTC и диспетчерами ресурсов (например, SQL Server).
conn.OpenO;
cmd.ExecuteNonQueryO;
catch
throw;
finally
conn.CloseO;
Транзакции
Практически всем коммерческим приложениям, изменяющим данные,
нужна поддержка транзакций. Транзакции гарантируют целостность со-
стояния системы в рамках одного или нескольких источников данных. Это
реализуется за счет общеизвестных свойств транзакции ACID: атомарно-
сти (atomicity), целостности (consistency), изоляции (isolation) и отказоу-
стойчивости (durability).
Дополнительная информация
• По умолчанию в ручных транзакциях ADO.NET используется уровень
изоляции Read Committed. Это означает, что в базе данных на время
чтения из нее устанавливаются разделяемые блокировки (shared locks),
но данные можно изменять до завершения транзакции. При таком
уровне изоляции возможно чтение одной и той же записи по-разному
(non-repeatable reads), или появление фантомным данных (phantom
data). Уровень изоляции транзакции можно изменить, присвоив свой-
ству IsolationLevel ее объекта одно из значений перечислимого Isola-
tionLevel.
• Тщательно выбирайте подходящий уровень изоляции транзакций,
Здесь приходится идти на компромисс между целостностью данных и
производительностью. Самый высокий уровень изоляции (Serialized)
обеспечивает абсолютную целостность данных за счет снижения об-
шей производительности системы. Более низкие уровни изоляции мо-
гут повысить масштабируемость приложения, но при этом возможны
ошибки, связанные с нарушением целостности данных. В приложени-
ях, главным образом читающих и лишь изредка записывающих дан-
ные, лучше использовать более низкие уровни изоляции.
• Ценную информацию о выборе подходящего уровня изоляции тран-
закций можно найти в книге Кэйлин Дилэйни (Kalen Delaney) «Inside
SQL Server 2000» (Microsoft Press).
Дополнительная информация
• При необходимости в хранимой процедуре можно управлять уровнем
изоляции транзакции с помощью оператора SET TRANSACTION ISO-
LATION LEVEL. По умолчанию в SQL Server используется уровень
изоляции Read Committed. Подробнее об уровнях изоляции транзак-
ций SQL Server см. SQL Server Books Online (раздел «Accessing and
Changing Relation Data», подраздел «Isolation Levels»).
• Пример кода, иллюстрирующий, как выполнять изменения в рамках
транзакции с помощью транзакционных операторов языка Transact-SQL,
см. в приложении «Выполнение транзакций с помощью Transact-SQL».
54 Microsoft AD0.NET
Дополнительная информация
• Дополнительную информацию об автоматических транзакциях СОМ+
см. в документации Platform SDK в разделе «Automatic Transactions
Through СОМ+».
• Пример транзакционного .NET-класса см. в приложении «Программи-
рование транзакционного класса .NET».
Руководство по архитектуре доступа к данным на платформе .NET 55
Дополнительная информация
• Подробнее о настраиваемых уровнях изоляции транзакций и других
усовершенствованиях СОМ+ в .NET см. статью «Windows XP: Make
Your Components More Robust with COM+ 1.5 Innovations*- в журнале
«MSDN Magazine» за август 2001 г.
Методы с [AutoComplete]
Для методов с атрибутом AutoComplete выполните одно из следующих
действий.
}
catch (SqlException sqlex )
<
LogException( sqlex ); // протоколируем параметры исключения
throw; // снова генерируем то же исключение, чтобы
// флаг целостности получил значение false
}
finally
{
// Закрываем соединение с базой данных
58 Microsoft ADO.MET
• вызывать Con text Util. Set Abort в блоке catch, чтобы при исключении
проголосовать за отмену транзакции. При этом флаг целостности по-
лучит значение false;
• вызывать Context UtiLSetComplete, если исключения не было, и прого-
лосовать таким образом за фиксацию транзакции. При этом флаг це-
лостности получит значение true, что совпадает с его значением по
умолчанию.
• требований к масштабируемости;
• требований к производительности;
• полосы пропускания сети;
• объема памяти и мощности сервера базы данных;
• объема памяти и мощности сервера промежуточного уровня;
• числа возвращаемых записей, которые вы хотите разбить на порции;
• размера нужной вам порции данных.
Применение SqIDataAdapter
Как уже говорилось, объект SqIDataAdapter используется для заполнения
DataSet информацией из базы данных. Один из его перегруженных мето-
дов Fill (показанный ниже) принимает два целочисленных индекса.
Применение ADO
Еще один вариант реализации загрузки порциями — использование ADO
средствами СОМ. Основная идея такого подхода — получить доступ к сер-
верным курсорам, предоставляемым ADO-объектом Recordset. Вы присва-
иваете свойству CursorLocation объекта Recordset значение adUseServer,
Если ваш провайдер OLE DB поддерживает этот параметр (как, например,
Руководство по архитектуре доступа к данным на платформе .NET 61
Реализация вручную
Последний вариант загрузки данных порциями, рассматриваемый в этом
разделе, — реализация этой функциональности вручную, на основе храни-
мых процедур. В случае таблиц, имеющих уникальный ключ, создать та-
кую хранимую процедуру сравнительно легко. В случае таблиц без уни-
кального ключа (вряд ли вам слишком часто попадаются такие таблицы)
этот процесс усложняется.
Size int
AS
SET HOWCOUNT @pageSize
SELECT «
FROM Products
WHERE [стандартное условна поиска]
AND ProductID > §lastProductID
ORDER BY [сортировка, при которой ProductID монотонно возрастает]
GO
11
[ConstructionEnabled(De"fault= default DSN")]
public class DataAccessComponent : ServicedComponent
[assembly : ApplicationName("DataServices")]
// Атрибут ApplicationActivation.ActivationOption определяет.
// в какой процесс компоненты загружаются при активации:
// Library - компоненты выполняются в процессе создавшего их приложения,
// Server - компоненты выполняются в системном процессе dllhost.exe
[assembly: ApplicationActivation(ActivationOption. Library)]
// Подписываем сборку. Ключевой файл с расширением snk
// создается утилитой sn.exe.
[assembly: AssemblyKeyFile("DataServices.snk")]
[Const ructionEnabled(Default="Default DSN")]
public class DataAccessComponent : ServicedComponent
i
private string connectionString;
public DataAccessComponent О
{
// Конструктор вызывается при создании экземпляра
}
public override void Construct( string constructString )
{
// Метод Construct вызывается сразу после конструктора.
// Единственный аргумент - OSN, заданное при настройке.
this. connectionString = constructString;
3 -5947
66 Microsoft ADO.NET
Использование XmlReader
для чтения нескольких записей
Объект SqlCommand можно использовать для генерации объекта XmlRea-
der, предоставляющего доступ к потоку XML-данных в направлении толь-
ко вперед. Команда (обычно хранимая процедура) должна сгенерировать
набор результатов в формате XML. Для этого в SQL Server 2000 обычно
применяется оператор SELECT с блоком FOR XML. Этот подход показан
в следующем фрагменте кода.
public void RetrieveAndDisplayRowsWithXmlReaderO
{
SqlConnection conn = new SqlConnection(connectionString);
SqlCommand cmd = new SqlCommand("DATRetrieveProductsXML", conn );
cmd.CommandType = CommandType.StoredProcedure;
try
I
conn.0pen();
XmlTextReader xreader = (XmlTextReader)cmd. ExecuteXmlReaderf);
while ( xreader. Read() )
{
if ( xreader. Name == "PRODUCTS" )
I
string strOutput = xreader. GetAttributeC'ProductlD");
68 Microsoft ADO.NET
xreader.CloseC);
}
catch
{
throw;
}
finally
{
conn.Close();
}
:
3. Откройте соединение.
4. Вызовите метод ExecuteNon Query объекта SqlCoramand. При этом за-
полняются выходные параметры (и, возможно, возвращаемое значение).
5. Считайте значения выходных параметров через свойство Value объек-
тов Sql Parameter.
6. Закройте соединение.
try
{
conn.OpenO;
SqlDataReader reader = cmd.ExecuteReaderf);
reader. ReadO; // читаем единственную запись
// Возвращаем выходные параметры из полученного потока данных
ProductName = reader. GetString(O);
UnitPrice = reader. GetDecimal(l);
reader. Closef);
}
catch
{
throw;
}
finally
{
conn.CloseO;
conn.CloseO;
}
return (int)paramRet.Value == 1;
J
reader.Read();
bool bRecordExists = reader.Getlnt32{0) > 0;
reader.Closet);
return bRecordExists;
catch
throw;
finally
conn.Close();
END
- ВЫПОЛНЯЕМ ОПЕРАЦИЮ ПО КРЕДИТУ
UPDATE Accounts
SET Balance = Balance + ©Amount
WHERE AccountNumber = @ToAccount
IF @@RowCount = 0
BEGIN
RAISERROHCInvalid To Account Number', 11, 1)
GOTO ABORT
END
COMMIT TRANSACTION
RETURN 0
ABORT:
ROLLBACK TRANSACTION
GO
[Transaction(TransactionOption.RequiresNew}]
public class Transfer : ServicedComponent
<
[AutoComplete]
public void Transfer^ string toAccount,
string fromAccount, decimal amount }
{
try
I
[Т ransaction(TransactionOption. Required)]
public class Debit : ServicedComponent
{
public void DebitAccount( string account, decimal amount )
{
SqlConnectton conn = new SqlConnection(
"Server=(local); Integrated Security=SSPI"; database="SimpleBank");
SqlCommand cmd = new SqlCommandC'Debit", conn );
cmd.CommandType = CommandType.StoredProcedure;
cmd. Parameters, Add( new SqlParameter("@AccountNo", account) );
cmd,Parameters.Add( new SqlParameter("$Amount", amount ));
try
{
conn.0pen();
80 Microsoft ADO.NET
cmd. ExecuteNonQueryO;
Благодарности
Выражаем признательность всем, кто участвовал в подготовке этой статьи:
Bill Vaughn, Mike Pizzo, Doug Rothaus, Kevin White, Blaine Dokter, David
Schleifer, Graeme Malcolm (Content Master), Bernard Chen (Sapient), Matt
Drucker (Turner Broadcasting) и Steve Kirk.
ADO.NET: концепции
и реализация
рование под .NET. Я покажу, как создать сервис для доступа к данным че-
рез ADO.NET, отделенный от бизнес-логики, реализуемой посредством
ASP.NET.
Web-форма
Начнем с простой Web-формы, представляющей пользовательский интер-
фейс (UI) приложения. Код формы WebForm.aspx содержит два серверных
элемента управления DataGrid, которые будут заполнены данными из
двух разных ADO.NET-объектов DataSet (рис. 1). DataGrid позволяет
представлять набор данных как HTML-таблицу. (О DataGrid подробно
рассказывает Дино Эспозито в рубрике «Cutting Edge» в апрельском, май-
ском и июньском номерах «MSDN Magazine? за 2001 г.) На моей Web-
форме два элемента DataGrid: grdSql (заполняется SQL-оператором с ис-
пользованием моего сервиса доступа к данным) и grdProc (заполняется
хранимой процедурой с применением того же сервиса).
Imports System
Imports System. Data
Imports System. Wet. 01
Imports System. Web. UI.WebGontrols
'ft Конструктор
' / / ____ _„_ __ __.„,
Public WebForntO
End Sub
End Class
// Пространства имен
using System;
using System. Data;
using System. Web. Ul;
using System . Web. UI . WebContt ol&;
namespace MyCSDataLayer
i /____—«„„»„„ _____ _
// имя файла; WebForm.aSpx.C3
// Автор; Lancelot Web Solutions, LLC
// Дата: 07/04/2001
/ / ______________ _ ____
// Назначение: данный класс содержит код для ASP.NET-етраницы
// WebForts.aspx. При загрузке страницы заполняются два DataGrid
// с использованием собственной сборки MyCSDataLayer. Первый
// DataGrld заполняется из набора результатов, полученного от
// SQL-оператора. Второй - из набора результатов, полученного
// от хранимой процедуры.
//_—.«.-.—-._. _„_____
public class WebForm : System. Web. UI. Page
/_„..»....____ ____ „„„_—-
//_________ _„„„
// Конструктор
//_ __„_-.„„„„.-.___
protected WebFoneO
i
Page,Init += new System.EventHandler(Paffe_lnit);
Класс SqIService
Класс SqIService является компонентом доступа к данным, определяя все
его методы, свойства и перечислимые. В начале класса я указываю все
используемые пространства имен:
Imports System
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.SqlTypes
Imports System.Collections
With oCffid
'//- Установить CommamjText, Connection и СошпапоТуре
'//- для объекта SqlCommand
.Connection - oCn
. ConiBiandText = sSql
.ComrnandType = CommandType.Text
End With
With oDa
'//- Назначить объект SqJGomruand для SqlDataAdapter
.SeleotCommand = oCmd
End Function
ADO,NET: концепций я реализация
With oGuid
'//- Выполнить SOt-one ратор
,CommandText = sSql
.Connection = oCn
.CommandType = ComrnandType.Text
. ExecuteNonQue ry()
End With
DataSet примерно соответствует объекту Recordset в ADO 2.x, так как со-
держит данные, получаемые в результате выполнения запроса. Между тем
DataSet может содержать несколько наборов данных благодаря объектам
DataTable. Например, DataSet может содержать три связанных друг с дру-
гом объекта DataTable (вроде таблиц покупателей, заказов и позиций за-
казов). Кроме того, DataSet не знает, из какого источника в него поступи-
ли данные, что совсем не похоже на объект Recordset в ADO 2.x. По сути,
различий у DataSet и Recordset больше, чем общего. К другим особеннос-
тям DataSet относятся Extended-свойства, позволяющие задавать нестан-
дартные свойства объекта; свойства DataRelation для установления связей
между разными объектами DataTable; и средства работы с XML для экс-
порта в DataSet и импорта из него данных в формате XML.
94 Microsoft ADO.NET
With oCmd
'//- Настройка CommandText, ActiveConneetlon и CommandType
'//- йля объекта SqlConmtand
.Connection = oCn
.CommandText = sProeName
.CommandType = GommandType.StoredProcedure
End With
With OOA
'//- Назначить объект SqlCoirmiand для ^IDataAdapter
.SelectCoflimand = oCmti
With
'//- Настрбйка CoaniandText, ActiveCotinection :и CommandType
End Sub
SqIService на С#
Версия SqIService на С# аналогична описанной выше — различия только
в синтаксисе. Полностью код обоих примеров (на Visual Basic и С#) мож-
но скачать с сайта MSDN Magazine (http://msdn.microsoft.com/msdnmag/
issues/01/ll/code/DataOlll.exe). Метод RunSql, реализованный на С#,
выполняет SQL-запрос и возвращает DataSet (рис. 11). Обратите внима-
ние на ключевую особенность: возвращаемое значение здесь указывается
4-5947
Microsoft ADO.NET
типом данных этого открытого метода. Кроме того, метод является пере-
гружаемым (есть две версии RunSql), но явно указывать это (как делает-
ся в Visual Basic ключевым словом Overloads) не нужно. В С# для пере-
грузки метода достаточно определить два метода с разными списками ар-
гументов.
Выполнение кода
Ознакомившись с кодом, остается загрузить страницу WebForm.aspx
(рис. 13). (Ее внешний вид одинаков для обеих версий — на Visual Basic
и С#.) Поскольку данные привязаны к элементу управления DataGrid, вам
не нужно заботиться о переборе всех записей и их отображении в HTML.
F-gyortes Vcots He p
J ]
Заключение
Компонент доступа к данным, описанный в этой статье, выносит код, от-
носящийся к ADO.NET, в отдельные пространство имен и класс. Это по-
зволяет разделить ADO.NET-код и бизнес-логику, а также унифицировать
доступ к данным через ADO.NET.
Разработка собственных
провайдеров данных
для .NET Data Access
Framework*
нативах провайдерам данных. Учтите, что код для этой статьи был напи-
сан в расчете на .NET Beta 2.
Написать провайдер данных для ADO.NET гораздо проще, чем для OLE
DB. Интерфейсы в ADO.NET четко определены; создавая класс с опреде-
ленной функциональностью, вы должны реализовать соответствующий
интерфейс. При этом нужно реализовать минимум четыре основных клас-
са: Connection, Command, DataReader и DataAdapter. Вкратце рассмотрим
эти классы.
ctor(Command) ExecuteReader
тжввввтвтгявчячч
IDbDataRecord
Класс Connection
До реализации класса Connection надо реализовать интерфейс IDbConnec-
tion. В IDbConnection (рис. 2) шесть открытых методов, среди которых
наиболее очевидны Open и Close. Так как в исполняющей среде .NET не
поддерживается концепция детерминированной деструкции (deterministic
destruction), вы должны явно вызывать метод Close, а не просто освобож-
дать все указатели на интерфейс, как это можно было бы сделать в OLE
DB. Метод ChangeDatabase используется, если ваш источник данных дол-
жен поддерживать подключение к другой базе данных.
// Свойства
string ConnectionString // get, set
int ConnectlonTimeout // get, set
string Database // get, set
Connect!onState State // set
Член Описание
Класс Command
Класс Command должен реализовать интерфейс IDbCommand (рис. 3),
Основное назначение IDbCommand — отправлять команды или запросы
хранилищу данных. Команды, изменяющие данные, но не генерирующие
набор результатов, передаются методом IDbCommand.ExecuteNonQuery,
возвращающим общее число записей, на которые воздействовала команда.
Команды-запросы возвращают наборы записей через класс DataReader.
Метод IDbCommand.ExecuteReader вовзращает интерфейс IDataReader
класса DataReader. Метод ExecuteScalar используется, чтобы получить
первое поле первой записи, но применим и для получения скалярного ре-
зультата. Возможное применение этого метода — чтение набора записей,
который на самом деле является целым экземпляром объекта или доку-
ментом.
Член Описание
Рис. 4. ExecuteReader
public IDataReader ExecuteReader(CominandBehaviQr t>)
{
Debug.WriteLine("MBirCoimancl.ExecuteReader(b)", "MdirCommand"};
Класс DataReader
У класса DataReader нет открытого конструктора. Этот класс должен реа-
лизовать интерфейсы IDataReader и IDataRecord. Класс DataReader по-
зволяет читать данные по одной записи единовременно в направлении
только вперед и получать значения полей извлеченных записей как типи-
зированные или обобщенные типы данных. Определения интерфейсов
IDataReader и IDataRecord показаны на рис. 5.
// Свойства IDatafieader
public int Depth
public bool IsGlosed
public int Records Affected
Реализация DataReader
Класс DataReader содержит специфичный для провайдера метод Get-
Directory, который вызывает управляемый метод System.IO.Directory-
Info. GetFileSysternlnfos. Провайдер данных предоставляет доступ к под-
множеству возвращаемой этим методом информации, позволяя получить
поля Name, Size, Type (файл или подкаталог) и CreationDate. Name и Туре
имеют тип String, Size — Int64, a CreateDate — DateTime. Поскольку в каж-
дом наборе результатов возвращаются одни и те же метаданные, результа-
ты помещаются в четыре массива: типов (управляемый тип каждого поля),
размеров (размеры этих типов), имен (имена полей) и полей (значения
типа object). Хотя элементами массива полей являются объекты, каждое
поле — это экземпляр соответствующего типа, т. е. в отличие от ADO поля
не представляются типом Variant. Поскольку метаданные всегда одни и те
же, получение информации о схеме «зашито» в методе GetSchemaTable. За-
щищенная реализация класса DataReader и метода Read показана на рис. 6.
else
Oireetoryinfo d * (DirectoryInfo)_fsit_GurrentBow];
_cols[0] * d.Hame;
_cols{1] = "0";
_cols[23 * "Directory";
_eols[3] * d. Great ionTiae.ToStringO;
return notEOF;
}
, return false;
}
// Реализация MOir DataReader используется методами
// HDirCoronand, Execute и HOirCoiffiand.ExecuteReader
/*
« Поддерживаем автоматическое закрытие соединения, обрабатывая
* флаг CoBinandfleliavior.GloseConnection. Null задает
* обычное поведение (без автоматического закрытия),
*/
Класс DataAdapter
DataAdapter — один из немногих классов провайдеров данных, который
реализуется на общем базисе. Ваши классы DataAdapter, специфичные для
конкретных провайдеров, наследуются от класса DbDataAdapter, который
в свою очередь является производным от DataAdapter. Эти классы реали-
зуют интерфейсы IDataAdapter (в котором определяются методы Fill и
Update, используемые при взаимодействии с DataSet) и IDbDataAdapter.
Последний предоставляет доступ к четырем объектам Command (Select-
Command, UpdateCommand, InsertCommand и DeleteCommand), определя-
ющим взаимодействие между провайдером и DataSet. Кроме того, класс
DataAdapter содержит стандартный набор событий и делегатов, позволя-
ющих принимать уведомления и влиять на поведение класса до и после
обновлений (под обновлениями подразумеваются операции Insert, Delete
или Update, выполняемые над хранилищем данных). Иерархия классов и
интерфейсов показана на рис. 7.
IDbDataAdapter
t
IDbDataAdapter
Наследование интерфейса t
Наследование класса
I
IDataAdapter
DataAdapter
Реализация DataAdapter
Реализация DataAdapter провайдера данных DirProv будет «облегчен-
ной», поскольку этот провайдер предоставляет данные только для чтения.
Поддерживается лишь метод SelectCommand, поскольку никакой поддер-
жки обновлений или передачи команд не требуется — как и событий и
делегатов, относящихся к обслуживанию обновлений (они все равно не
будут использоваться).
Заключение
Хотя мой провайдер данных не имеет прямого отношения к объектам
Connection, Transaction и Parameters или к преобразованию типов источ-
ника данных в управляемые типы, он позволяет заполнять DataSet. Этого
можно было бы добиться и по-другому, но в любом случае этот пример
весьма полезен, так как благодаря ему вы изучили архитектуру провайде-
ров данных.
Доступ к данным
Выражения в ADO.NET
SQL-выражен ия
SQL-выражения бывают разных видов и служат разным целям, в частно-
сти для форматирования строк, вычисления пользовательских функций и
математических расчетов. Если в SQL-операторе объединяются имя и фа-
милия, вычисляется сумма позиции заказа или содержится пользователь-
ская функция SQL Server 2000, извлекающая дату заказа, значит, в этом
операторе есть выражение.
SEUCI QrderlO, :
ProductID,
UnitPrice,
Quantity,
(UnitPrice * Quantity) AS ExtendedPrice,
Discount,
((UnitPrioe * Quantity) * (1 - Discount)) AS
Extended?riceWithDiscauflt
РЙ0Н [Order Details]
QROER BY
OrderlD,
Product!»
USE pubs
GO
oDs.Tables["ОrderDetail"].Columns.Add("GetsDiscount",
typeof(bool), "Discount > 0");
oDs.Tablest"ОrderDetail"].Columns.AddC'stringfield",
typeof(string), "ProductID + '-' + ProductName");
.r •
Функции
Чтобы поле содержало выражение с более сложной логикой, можно задей-
ствовать функции. В выражениях применяются такие функции, как Len,
lif, IsNull, Convert, Trim и Substring. Каждая из них позволяет более гибко
создавать выражения. Функция Len вычисляет длину строки:
oDs.Tables["OrderDetail"].Columns.AddC'LengthOfProductName",
typeof(int), "Len(ProductName)");
oDs.Tables["OrderDetail"].Columns.Add{"Inventory",
typeof(string),
"Iif(Quantity < 10,'A few left', 'Plenty is stock')");
oDs.Tables["OrderDetail"].Columns,Add("DiscountString",
typeof(string), "IsNull(Discount, '[null value]')");
oDs.Tables["OrderDetail"].Columns.Add("ShortProduct",
typeof(string), "Substring(ProductName, 1, 10)");
Агрегация и отношения
Внедрение функций в выражения позволяет создавать вычисляемые поля
с достаточно сложной логикой. А как быть, если требуется создать поле,
вычисляющее значения по нескольким записям? Используйте функции
агрегации.
I/
//- Связываем SqlCommand с OataAdapter и заполняем DataSet
//
SqlOataAdapter oDA = new SqlDataAdapter(oSelCmd);
oDA.FillCoDs, "OrderDetail");
oDs.Tables["OrderDetail"].Columns.Add("OrderDate",
typeof(string), "Parent.OrderDate");
oDs.Tables["OrderDetail"].Columns,AddC'ExtendedPrlce",
typeof(decifflal), "(UnltPrice * Quantity) * (1 - Discount)");
oDs.Tablesf'OrderDetail"]. Columns. AddC'GetsDiscount",
oDs.Tables["Order"].Columns.AddC'AvgQuantity", typeof(decimal),
Avg(Child(Order20rderDetail). Quantity)");
//- Показ общего числа заказов с суммой, большей или равной $1000
Двоичная сериализация
ADO.NET-объектов
См. MSDN Magazine/Русская Редакция. 2002. Спецвыпуск №2, №1(7), №3(9). - Прим. изд.
Двоичная сериализация ADO.NET-объектов 135
Также заметьте, что объект DataTable нельзя связать более чем с одним
объектом DataSet; одновременно. Если DataTable принадлежит родитель-
скому объекту, тогда его свойство DataSet не равно null. В этом случае
временный объект DataSet. используемый для сериализации таблицы, сле-
дует связать с копией таблицы в памяти. Метод Сору просто создает пол-
ную копию указанного объекта DataTable. При желании для сериализации
этого объекта можно реализовать интерфейс ISerializable (см. врезку «Ре-
ализация ISerializable»).
Фильтр
Массив индексов
Выборка
Dataflow Dataflow
DataView dv = dt.DefaultView;
щается и заполняется при первом запросе. Этот кэш может оказаться пус-
тым либо потому, что еще ни разу не использовался, либо потому, что вы-
ражение сортировки или критерии фильтрации изменились. Всякий
раз, когда фильтр или выражение сортировки изменяется, кэш очищается,
Если какая-либо строка запрашивается впервые и кэш пуст, Data View
быстро заполняет его массивом объектов Data Row View, каждый из кото-
рых ссылается на исходный объект DataRow.
return dt;
Пока все замечательно. Однако при запуске этого кода произойдет кое-что
неожиданное. Если вы попытаетесь сериализовать объект DataTable или
DataSet с помощью двоичного форматирующего объекта, то двоичный
файл, конечно, получите — только он окажется весьма большим, с тонной
XML-данных. К сожалению, XML-данные занимают много места в двоич-
ном файле. Десериализация таких файлов может потребовать нескольких
секунд и гораздо больше памяти, чем нужно на самом деле. В результате,
выбрав двоичную сериализацию ADO.NET-объектов в стремлении полу-
чить более компактный файл, вы не достигнете своей цели. Двоичная се-
риализация применительно к ADO.NET-объектам оказывается не столь
142 Microsoft ADO.NET
public GhostDataTableSerializerO { . . . }
public void Load(DataTable dt) {...}
144 Microsoft ADO.NET
// Сериализовать объект
bin.Serialize(dat.BaseStream, ghost);
dat.CloseC);
// Свойства
protected ArrayList eolNantes;
protected ArrayList colTypes;
protected ArrayList dataRows;
// Добавить столбцы
foKirvt 1=0; i<colNames..Count; 1-й-)
{
OataColumn col = new DataColufflrr(colHaiBesti3.ToString(),
Tyoe.6etType(colTypes[l].ToString() ));
dt,Columns.,Add<col);
// Добавить отроки
for(int 1=0; KdataRows, Count;
dt , AcceptChangesC } ;
return dt;
}
146 Microsoft ADO.NET
Реализация JSerializable
Самое важное, на что здесь следует обратить внимание: как объект Data-
Table сопоставляется с классом GhostDataTableSerializer. Сопоставление
осуществляется в методе Load.
Здесь экономия составит всего 25%, и это максимум, чего я добился в сво-
их тестах. Причина, думаю, очевидна. BLOB-поля сами содержат двоичные
данные, поэтому экономия невелика.
Десериализация данных
После того как двоичные данные десериализованы, вы должны преобразо-
вать экземпляр теневого класса обратно в объект DataTable. Посмотрим,
как это делается в приложении-примере:
DataTable BinaryDeserialize(string sourceFile)
{
BinaryFormatter bin = new BinaryFormatterO;
StreamReader reader = new StreamReader(sourceFile);
GhostDataTableSerializer ghost;
ghost = (GhostDataTableSerializer) of.Deserialize(reader.BaseStream);
sr.Closef);
return ghost.Savef);
}
// Добавить столбцы
for(int 1=0; KcolNames. Count;
// Добавить строки
forfint i=0; Kdataflows.Count; i-н-)
{
OataRow row = dt.NewRowO;
row.IteieArray = (objectU) dataRows[i];
dt.Rows.Add(row);
dt.AceeptChanges();
return dt;
*
colNames.Add<col.ColumnName);
colTypes.Add(col.DataType.FullName);
Заключение
Под занавес хочу еще раз отметить, что объективные трудности с исполь-
зованием суррогатных типов в процессе ADO.NET-сериализации возника-
ют по двум причинам. Первая — упомянутые объекты крайне сложны и
связаны с другими объектами. Однако, согласно Джеффри Рихтеру, есть и
другая причина, которая, надеюсь, скоро будет устранена, — ошибка в коде
форматирующего объекта. Она приводит к тому, что SetObjectData не воз-
вращает совершенно новые типы. После того как ошибку исправят, вы
сможете легко избегать использования системой метода Get Uninitialized,
вызывая конструктор объекта из SetObjectData и возвращая созданный
объект форматирующему объекту. Подробнее об этой ошибке см. статью в
рубрике «.NET» в сентябрьском выпуске журнала за 2002 г. (http://
msdn.microsoft.com/msdnmag/issues/02/09/net/default.aspx).
Разработка
распределенных
приложений в .NET
Введение
Транзакция — это последовательность операций, выполняемых как единое
целое. Благодаря объединению взаимосвязанных операций в транзакцию
гарантируется согласованность и целостность данных в системе, несмотря
на любые ошибки, которые могли возникнуть в ходе транзакции. Для ус-
пешного выполнения транзакции необходимо успешное завершение всех
ее операций.
library/en-us/cpguide/html/cpconprocessingtransactions.asp) в Microsoft
.NET Framework SDK и в Microsoft Platform SDK.
AS
-- Начинаем транзакцию
BEGIN TRANSACTION
-- Выполняем операции транзакции
- Фиксируем транзакцию
COMMIT TRANSACTION
Транзакции вручную
Транзакции, выполняемые вручную (manual transactions), позволяют явно
управлять границами транзакции с помощью команд начала и конца тран-
закции. В этой модели также поддерживаются вложенные транзакции, т. е.
вы можете начинать новую транзакцию в рамках активной транзакции.
156 Microsoft ADO.NET
Visual C# .NET
SQLConnection Conn = New SQLConnectlon(TTConnString");
SQLCommand Cmd = Kew SQLCommand;
// Открываем соединение
Conn.OpenO;
// Начинаем транзакцию
3QLTransaction Txn ~ Conn.Begin!ransaction();
// Настраиваем свойство Transaction на транзакцию, где выполняется
// SQL-команда
Crnd.Transaction - Txn;
Как видите, две команды выполняются как часть одной транзакции. Если
одна из них терпит неудачу, транзакция отменяется, и любые изменения в
базе данных откатываются. Заключив код в блок try /catch/finally, вы га-
рантируете корректное выполнение транзакции: она фиксируется в самом
конце блока try после успешного выполнения обеих SQL-команд. Любое
исключение перехватывается в блоке catch, где транзакция отменяется и
изменения, внесенные в ходе этой транзакции, откатываются.
Автоматические транзакции
Поддержка автоматических транзакций в .NET Framework опирается на
службы MTS/COM+. СОМ+ использует DTC в качестве диспетчера и
координатора транзакций в распределенной среде. Это позволяет прило-
жениям .NET выполнять транзакции, охватывающие разнообразные опе-
рации над множеством ресурсов, например вставку заказа в базу данных
SQL Server, запись сообщения в очередь MSMQ (Microsoft Message Queue),
отправку сообщения электронной почты и считывание информации из
базы данных Oracle.
ASP.NET
Web-сервис ASP.NET
Visual C# .NET
[Transactlon(TransactionOption. Required)]
public class Classl : ServicedComponent {
Imports System
Imports System.Runtime.CompilerServices
Imports System.EnterpriseServices
Imports System.Reflection
1
Детали регистрации.
1
Имя COM-»--приложения в том виде, в каком оно присутствует
' в каталоге СОМ+
<Assembly: ApplicationName("Classl"}>
' Строгое имя (strong name) для сборки (assembly)
<Assembly: AssemblyKeyFileAttribute("class1,snk")>
<Assembly: ApplicationActivation(ActivationOption.Server)>
End Sub
End Class
Visual C# .NET
using System;
using System,Runtime.CompilerServices;
using System.EnterpriseServices;
using System.Reflection;
// Детали регистрации,
// Имя СОН+-приложения в том виде, в каком оно присутствует
// в каталоге СОМ+
[Assembly; ApplicationName("Classl")]
// Строгое имя для сборки
[Assembly: AssemblyKeyFileAttribute("class!.snk")]
[Assembly: ApplicationActivation(ActivationOption.Server)]
[Transaction(TransactionOption,Required)]
public class Classl : ServicedComponent {
[AutoComplete]
public void Example1()
i
162 Microsoft ADO.NET
Try
' Создаем новое соединение
Разработка распределенных приложений е .NET 163
, ExecuteNonQuervO
Очмцаем параметры для следующей команды
.Parameters.clear()
End With
Выполняем команду
. ExecuteNonQueryO
Повторяем эти строки для каждой позиции заказа
End With
End Sub
End Class
Visual C# .NET
[Transact ion(TransactionOpt ion. Required)]
public class Class"! : ServicedComponent {
[AutoComplete]
public void Example1()
'<
Заключение
При использовании каждой из технологий работы с транзакциями прихо-
дится идти на компромисс между производительностью приложения и
удобством сопровождения кода. Запуск реализованной в хранимой проце-
дуре транзакции базы данных обеспечивает наилучшую производитель-
ность, так как требуется лишь один двусторонний обмен информацией с
базой данных. Кроме того, обеспечивается гибкость управления транзак-
циями, поскольку явно указываются начало и завершение транзакции. Но,
хотя этот способ дает хорошую производительность и гибкость, приходит-
ся программировать на Transact SQL. что не так легко, как па языках .NET.
SELECT @@IDENTITY
Это совсем простой SQL-оператор, но важно иметь в виду вот что: если
последним был не оператор INSERT или если он был выполнен на другом
соединении, ожидаемого результата вы не получите. Этот код надо выпол-
нять сразу после INSERT и на том же соединении, например так:
SELECT ^IDENTITY
IProductID = oRs(O)
Возвращаются в JOIN
Рис. 1. Запрос
Декартовы произведения
Здесь я дам совет, идущий в разрез с общепризнанным мнением: в опре-
деленных ситуациях декартово произведение (Cartesian product) очень
полезно. Почему-то декартовы произведения (CROSS JOIN) пользуются
дурной славой, и разработчиков зачастую призывают вообще отказаться от
них. Во многих случаях CROSS JOIN действительно приводят к неэффек-
тивному использованию ресурсов. Но их надо применять с умом — как и
любой другой инструмент SQL. Например, декартово произведение очень
удобно в запросе, который возвращает сводку по всем клиентам за каждый
месяц, в том числе ничего не заказавшим в каком-либо месяце.
Все месяцы перечислены в первой таблице (12 строк), а все клиенты, что-
либо закупившие в этот период. — во второй (в моем случае — 81 компа-
ния). Не все клиенты делали покупки в каждом из 12 месяцев, поэтому
INNER JOIN или LEFT JOIN пропускал бы тех клиентов, которые в дан-
ном месяце ничего не покупали.
Пять способов подстегнуть производительность SQL 175
SELECT c.CustoraerlD,
CAST(YEAR(o.QrderDate> AS VAfiCHAR(4)) •
CASE WKEN MQNTH(o.QrderDate) < 10
THEN '0' + CAST(MONTH£o.QrderDate) AS VARCHARC2))
ELSE CASTCHONTHCo.QrtlerDate) AS VARCHAR(2)}
END AS SHonth,
SUH( od. Quantity * od.UnitPrice) AS raSales
FROM Customers с
ШЕЙ JOIN Orders о ON c.CustoroerlD = o.CustomerlO .
INKER JOIN tQrder Details} od ON o.OrderlD = od.OrderlD
WHERE o.OrderDate BETWEEN 9dtStartDate AND ©dtEndSata
GROUP BY
c.CustojaerlD,
CAST(Y£AR<O.OrderDate) AS VAfiCHAR(4)) + '-; +
CASE WHEN HOHTHCo.OrderDate) < 10
THEN '0' + CAST{MQNTH<O.GrtferOate) AS VARCHAR(2))
ELSE CAST(HONTH(o.OrderOate) AS VARCHAR<2»
END
) mydata on f.CustomerlD * mydata.CustomerlD AND f.sMonth =
mydata.sltonth
SELECT f.sMonth,
f.CustomerlD,
f.Com&anyName,
f.ContactName,
f .mSales
.FROM etblFinal f
ORDER BY
f.sHonth
Всякая всячина
Вот еще несколько приемов, увеличивающих производительность SQL-
запросов. Допустим, вы хотите сгруппировать всех продавцов по регионам
и суммировать объемы их продаж, причем вам нужны только продавцы,
помеченные в базе данных как активные. Вы могли бы группировать про-
давцов по регионам и использовать HAVING для исключения неактивных
продавцов, но то же самое можно сделать с помощью WHERE. Блок
WHERE уменьшает число строк, подлежащих группированию, и поэтому
он эффективнее, чем HAVING. Фильтрация строк по какому-либо крите-
рию в HAVING приводит к группированию и тех строк, которые были бы
исключены в WHERE.
Сценарии в SQL
Преобразование данных
и предоставление отчетов
SQL Server 2000 через
VBScript-интерфейсы*
Приложение-пример
Наш пример показывает, как использовать VBScript-задания для выпол-
нения функций, недоступных в других заданиях DTS, и каким образом
сделать так, чтобы VBScript-сценарий работал в качестве задания, выпол-
няемого по расписанию (scheduled task). Мы не будем рассматривать гло-
бальные переменные DTS, рабочие процессы или интеграцию с другими
заданиями, так как наша цель — продемонстрировать мощь VBScript в
SQL Server 2000.
Данные
{в базе данных
SQL Server)
Т
Создание
локального
DTS-пакега
s SQLServer 2000
Создание
VBScript-задания
в локальном
DTS-пакете
Т
Написание на VBScript
функций выборки.
преобразования
и рассылки данных
(по электронной почте)
i
Конфигурирование
OTS-пакета как задания,
выполняемого
по расписанию
*'.$»». Э£
aujd id (varchar) 11
aujname varchar 40
aujnams yardw 20
phone char "12
address varchar 40 V
city varchar 3J ! V
-_ Фstate chat
char "" 5
2 V
V
contract bit !l
г aujsmaill that ' 75 V
на иллюстрации хорошо видно, что это поле имеет тип char и длину 75 символов. —
Прим. сост.
Преобразование данных и предоставление отчетов SQL Server 2000 ... 183
^CwscJaPcw
1| nscwsefr S'5L 3«v*n
В l|J SQL Server бгоир
. |jk DWUIAMS (Windows NT)
Loiil Packs
Meta D-ifs
Mil* Data
:
li--Й M«l* Did S»rviMi
pp lh DWIlL!AM£'DWMJdM5_SER'/«R Windows HT-
• IbSQlDEVDEFERRALlWifKjowsNT)
Package Objec
'B Tasks Cons Visual Eass veX bcri
Author AiokMehtaandDi
ale: 1Л/2002
aiy Sales Repent using CDOI
'ou should have 5QL Seivw i
MallTransfeiPitiocol |
Соги(5МТР_ЗЕПУЕЯ-"м!с
••-,/. й-.d
Код на VBScript
Исходный код нашего примера находится в файле SourceCode.txt. После
того как мы поясним константы, которые нужно задать, и кратко рассмот-
рим функцию main, мы расскажем, как этот код обрабатывает данные по
продажам, обращается к базе данных, форматирует набор записей и от-
правляет отчеты.
Initial Catalog содержит имя базы данных; в нашем примере это Pubs, ко-
торая поставляется с SQL Server. Наконец, user id определяет имя пользо-
вателя, a password — его пароль.
4.1ирвмиш
Й ]S т.*rs Con^anl V^.iB^.Adiv^-.rn
Da^a Punp Corijlani
AulHiot: Atok Meht3 and Dad
S В TSTiansformStaLOK Oale 1 Л /2002
£ DTSTiansrojmSialJnlo IJaijji Sales Report using CDG.J|
You should have SQL Serves |is
£В DTSriwisloimSfaUJpditeQuery
-jSl DTSTransrermStaLDfcleteQuefy
:
Name: Main
- JSJ DTSTFertsforrffiaUJserQuwji Aur.hoi: Atak Mehta and Oal1
]
(-..-Щ Active* Script Cnrtstant 'Dale' 1 Л /2002
£51 DTSTa;kFse-?Result_SuccsES Purpose. Calbihe PioceM_Di-
!
-[Я DTSTaskEKseRefull Failure
\3 У Steps Coreienl
--X DTSSiepEKecS(at_Compteted :
MictbnK)ain{l
jf DTSSteptHecSsatjnashve Call P[ocess_Daily_Sa|
! У OTSSiepExecSiatJnProgrew
End Function
JT •. . *e,ik.^..c5rai_ /a. ng
'
jf OTSStepSctiplResuH^ExeculeTask ^
'Name: Fundicin Serid^iii.J"
A l~
V
:
.-ih !VУанаом*
LitoM 'яН 'Authoi: A!ck Mehta and С з,-
'Daie 1/1.^2002
'Puipoje IJsssCDD to spni
1 5глч1Св! J Return Noihing
Dim rstAuthors
Dim rstSales
Dim Todays_Date
Так как не все базы данных Pubs содержат данные по продажам за сегод-
няшний день, следующие две строки кода генерируют отчет за 14.09.1994.
Отчет за эту дату можно получить почти в любой базе данных Pubs. Мы
просто убрали признак комментария из строки кода для даты 14.09.1994 и
закомментировали строку с сегодняшней датой:
"Todays_Date = & DateQ &
Todays_Date = "'9/14/1994'"
Преобразование данных и предоставление отчетов SQL Server 2000 ... 189
Set rstSales=ExecuteSQL(strAu_Sales)
Если у вас есть опыт работы с ADO, все эти действия будут для вас очень
просты.
Отправка отчетов
Для отправки отчетов авторам служит функция Send_Email. Заметьте, в
ней используются константы SMTP_SERVER и SENDER_EMAIL, а так-
же Microsoft Collaboration Data Objects (CDO 2.0) из CDOSYS.DLL. CDO
предоставляет объектную модель для разработки коммуникационных при-
ложений в Windows 2000, базируется на стандартах SMTP и NNTP и дос-
тупен как системный компонент при установке Windows 2000 Server. Это
стандартный API для создания приложений массовой рассылки и переда-
чи почтовых сообщений через Web, Такие приложения работают под уп-
равлением Windows 2000 Server.
cdoSendUsingPickup
. Item( "http://schenias.microsoft.coin/cdo/configuration/snitpserve г") =
SHTP.SERVER
,Item("http://schemas.microsoft.com/cdo/configuration/ " & _
"smtpconnectiontimeout") = 10
.Update
End With
With iMsg
Set .Configuration = iConf
.To = rcpt
.From = SENDER_EMAIL
.Subject = subject
.HTMLBody = msgHTML
.Send
End With
Заключение
В этой статье мы рассказали, как реализовать технологию доставки инфор-
мации на основе SQL Server 2000, VBScript, ADO, CDO и DTS-пакетов.
Наш пример прост, но функционален. Чтобы расширить его, вы могли бы
использовать более сложную модель данных и внешние компоненты, по-
зволяющие отправлять пользователям более полные отчеты. Кроме того,
вы могли бы интегрировать в VBScript-задание другие DTS-задания. Ме-
тодика, описанная в этой статье, применима и к рассылке самой разнооб-
разной информации: регулярных финансовых отчетов, сведений о состоя-
нии заказов и любых других данных.
7-5947
Франческо Балена
Автоматизация выполнения
административных задач
в SQL Server*
(dual interfaces), поэтому она применима почти в любом языке, в том чис-
ле в Visual Basic, C++, VBScript, JScript, Windows Script Host (WSH) и
ASP-сценариях. В статье приводится исходный код, показывающий, как
программно считывать параметры конфигурации, создавать базы данных,
запускать сценарии T-SQL, создавать задания на резервное копирование
и указывать расписание их выполнения. Основная часть кода написана на
Visual Basic, но концепции, о которых рассказывается в статье, легко реа-
лизовать на любом другом языке программирования.
Обзор SQL-DMO
С физической точки зрения, объектная модель DMO реализована в SQL-
DMO.DLL. Вспомогательный файл SQLDMO.RLL содержит все локали-
зуемые ресурсы. В SQL Server 7.0 этот RLL-файл находится в каталоге
\Mssql7\Binn\Resources'vrxYA- (если при установке выбран путь, предлага-
емый по умолчанию), где хххх — десятичное значение идентификатора
языка (например, 1033 для U.S. English), В SQL Server 2000 по умолчанию
используется каталог C:\Program Files\Microsoft SQL Server\80\Tools\
Binn\Resources\;u:xr. Упомянутые файлы копируются на диск стандарт-
ной программой установки SQL Server, так что для активизации DMO
дополнительных действий не требуется. Кроме того, программа установ-
ки запускает сценарий SQLDMO.SQL (написанный на T-SQL) для уста-
новки нескольких системных процедур, необходимых DMO. Вы можете
самостоятельно запустить его из каталога \Mssql7\lnstall (для SQL Server
7.0) или Microsoft SQL Server\MSSQL$w.w,a cepeepa\lnsta\\ (для SQL
Server 2000), если полагаете, что эти процедуры были удалены или изме-
нены. DMO также устанавливается в составе инструментальных средств,
развертываемых на клиентских рабочих станциях. В файле REDIST.TXT
в корневом каталоге установочного компакт-диска SQL Server содержит-
ся информация по установке и редистрибуции SQL-DMO.
BackupDeuices
Languages
RemoteServers
Re in ale Logins
Server Roles
Set NL = sqlApp.ListAvailableSQLServers
For index = 1 To NL.Count
cboServers.Addltem NL.Item(index)
Next
Для SQL Server 7.0 этот код выведет список серверов, которые работают
под управлением Windows NT, используют протокол Named Pipes и нахо-
дятся в одном домене сети. (Так как при поиске серверов применяется
широковещание по NetBIOS, список серверов может стать еще короче в
зависимости от инфраструктуры маршрутизации. И еще одно: серверы
SQL Server под управлением Windows 9.r не показываются, поскольку они
не прослушивают пакеты по протоколу Named Pipes.) В SQL Server 2000
при поиске серверов наряду с широковещанием по NetBIOS применяется
широковещание по UDP, поэтому в список попадут и те серверы, которые
используют стек протоколов TCP/IP. Кроме того, если на локальном ком-
пьютере работает сервер, он тоже добавляется в список.
Подключение и отключение
Для подключения к заданному экземпляру SQL Server нужно создать
объект SQLServer, при необходимости присвоить подходящее значение
полю LoginTimeout и вызвать метод Connect:
Dim SQLServer As New SQLDMO.SQLServer
SQLServer.LoginTimeout = 10
If UseNTAuthentication Then
1
Аутентификация средствами Windows NT
SQLServer.LoginSecure = True
SQLServer.Connect strServer
Else
£98 Microsoft SQL Server
1
Аутентификация средствами SQL Server
SQLServer.Connect strServer, strlogin, strPassword
End If
SQLServer.Registry.AutostartServer = True
SQLServer.Registry.AutostartDTC = True
Exit Sub
errHandler:
HsgBox Err.Number & " " & Err.Description & " <" & _
Err.Source & " " & Err.LastDUError & ")", _
vbQKOnly, "SQL Server Service Manager"
€nd Sub
200 Microsoft SQL Server
End Function
Объект Database
Это, пожалуй, самый интересный с точки зрения разработчика объект в
иерархии DMO-SQL. Получив на него ссылку, вы можете создавать и уда-
Автоматизация выполнения административных задач в SQL Server
DataBaseRoles
FullTeitCattlogs
StoredProcedures
SystemDatalypes
UserDefinedDataTypes
FileGroups
Set db - SQLServer.Databases("pubs")
For Each tbl In db.Tables
PriRt "TA8L£ " 4 tbl.Nase
For Each col In tbl.Columns
Print " " 8 col.Name
fiext
Next
oSQLServer.LoginSecure * True
,oSQLServer.Connect "(local)"
oSQLServer.Databases("pubs").Exeeutelmmediate sqlText
oSQLServer.Disconnect
AlerlCategories
JabCategories
JabSchedules
JobSteps
QperatorCategories
Operators
TargetSermGroups
MemberServers
TargetServers
Заключение
В примерах кода к статье содержится демонстрационное приложение, по-
зволяющее выбрать одну или несколько баз данных и либо сразу же вы-
полнить их резервное копирование, либо указать расписание: каждые п
Microsoft SQL Server
дней, начиная с заданных даты и времени (рис. 12). Если щелкнуть кноп-
ку (Ж, программа динамически создаст объект Job, состоящий из несколь-
ких операций, и инициализирует его дочерний объект JobScheduIe в соот-
ветствии с параметрами, заданными пользователем. Это пример простого,
но надежного UI, который по достоинству оценят большинство пользова-
телей.
Первая попытка
Мы с самого начала понимали, что данные должны передаваться на пре-
зентационный уровень в XML-формате, так как это позволило бы нам ис-
пользовать XSL для преобразования XML в HTML (поддержка наиболее
распространенных браузеров была для нас очень важна, равно как и воз-
можность изменять визуальное представление без перекомпиляции при-
ложения). Однако было неясно, как создавать XML-данные и передавать
их на презентационный уровень. Для надежности мы хотели инкапсули-
ровать создание и синтаксический анализ XML. Инкапсуляция позволи-
ла бы скрыть детали использования XML от разработчиков, незнакомых
с синтаксическим анализатором (parser) MSXML.
Решение
А что если вместо определения уникальной XML-схемы для каждого на-
бора данных, поступающего на презентационный уровень, сделать так,
чтобы сами данные определяли формат XML? И почему бы, вместо того
чтобы поддерживать уникальные классы для каждого набора данных, не
разработать универсальный код, который будет считывать данные и созда-
вать XML? Тогда мы смогли бы экономить по два-три дня на введении
поддержки каждого нового набора данных! Именно это и было целью раз-
работки нашей архитектуры. Она показана на рис. 1. Вот что представля-
ют собой ее компоненты.
УРОВЕНЬ
s KpSH.BHI
aotryni
[ лроиещри
Ш$*ИШ1С
«iSKSener
Пример Web-приложения
В код, который можно скачать к этой статье, мы включили пример Web-
приложения, использующего нашу технологию. Оно предоставляет про-
стой Ш к базе данных Northwind, поставляемой с SQL Server. Как развер-
тывать компоненты, мы поясним позже.
Histor
Ц http://niyserver/empdetai
Edit
Employee Information:
Ms. Nancy Davolio
Sales ^Representative
Here is a list of the orderer? for which you are the sales representative.
Order ID Customer Name Shipped Date Total
11077;, Hattiesnake Canyon Grocery 1255^
П071 LILA-Supennercado S-484.50
.00
\ 10 67 Drachenblut Delikatessen 05/06И9Э
ji|M Save-a-bt Markets '--:-:! 05/04/199
11039 LINO-Delicateses
Supremes delices
1 1 027 Bottom-Dollar Markets
ASP-страницы
Выборка данных начинается, когда пользователь открывает одну из ASP-
страниц системы. Мы рассмотрим процесс выборки на нескольких примерах
исходного кода. Наш код реализует гипотетическое приложение, работа-
ющее в интрасети и позволяющее сотруднику входить в систему и офор-
млять заказы. Пользователь входит в систему, открывая в браузере стра-
ницу login.asp и вводя свой идентификатор (employee ID). После успеш-
ного входа происходит перенаправление на страницу empdetail.asp. Эта
страница полностью основана на нашей RAD- архитектуре, поэтому мы
детально рассмотрим ее работу.
Страница Функции
Страница Функции
Объекты бизнес-уровня
Объекты бизнес-уровня реализованы в проекте MiddleTier.vbp, написан-
ном на Visual Basic и скомпилированном в spdamid.dll. В этом проекте два
класса: cEmployees и cOrders (считывающие сведения о сотрудниках и за-
казах соответственно). В предыдущем разделе мы показали, как презента-
ционный уровень вызывает метод GetDetail класса cEmployees, чтобы по-
лучить информацию о вошедшем в систему сотруднике. Рассмотрим этот
метод, чтобы понять, что происходит, когда презентационный уровень
вызывает этот метод.
strlnvokingXML = m_objParmCol.ParametersXML(strSPName)
strResultXHL = oAccessor.GetDataXML(strInvokingXML, ReturnType)
Хранимые процедуры
Чтобы весь этот механизм работал, в нашей базе данных должны быть хра-
нимые процедуры, имеющие строго определенный формат. При разработке
этого формата мы стремились к гибкости в задании типа и объема возвра-
щаемых данных Кроме того, мы учитывали, что единственным модулем,
Вызов хранимых процедур я получение их результатов через Web
' Предполагается, что где-то выше есть операторы Dim и Set для cmd
1
Делаем что-то с rs...
Loop
Все делается так же, как и при использовании одного набора результатов, —
просто добавляются цикл While и вызов NcxtRecordset
В-5947
226 Microsoft SQL Server
dt;dt*"i4">11077</OrdefIO>
dt: dt="string">RATTC</CLJStomerIO>
,<CoiapanyNaaie dt:dt="string">Rattlesr>ake
Canyon Grocery</CompanyName>
<0rder0ate dt:dt="string">05/06/199a</0rderpate>
<Shipped8ate dt:dt="string"x/ShippedDate>
<Total dt;dt="string">1255.72</Total>
</Qrder>
<0rder>
<OrcferID dt:dt="i4">n07K/OrderIB>
<CustOBier:D (Jt:dt=1'strin9">LIUS</CiJstoinerII)>
<CompanyName dt:dt="string">LILA-SiJpermerca(Jo</CoiBpanytJarne>
<OrderDate dt:at="string">05/05/1998</OrderDate>
<ShippedDate dt:dt="string"></ShippedDate>
<Total cft:dt="string">484.50<Aotal>
</Srder>
</Orders>
</ Etnp loyeeDet ail>
• Customer ListCustomers;
• Einployee_GetDetail;
• Employee_GetIDFromOrder;
• Order_CreateOrder;
• Order_GetDetail;
• Order_Updat:eOrder;
• Product_ListProducts;
• Valid Employee.
«Crdsrs;.
^;"."7inr>
s.Ordi>|-iD dl -l=11t.i">11077c/Ofd£-l'I!J>
Jt:iit=!'itrifta">Rattiesnake
Canyon Grocery c/CompanyName?
Программное определение
параметров хранимой процедуры
Один из способов передачи параметров хранимой процедуре — вручную
сформировать набор ADO-параметров. Пишется код примерно такого вида:
Однако, если вы работаете с ба:юй данных SQL Server 7.0, то можете ини-
циализировать объект Command и вызвать метод Refresh его набора Para-
meters, Тогда в набор Parameters загрузятся описания параметров из базы
данных. После этого вы можете обращаться к параметрам, используя име-
на параметров в качестве индексов. При таком подходе пишется пример-
но такой код:
' Предполагается, что cmd уже объявлена оператором Dim
' как ADODB.Command и ей присвоено соответствующее значение
' оператором Set
With cmd
.Parameters.Refresh
With .Parameters
,Item("@Parameter").Value = 10
.Item("saParameter2"}.Value = "Some Text"
End With
.Execute
End With
Второй подход дает ряд преимуществ. Во-первых, если кто-то решит изме-
нить имя параметра в базе данных, вы сможете точно узнать, какой пара-
метр изменился, так как при попытке обращения к этому параметру воз-
никнет ошибка. При использонании первого способа ошибки не будет,
пока вы не выполните команду. Кроме того, второй подход позволяет пе-
ребирать объекты Parameter, запрашивать их свойства и действовать в со-
ответствии с полученными значениями свойств. Мы применили этот под-
ход при формировании XML-представления параметров, передаваемого
адаптеру доступа к данным.
Развертывание компонентов
Развертывание лучше выполнять в указанном нами порядке. Все ссылки
на имена папок даются по отношению к корневому каталогу примеров
кода (каталогу, где вы сохранили этот код).
Если вы не очень хорошо знакомы с созданием DSN, см. SQL Server Books
Online.
Чтобы развернуть DLL доступа к данным в среде СОМ+, сделайте вот что.
Развертывание ASP-страниц
Для развертывания ASP-страниц выполните следующее.
Заключение
Разработанная нами методология оказалась вполне удачной. На написа-
ние универсальных компонентов потребовалось около недели. После это-
го передача информации между клиентской частью и базой данных пре-
вратилась в обмен соответствующими XML-данными. Это упростило пе-
редачу информации между компьютерами, так как все данные передаются
по значению, а не по ссылке на объект. Представление данных в XML-
формате облегчило разработку клиентского интерфейса: чтобы изменить
представление данных на экране, нужно лишь изменить XSL-преобразова-
ние, применяемое к XML.
Компактные и надежные
приложения на основе SQL
Server CE 2.0 и .NET Compact
Framework*
Microsoft SQL 2000 Windows CE Edition (также известный как SQL Server
СЕ 2.0) является дальнейшим развитием версии Microsoft SQL Server,
Studio .NET. За счет бесшовной интеграции Visual Studio .NET и SDE об-
разуют платформу для разработки приложений Windows СЕ.
Try
:
ceCn.QpenO
Catch a As SqlCeException
MsgBox(a.ToStrinaO)
End Try
Try
Dim ceCiad As New System.Data.SqIServerCe.SqlCeComraand^strSQl, ceCn)
' Создать экземпляр ListBox
Dim listfloxl As Hew ListBoxO
With listBoxl
' Задать размеры и позицию списка
.Size = New System.CeDrawing.Size(20B, 160)
.Location = New System.CeDrawing.Point(8, 64)
ceOr.CloseO
Try
ceRda.InterrretUrl = strlnternetURL.
,ceRda.LocalConnectionString = strLocalConnect
Серверные средства
В SQL Server CE 2.0 есть новый мастер Connectivity Setup Wizard, облег-
чающий создание виртуальных каталогов и разрешений на доступ к дан-
ным (рис. 3). Пользователи предыдущих версий SQL Server CE знают, что
для взаимодействия SQL Server с устройствами, работающими под управ-
лением SQL Server CE, нужны определенные виртуальные каталоги и пра-
ва доступа. Новый мастер позволяет создавать на компьютере, где установ-
лен Microsoft Internet Information Services (IIS), виртуальные каталоги и
управлять ими, Кроме того, при помощи этого мастера можно конфигури-
ровать и настраивать разрешения NTFS на компьютере с IIS и SQL Server.
4* Microsoft !ад £епя?г CE Connectivity Wizard - ft<rasote RacrtVrtfcrwraft SOI- Server Ctterceweb^J и|0|Щ]
*• -* И ЕВ Lf
_J ^..tcde Root HTTp Car!en
,Fo|(ja HTTPAu!hfnlice!ion ! HTFS Pamisaons j
-; Щ Microsoft SQL Sever CE
:|ПЩ| Thf! foldia should contain the Seiwi Agent [Ssceja20 d([
p?r^^R^™5iWs^^7^^^ - . Bl2Wie I
Sel the HTTP perrrmiHis Id" the conterU fokta Елеси1в p^imission is lequneti by SQL Server CE
P Bead
P Runicripl5[aJchasASP]
Г W,it,
fi< Browse
Как и в предыдущей версии SQL Server СЕ, при работе под управлением
IIS в новой версии для взаимодействия между Windows СЕ и SQL Server
2000 используется библиотека ISAPI DLL. Таким образом, если устрой-
ство имеет доступ к виртуальному каталогу SQL Server СЕ через HTTP
(т. е. возможен просмотр этого каталога через Pocket Internet Explorer),
оно может подключаться к серверной базе данных с использованием Re-
mote Data Access (RDA) или Merge Replication.
Как и в прежней версии SQL Server СЕ, локальную базу данных можно
защищать только паролем или паролем и 128-битным шифрованием. Если
локальная база данных защищена паролем, к ней нельзя получить доступ
программным способом, но это не мешает просмотру файла базы данных
как обычного текстового файла.
Кроме того, через SQL Server Client Data Provider можно выдавать запро-
сы к SQL Server для выборки данных с последующей записью их в локаль-
ную базу данных SQL Server CE.
248 Microsoft SQL Server
Рис. 4. ISQLW
Клиент
Приложение
Т
SOL Server База данных
Client Agent SGI Server СЕ
Сервер
ИВА
IIS
SQL Server CE Hi
service Agent
out
Репликация
SQL Server Reconciler
(синхронизатор)
.out
Пишем приложение
А теперь проиллюстрируем новые функции SQL Server CE 2.0 на приме-
ре реального кода. Для этого напишем на С# простое приложение Smart-
Device, используя Visual Studio .NET и .NET Compact Framework. Чтобы
упростить разработку и тестирование этого приложения, возьмем эмуля-
тор Pocket PC 2002.
~гв SQL Server Errterfmse Manager - [Cens&te RootUJicrosoft SQL SwversVSQL Serve... |- ItljfXj
"|э Fife Action $ei4 Look ^indo-rt help -...jeJKJ-
<&=•••, (t] щ gf* Ц| щ, rj§ 4; f s S^ Q 33 ^
Is О Console Root 5QLCERep(DemoNet N wind_5QL^ 8 Items
Hi ^fj Microsoft SQL Servers gents__ ^I^KL_J
'•— jjjj£ S(3_ Server Group
~. i$3 MBLAPOl (Windows NT)
•?i)MBPPC01:-7 Anonymous
!*. \2\ Databases
^SQLCE5Lib*l:-l Anonymous
:*' О Data Transformation Services
•^)SQLCE5ub#J:-2 Anonymous
+ Sj.1 Management
^)SQLCESub#1;-3 Anonymous
'rl CiJ Replication
E
£ 4-^ Publications §)SQLCE5ub#lH Anonymous
^ SQLCEReplDernoNet:Nwind_SQLCE €)SQLCESub#l:-5 Anonymous
••JiB Subscript iori5 €j|sQLCESUb#l;-6 Anonymous
-; Ц Replication Monitor
'r."-i^ Publishers
-i ^ MBLAP01
t. -.^J Agents
О Replication Alerts
± Cil Security
* ;£j Support Services
'i1 jii] Meta Data Services
<: ; Ш
Обзор приложения
Разработанное нами приложение иллюстрирует некоторые из обсуждав-
шихся ранее новых возможностей SQL Server CE 2.0, в том числе репли-
кацию через метод SqlCeReplication.Synchronize. Мы также продемонстри-
руем полную (двухстороннюю) синхронизацию базы данных Nwind_SQL-
СЕ (хранящуюся на компьютере с SQL Server 2000) с локальной базой
данных подписок SQL Server СЕ и ряд других средств, например обьект
SqlCeException и параметризованные запросы,
: Employe E ID
Synchronize
EX* ;::::
^Enroll 1
Ш • "--'•
Рис. 7. Windows-форма
Первая часть кода демонстрирует, как создать пустую базу данных через
объект SqlCeEngine в SQL Server СЕ и синхронизировать ее с существую-
щей публикацией SQL Server для базы данных Nwind_SQLCE через объ-
ект Replication. Функция DBInit, вызываемая обработчиком Form_Load,
создает пустую базу данных подписки (если ее еще нет) и обращается к
DВ Sync, чтобы синхронизировать базу данных публикации с базой дан-
ных подписки (рис. 8).
ShowErrors(ex);
MessageBox.Show(ex.Message);
try
ShowErrors(ex);
MessageBox.Show(ex.Messag«);
finally
oRepl,8ispose();
Компактные и надежные приложения на основе SQL Server CE 2.0 255
if (iNumPar != 0)
oStrBld.Append("\nNum. Par. : " -f iNumPer);
if (sErrPar 1= String.Empty)
oStrBld.Append{"\R Err. Par. '•*• sSrrPar);
HessageBox.ShQfcKoStr&ld.ToStrifigO,
oStrBld.RemoveCO, ostraid.l
try
oCon.OpenC);
SqlCeCommand oCmd = oCon,CreateCoinBiand();
oCfnd.CoitiraandText =* "SELECT EmployeelQ, RTRDt(LastName) +
-
, * + RTRlM(FirstName) AS Y'Full Hanre\" mm
Employees WHERE lastName LIKE ? 0ROER BY LastNaffle";
ShowErrors(ex);
Messa§eBox.Show(ex.Message);
finally
if (oGon I- null)
oCon.Dispase();
} • -
Компактные и надежные приложения на основе SQL Server CE 2.0
Pocket PC 2002
Emulator Help
Е:Г 1ЕМУ8ЕЯ
Buchananj Steven
I ID Л*
CaSahan, Laura :
DavollOj Nancy 1 -----
Dodsworthj Anne i
Fuller, Andrew
King, Robert 7
Levelling, Janet 3
9-5917
258 Microsoft SQL Server
Марк Браун (Mark Brown) — главный архитектор ПО, а Дэвид Менье (David
Meunier) — ведущий инженер ПО в компании IdentityMine Inc. (http://
www.identitymine.com), которая является независимым поставщиком ПО
(Такома, штат Вашингтон). IdentityMine занимается проектированием и
реализацией бизнес-решений следующего поколения с применением
серверных технологий Microsoft .NET Enterprise. С авторами можно связать-
ся по адресам mark.brown@identityrnine.com и
david.meunier@identitymine.com соответственно.
Марк Браун
Доставка информации
в реальном времени
с применением Notification
управления подянсиав
ТЛ
Рис. 1. Приложение управления подпиской
</Schema>
</EventClas5>
</EventClasses>
Хронологии событий
Еще одна особенность архитектуры сбора событий — поддержка хроноло-
гии событий (event chronicles). Полное объяснение того, как их можно
использовать в уведомляющем приложении, я приведу в следующем раз-
деле. А пока скажу, что пакет событий обрабатывается только в процессе
генерации уведомлений (не считая случаев системных сбоев и перезагру-
зок), и таблицы хронологии событий позволяют в этот период сохранять
(или архивировать) данные событий для последующей обработки.
Генерация уведомлений
После того как необработанные данные событий собраны провайдерами и
записаны в таблицу хронологии событий, можно приступать к обработке
подписок.
Необработанные
Подписки
события
Хронология
^ событий
Данные
1едомления
Компонент,
объединяющий уведомления
КОМПОНЕНТ,
форматирующий кояшт
:
.'.Какая I Какая 1 Канав
Яввтавкй ЬоЕтав** 1 до&таекв
1 & I В; I С
Рис. 5. Форматирование и доставка уведомлений
Развертывание и масштабируемость
Как я уже говорил, приложения Notification Sen-ices отличаются высокой
масштабируемостью — во многом благодаря нижележащей архитектуре
распределенных сервисов. Более того, каждое уведомляющее приложение
делится на экземпляры. Каждый экземпляр использует собственные запи-
си в реестре и запускается как отдельная Windows-служба. Это позволяет
выполнять множество экземпляров одного уведомляющего приложения
как на одном компьютере, так и в системе из нескольких компьютеров.
Конфигурации с вертикальным
и горизонтальным масштабированием
С помощью XML-файлов определения и конфигурации экземпляры уве-
домляющего приложения можно настраивать по-разному и при необходи-
мости размещать их на отдельных компьютерах. Кроме того, большинство
компонентов таких приложений (например, сбора событий, генерации
уведомлений, дистрибуции и доставки) являются многопоточными, что
позволяет распределять нагрузку между несколькими процессорами. Это
обеспечивает возможность вертикального масштабирования (scale-up),
когда производительность повышается простым добавлением памяти и
процессоров в единственный сервер.
Заключение
Notification Services позволяет отслеживать любые источники данных, в
том числе предыдущие версии SQL Server, файловые системы и нестан-
дартные источники. Notification Services (она доступна для скачивания с
http://www.microsoft.com/sql/ns) упрощает разработку уведомляющих
приложений, которые своевременно доставляют нужную клиентам инфор-
мацию.
Динамическое связывание
уровня данных
с хранимыми процедурами
и командами SQL*
CreatedByllser,
CreatedDate,
Title,
Mo relink,
НоШеМо relink,
Expi reOate,
Description
FROM
Announcements
WHERE
KodulelD = @HoduleXB
AND
ExpireDate > 6etDate()
Connection.Close
Рис- 3. AnnouncementsDB.GetAnnouncements
Хорошая новость в том, что перенос вашего кода доступа к данным с клас-
сической клиент-серверной архитектуры или Windows DNA достаточно
прост. А плохая — вам все равно нужно поддерживать в коде определения
хранимых процедур и писать объемистые функции-оболочки на С# или
Visual Basic .NET.
Так что же, засучить рукава, и пусть этот генератор создает код не на VIJA,
а на С# или Visual Basic .NET? Нет, этим мы заниматься не будем. Более
современный подход — использовать преимущества некоторых инноваций
в CLR и автоматически генерировать вызовы хранимых процедур на осно-
ве сигнатур функций. Я продемонстрирую, как это делается, и предложу
одну библиотеку, которая послужит вам отправной точкой,
Динамическое связывание уровня данных 279
Метаданные и Reflection
Компилятор, рассчитанный на CLR, генерирует метаданные, описываю-
щие все аспекты типа и его членов. Эти метаданные обычно помещаются
в сборку (assembly) (статическую на диске или даже динамическую в па-
мяти), и к ним можно обращаться в период выполнения через сервисы
отражения из пространства имен System.Reflection. Рис. 4 иллюстрирует,
как это реализуется на С#. Класс PortalDatabase содержит Си-эквивалент
функции Get Announcements, которую вы видели в VBA-коде. Поскольку
на данном этапе я лишь показываю, что представляют собой сервисы от-
ражения, реального кода в самой функции нет. На выходе этот пример
кода дает имя функции, типы всех ее параметров и тип возвращаемого
значения:
PortalDatabase.GetAnnouncements [System.Data.DataSet]
System.Data.SqlClient.SqlConnection connection
System.Int32 moduleld
Type.GetMethodlmpl("GetAnnouncements",
BindingFlags.Public | BindingFlags.Instance |
BindingFlags.Static, null, CallingConventions.Any,
new Type[] { i:ypeof(SqlConnection), typeof(int)
}, null);
Hethodlnfo methodlnfo =
typeof(PortalDatabase).GetMethod ("QetARnoufseenmnts",
RSW Type[] { typeof(SqlConnection), typeof(int) });
SqlCommand command =
SqlGofflffiandGerte rato r. Gene rateCommand (connection.,
methodlnfo, new object[] { noduleld });
DataSet dataSet = new DataSetC);
SqlDataAdapter dataAdapter = new SqlDataAdapter{commaRd};
dataAdapter.Fill(dataSet);
return dataSet;
Хотя код на рис. 6 делает то, что нужно, он все еще далек от идеала, так как
теперь вы должны заботиться о синхронизации объявления Си-функции
и параметров Type.GetMethod. Если вы измените имя функции или тип
какого-нибудь параметра, вам придется соответственно модифицировать
вызов Type.GetMethod. Эту проблему, связанную с получением метадан-
ных метода, можно решить простым проходом по стеку и захватом фрей-
ма, принадлежащего нужному методу. В библиотеке Framework Class
Library (FCL) есть очень удобная для этого функция, но она скрыта, и вот
так сходу ее не найдешь. Я подскажу вам, что это за функция: Method-
Base. GetCurrentMethod из пространства имен System.Reflection. У нее нет
никаких аргументов, и она возвращает MethodBase (сейчас это либо Ме-
thodlnfo для обычного метода, либо Constructorlnfo для конструктора
типа), который представляет вызвавшую функцию. Чтобы передать его в
GenerateCommand, вы должны привести возвращаемое значение к Ме-
thodlnfo. В окончательном виде вызов выглядит так:
public static DataSet GetAnnouncementsCSqlConnection connection,
int moduleld)
{
SqlConrnand command = SqlCommandGenerator.GenerateCommand(
connection, (Methodlnfo) MethodBase.GetCurrentMethodQ,
new object[] { moduleld });
Библиотека-пример
Эта библиотека, которую можно скачать вместе с другим исходным кодом
для моей статьи по ссылке http://download.microsoft.com/download/msdn-
magazine/code/Aug02/WXP/EN-US/NETReflection.exe, решает все ранее
упомянутые проблемы. Вероятно, вы воспользуетесь ею как отправной
точкой и адаптируете или расширите библиотеку под свои, более специ-
фические потребности. В моей библиотеке предполагается, что вы имеете
дело с SQL Server, поэтому она работает в основном с объектами из про-
странства имен System.Data.SqlClient и типами данных, перечисленными
в System.Data.SqlDbType.
SqICommandMethodAttribute
Атрибут SqICommandMethodAttribute служит трем целям. Во-первых, он
помечает функцию, написанную на С# или Visual Basic .NET, как ориен-
тированную на команду базы даннЪтх. Для большей безопасности я сделал
его обязательным, так что SqlCommandGenerator заглохнет и сообщит о
неудаче проверки (assertion failure), если вы случайно подсунете ему ме-
тод без этого атрибута. (Наверное, вы предпочтете заменить эти проверки
собственными исключениями.) Во-вторых, поскольку у SqICommand-
MethodAttribute нет конструктора по умолчанию, вам придется хотя бы
указать тип команды, представляемой функцией. Поддерживаются два
значения из свойства System. Data. CommandType перечислимого типа:
CommandType.StoredProcedure (для хранимой процедуры) и Command-
Type. Text (для параметризованного SQL-запроса). В-третьих, у SqICom-
mandMethodAttribute имеется свойство CommandText, позволяющее зада-
вать имя целевой хранимой процедуры или SQL-оператора. В первом слу-
чае вам понадобится лишь предоставить имя хранимой процедуры, если
оно вдруг отличается от имени вашей функции-оболочки. Например, если
бы хранимой процедуре IBuySpy Portal было присвоено имя spGetAnno-
uncements, а вы захотели бы назвать свою функцию просто GetAnnoun-
cements, то вы могли бы применить этот атрибут следующим образом:
[ SqlCommandMethod( CommandType.StoredProcedure, "spGetAnnouricements") ]
public static DataSet GetAnnouncements(SqlCofinection connection,
int moduleld)
int moduleld)
NonCommandParameterAttribute
В классе NonCommandAttribute нет абсолютно никакого кода; помимо
того, что он наследует от System.Attribute, его определение пусто. Это ти-
пично для атрибутов, действующих просто как метки. Фактически един-
ственный член в подобных атрибутах — конструктор по умолчанию, гене-
рируемый компилятором в отсутствие такового. NonCommandParameter-
Attribute полезен, если вы не хотите, чтобы определенные параметры
вашей функции, написанной на СП или Visual Basic .NET, включались в ге-
нерируемый SqlCommand:
[ SqlCommandMethod(CommandType.StoredProcedure} ]
public static DataSet GetAnnouncementsC
[ NonCommandPa гаmete г ] SqlConnection connection,
int moduleld)
I
1
Бот так я позаботился о том, чтобы объект SqlConnection передавался как
первый параметр, Он — часть интерфейса функции, а не хранимой проце-
дуры в базе данных и, кроме того, не является параметром в параметризо-
ванном SQL-выражении. Класс SqlCommandGenerator пропускает любой
параметр, помеченный этим атрибутом; в ином случае предполагается об-
работка по умолчанию. В большинстве рассмотренных до сих пор приме-
ров я использовал статические функции, но если бы вы сделали объект
соединения членом класса, содержащего ваши функции-оболочки, этот
атрибут вам вообще бы не понадобился.
SqIParameterAttribute
SqlParameterAttribute обрабатывает любые расхождения между парамет-
ром функции и параметром целевой команды. В отличие от SqlCommand-
Met hod Attribute и SqlNonParameterAttribute этот атрибут не предназначен
просто для того, чтобы помечать некий параметр, — хотя и это возможно
при использовании его конструктора по умолчанию. Реальная необходи-
мость в нем возникает, только когда между параметрами есть какие-то раз-
личия; иначе SqlCommandGenerator пытается извлечь максимум инфор-
мации из метаданных соответствующего параметра метода.
. StoredProcedure) 3
public static int AddQrderltesK
;
E NonOommandParameter 3 SqlConnection connection,
{ SqlParameterC'PartNr", 20) ] string partHuiaber,
[ SqlParameterCSqlDbTypa. Decimal, Scale = 9, Precision = 4) ] decimal
«sltPrice, Int quantity)
{ ... } . ' " .,- • -r - - -
288 Доступ к данным из приложений
SqlCommandGenerator
Именно этот класс в конечном счете принимает метаданные метода, при-
меняет все переопределения, заданные моими атрибутами, и генерирует
готовый к выполнению объект SqlCommand. Его единственный открытый
метод GenerateCommand представляет собой более полную реализацию
того, что вы уже видели на рис. 5. Как и раньше, второй параметр в Gene-
rateCommand идентифицирует функцию, на основе метаданных которой
следует генерировать команду, — только на этот раз я сделал его необяза-
тельным. Если вы передаете NULL (или Nothing в Visual Basic .NET),
Generate Command автоматически использует метаданные вызвавшей фун-
кции. Свою работу он начинает с класса StackTrace из пространства имен
System.Diagnostics, чтобы инициировать трассировку стека (stack trace).
if (method == null)
method = (Methodlnfo) (new StackTrace<).GetFrame(1).GetMethod());
10-5947
290 Доступ к данным из приложений
nvarchar(IQQ),
«Title nvarcHar(150),
tHoreLlRk nvarchar{150),
@MobileMo relink nvarchar(15Q), .
©ExpireOate OateTime,
^Description nvarchar(20QO),
int OUTPUT
AS
INSERT INTO AnfflOtmceiiBnts
(
Module ID,
CreatetfByUser,
CreatedDate,
Title,
HoreLink,
см. след. стр.
292 Доступ к данным из приложений
@Mo relink,
©ExpireDate,
^Description
SELECT
int teiap = b;
b = a;
a - temp;
a = (int) parameters[0};
Ь = (in-t) paraiaeter&Et];
Обработка NULL-значений
О чем я вам еще не рассказал, так это об обработке NULL-значений. Если
хранимая процедура допускает NULL-значения в одном или двух парамет-
рах, в вашем прокси-методе нельзя использовать предопределенные типы
вроде int в С# и Integer в Visual Basic .NET. Вместо этого вы должны объя-
вить свой метод принимающим один из типов значений из пространства
имен System.Data.SqlTypes. Допустим, в базе данных Pubs имеется храни-
мая процедура:
SELECT *
FROM [employee]
WHERE [job_id] = ISNULL(@job_id, [job_id])
Динамическое связывание уровня данных 297
Бонус
Итак, вы видели, как с помощью атрибутов можно автоматически генери-
ровать команды в период выполнения, но они же позволяют создавать
вспомогательные утилиты и инструменты, полезные при разработке при-
ложений. Чтобы вы получили представление о том, как создать простой
инструмент, посмотрите на программу, приведенную на рис. 13. Если при
ее запуске в командной строке указывается какая-нибудь сборка, програм-
ма просматривает все экспортируемые типы и их методы и сообщает о тех
из них, у которых есть атрибут SqlCommandMethodAttribute со свойством
CommandType, установленным в CommandType.StoredProcedure. Какой-то
десяток строк кода — и вы сможете находить в сборках все прокси-функ-
ции хранимых процедур!
Динамическое связывание уровня данных 299
using System;
.usinf System. Reflection;
using Sample.Data.Sql;
class Sample
SqlCofflfflandHeth-odAttribute attribute =
CSqlCofBffiandHethodAttribute) Attribute.GetGustomAttribute
(iiethodlnfo, typeof(SqlCoramandMethodAttri&ute»;
if (attribute != null &&
attribute. CoiwnandType ==
System.Data.CormandType.StoredProcedure)
Console, Write("{6}.{1}",
nethodlnfo.NaBe);
If (attribute.CofflaiandText.Lervjth 1= 0)
Console.WriteC -> {0}", attribute.CoiwaandText);
.Writellne();
set noeount on
declare @sp varchar(lOO)
set esp = '« здесь указывается имя хранимой процедуры »'
case t.name
when 'char' then 'string'
when 'nchar' then 'string'
when 'varchar' then 'string'
when 'nvarchar' then 'string'
when 'bit' then 'bool1
when 'datetiiae' then 'DateTime'
when 'float' then 'double'
when 'real' then 'float'
when 'int' then 'int'
else 'object /* ' + t.name + ' */'
end
' + lower(substrlng(c.name, 2, 1)} + su*string(c.name, 3, tOO)
case c.colid
when 91ast then ')' + char(13) + "{'
else ','
end
case o.colid
when elast ttierv ');' + char(13) + ',}*
else ','
end
from dbo.syscolumns с
where c.ld = @oid
order by c.colic)
Заключение
Поддержка создания собственных атрибутов, связывания их с различны-
ми элементами программы и запроса метаданных через механизм Reflec-
tion открывает колоссальные возможности в автоматизации и в разработ-
ке совершенно нового класса динамичных приложений. Я продемонстри-
ровал использование атрибутов и механизма Reflection на примере
решения реальной проблемы (упрощения вызовов хранимых процедур) и
надеюсь, что вы теперь понимаете, как применить их на практике в других
ситуациях. Моя библиотека годится для любого CLR-совместимого язы-
ка программирования. В ней много чего можно усовершенствовать. Так, вы
могли бы реализовать кэширование, чтобы часто используемые и сложные
команды с массой параметров не становились «узким местом» в вашей
программе. Однако я не стал бы слишком увлекаться кэшированием без
предварительного профилирования кода. В целом, по сравнению с тради-
ционной настройкой объекта команды мой Sql Command Generator должен
работать лишь чуть медленнее.
Советы по защите