WWW.DISSERS.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА

   Добро пожаловать!

Pages:     | 1 |   ...   | 2 | 3 || 5 |

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

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

однако возможно применение и 270 Microsoft SQL Server нестандартных протоколов. Для этого вы реализуете протокол как класс, помещаемый в сборку управляемого кода, которая доступна вашему домляющему приложению.

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

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

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

Но учтите, что логика повторной доставки применяется ко всему пакету уведомлений: если не доставлено хотя бы одно уведомление из пакета, не удачной считается доставка всего пакета.

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

Развертывание и масштабируемость Как я уже говорил, приложения Notification отличаются высокой масштабируемостью — во многом благодаря нижележащей архитектуре распределенных сервисов. Более того, каждое уведомляющее приложение делится на экземпляры. Каждый экземпляр использует собственные запи си в реестре и запускается как отдельная Windows-служба. Это позволяет выполнять множество экземпляров одного уведомляющего приложения как на одном компьютере, так и в системе из нескольких компьютеров.

Свой вклад в высокую масштабируемость Notification Services вносит и тесная интеграция с SQL Server. Поскольку SQL Server хорошо зирован под запросы объединения (joins), такая интеграция гарантирует отличную производительность приложений. И конечно же, для сбора событий или создания правил генерации уведомлений нет ничего эффективнее запросов T-SQL!

Доставка информации в реальном времени Конфигурации с вертикальным и горизонтальным масштабированием С помощью XML-файлов определения и конфигурации экземпляры уве приложения можно настраивать и при необходи мости размещать их на отдельных компьютерах. Кроме того, большинство компонентов таких приложений (например, сбора событий, генерации уведомлений, дистрибуции и доставки) являются многопоточными, что позволяет распределять нагрузку между несколькими процессорами. Это обеспечивает возможность вертикального (scale-up), когда производительность повышается простым добавлением памяти и процессоров в единственный сервер.

Однако в ситуациях, когда одномашинная конфигурация не справляется с нагрузкой, многие компоненты Notification Services можно распределить между несколькими серверами. Данные экземпляров и приложения всегда должны храниться на одном SQL-сервере, но провайдеры событий, гене ратор уведомлений и компоненты дистрибутора можно регистрировать на разных серверах.

Чтобы задействовать такие конфигурации с горизонтальным масштабиро ванием (scale-out), установите исполняющую среду Notification Services на каждом компьютере, где будет зарегистрировано уведомляющее приложе ние. Развертывание подобной конфигурации упрощается за счет использования конфигурационных файлов и файлов определения приложения в формате — они облегчают регистрацию и создание экземпляров приложения на каждом компьютере.

Заключение Notification Services позволяет отслеживать любые источники данных, в том числе предыдущие версии SQL Server, файловые системы и нестан дартные источники. Notification Services (она доступна для скачивания с http://www.microsoft.com/sql/ns) разработку приложений, которые своевременно доставляют нужную клиентам инфор мацию.

Марк Браун Brown) — главный архитектор программного обеспечения в компании Inc. находящейся в (штат Вашингтон). IdentityMine занимается проектированием и разработкой нового поколения Интернет-решений для бизнеса на основе серверных технологий и программного обеспечения Microsoft С ним можно связаться по адресу mark.brown@identitymine.com.

Азиз Динамическое связывание уровня данных с хранимыми процедурами и командами SQL* Крупный недостаток, связанный с вызовом хранимых процедур SQL (stored — предоставлять соответствующую информацию о типах. Приходится писать объемистые функции — хранимых процедур только для того, чтобы обеспечить доступ к нужным типам данных.

в Framework сервисы отражения services) простран ства имен ion позволяют использовать метаданные, щие типы данных. В статье объясняется, как с помощью механизма Reflection в покончить с кошмаром хранимых процедур. Автор создает четыре собственных класса, один из которых генерирует объект на основе метаданных вашего метода. Используя библиоте ку, вы сможете автоматизировать генерацию объектов команд.

Если часто пользуетесь хранимыми то, уверен, отлично знаете, как это утомительно — писать один и тот же код, определяющий имя, тип и размер каждого параметра, всякий раз, когда готовишься выз вать объект команды (command object). А если вы когда-нибудь изменяли интерфейс хранимой процедуры, вам скорее всего приходилось возвра щаться к своему уровню доступа к данным (data service layer) и соответственно модифицировать вызовы хранимой процедуры. Програм мисты готовы на что угодно, лишь бы упростить этот процесс: централи зовать вызовы хранимых процедур в уровне сервисов доступа к данным, * в MSDN Редакция. 2002. №2 (август). — Прим. изд.

276 Доступ из приложений писать оболочки вокруг них и даже реализовать генераторы. Я собираюсь продемонстрировать, как с помощью атрибутов в Microsoft можно определить хранимую процедуру так, словно это функция, которую вы пишете на своем любимом языке — будь то С#, Visual Basic или дру гой язык, поддерживающий объявления с атрибутами и ориентированный на общеязыковую среду (common language runtime, В классическом ADO вызов даже самой тривиальной хранимой процеду ры, которая принимает единственный требует создания та команды и настройки набора параметров. Рассмотрим хранимую проце дуру из приложения-примера IBuySpy Portal (http://www.ibuyspy.com) (рис. 1).

Рис. 1. IBuySpy) AS SELECT Title, Expi WHERE AND > В данном случае не имеет никакого значения, знаете ли вы этот образец портала IBuySpy и структуру его базы данных. Здесь важно другое: интер фейс хранимой процедуры. бы вы использовали ADO и Visual Basic for Applications функция-оболочка для вызова этой хранимой про цедуры выглядела бы так, как показано на рис. 2.

Написание такой функции-оболочки позволяет не только определение хранимой процедуры в проекте, но и хранимую про цедуру за обычную Благодаря этому вызов хранимой цедуры упрощается до:

Динамическое уровня данных Dim Connection As Set Connection = OpenConnection() Dim Recordset As Recordset = 13) Рис. 2. Вызов через ADO As Integer) As As = New Set = Parameters As Set = 0} Command.

0) * As fteoordset New =, Set Set = Recordset End Function Это даже ужесточает контроль типов: свойство Value объекта Parameter определено как Variant, но параметр функции — как Integer.

Но когда для вызова простейшей хранимой процедуры вы пишете код, подобный функции в вашей голове звенит сигнал тре воги. Вы начинаете создавать какие-то вспомогательные функции, кото рые возвращают отсоединенный набор записей (disconnected recordset) при наличии любого объекта Command. Это уменьшает размер оболочек хранимых процедур, хотя настройка параметров вручную и синхрониза ция оболочек с определениями в базе данных — занятие скучное и чрева тое ошибками. Следующим интуитивным шагом может стать написание генератора, автоматически создающего в Visual Basic на основе объектной библиотеки функции — оболочки хранимых процедур. Многие разработчики даже пишут XML-определения хранимых процедур, а затем используют в качестве генератора кода XSLT-преобразование.

278 Доступ к данным приложений 3.

{ и = new SqlDataAdapter // как SPROC rayCoMaod.SelectComi»aRd.CQmantfType = в = 4);

// и заполняем DataSet new DataSet return Когда вы переносите свой код в ситуация во многом остается пре жней. Вместо объекта используется объект из пространства имен (при работе с SQL Server). Вместо ADO-объекта Command создается экземпляр SqlCom Вместо Parameter создается SqlParameter. Сам процесс фактически не меняется. По сути на рис. 3 показана оболочки Anno Lin cements из Хорошая новость в том, что перенос вашего кода доступа к данным с клас сической клиент-серверной архитектуры или Windows DNA достаточно прост. А плохая — вам все равно нужно поддерживать в коде определения хранимых процедур и писать объемистые функции-оболочки на С# или Visual Basic Так что засучить рукава, и пусть этот генератор создает код не на а на С# или Visual Basic Нет, этим мы заниматься не будем. Более современный подход — использовать некоторых в CLR и автоматически генерировать вызовы процедур на осно ве сигнатур функций. Я продемонстрирую, как это делается, и предложу одну библиотеку, которая послужит вам отправной точкой, Динамическое связывание данных Метаданные и Reflection Компилятор, рассчитанный на CLR, генерирует метаданные, описываю щие все аспекты типа и его членов. Эти метаданные обычно помещаются в сборку (assembly) (статическую на диске или даже динамическую в па мяти), и к ним можно обращаться в период выполнения через сервисы отражения из пространства имен Рис. 4 иллюстрирует, как это реализуется на С#. Класс PortalDatabase содержит функции которую вы видели в Поскольку на данном этапе я лишь что представляют собой сервисы от ражения, реального кода в самой функции нет. На выходе этот пример кода дает имя функции, типы всех ее параметров и тип возвращаемого значения:

Рис. 4, Пример Reflection в С# using using using sealed class public static DataSet class static | f in { in method.

к данным из приложений connection Функция Main начинает с получения всех методов класса Маска позволяет указывать, какие методы следует включать в возвращаемый GetMethods. и Bind ingFlags. Public обязательны, так как GetAnnouncements объявляется как public и static, — иначе возвращаемый массив окажется пуст. Флаг гарантирует, что в возвращаемый массив будут включены методы только от запрошенного типа. Помните, что Announ cementsDB — это класс и поэтому он неявно наследует от Если вы не хотите включать методы своих базовых классов, то знайте, что отбрасывает их. Остальной код в Main ется тем, что перебирает каждый перечисляя параметры и отображая интересующую нас информацию.

Рис. 5. Генерация на метода sealed private public static SqICommand connection, values) i = new = parameters for i * 1;

I < i++) sqlParameter = new = sqlParameter);

} return command;

Теперь вы представляете, как перечислить параметры нужной функции и создать объект SqICommand вкупе с соответствующими объектами SqlPa rameter. Все, что от вас требуется, — определить в коде на С# или Visual Basic функции, представляющие ваши хранимые процедуры. Осталь ное могла бы взять на себя единственная вспомогательная функция, кото рая динамически создает и конфигурирует объекты SqICommand в пери од выполнения. Такая функция могла бы выглядеть как показано на связывание данных рис. 5. Располагая объектом соединения (connection object), метаданными метода и значениями, которые должны быть переданы хранимой процеду ре, Generate Command генерирует полностью сконфигурированный и гото вый к выполнению объект Внимательные читатели, вероятно, заметили, что я не указывал тип дан ных для параметра метода, подразумевая SqlDbType, и не настраивал свой ство Туре объекта Это тем, что объект получает нужную информацию при настройке свойства Value. SqlDbType устанавливается, исходя из типа значения Value. А если вы все же явно настроите свойство Туре, объект SqlParameter отключит свой внутренний механизм «предположений» mechanism). Какой SqlDbType сопоставляется с тем или иным типом в Framework (System), C# или Visual Basic показано в табл. 1.

Табл. 1. Сопоставления типов SqlDbType System C# Visual Basic (не поддерживается) char Char long Long Binary Array byte Byte of Bit Boolean Char System. String string String System. DateTime System.DateTime Date Decimal System. decimal Decimal Float double Double Image byte Byte of System.Byte System.Int32 int Integer Money System. Decimal Decimal System. String string String NText string String NVarChar string String Real float Single System. DateTime System.DateTime Date short Short Decimal SmallMoney System. Decimal decimal Text System.String string String System. Date DateTime Date System.Byte byte Byte System. Guid Guid System.

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

н данным из приложений Табл. 1. Сопоставления (окончание) System C# Basic System. Array of string String Variant object Object Далее нужно поместить GenerateCommand в PortalDatabase.GetAn а затем использовать сгенерированную команду. Для этого функция должна передавать объект с мета данными, полученный через Как видно на рис. 6, поиск подходящей информации по заданным имени метода и типам его парамет ров берет на себя один из перегруженных методов Type.GetMethod. В ре альности он переадресует вызов защищенному абстрактному методу подменяя недостающие фрагменты информации значениями по умолчанию. Так что мой простой вызов Type.GetMethod внутренне транслируется в вызов | | null, new Type[] { typeof(int) }, null);

Рис. 6. метода ptfblic static DataSet connection, = Type[] { });

= Gene rateCommand { });

DataSet dataAdapter = new return dataSet;

Любопытно, что класс Type и его метод GetMethodlmpl являются абстрак тными. Так кто же предоставляет конкретную реализацию для них? Класс наследующий от — закры тый класс из сборки и, поскольку обращаться к нему связывание уровня напрямую CLR раздает объекты всякий раз, когда вы запрашиваете информацию о типе через в или Type в Visual Basic Таким образом, Type. Get Method заканчивает вызовом которая в свою очередь исполь зует функцию SelectMethod объекта Binder. Поскольку вы никогда не пе редаете собственную реализацию Binder в этой цепочке вызовов, Runtime Get Method Impl получает реализацию Binder по умолчанию через ста свойство За поиск метода, наиболее полно удовлетворяющего заданному набору критериев, в конечном счете отвеча ет Binder.SelectMethod, реализованный в Binder.

Хотя код на рис. 6 делает то, что нужно, он все еще далек от идеала, так как теперь вы должны заботиться о синхронизации объявления и параметров Если вы измените имя функции или тип какого-нибудь параметра, вам придется соответственно модифицировать вызов Type.GetMethod. Эту проблему, связанную с получением метадан ных метода, можно решить простым проходом по стеку и захватом фрей ма, принадлежащего нужному методу. В библиотеке Framework Class Library есть очень удобная для этого функция, но она скрыта, и вот так сходу ее не найдешь. Я подскажу вам, что это за функция: Method GetCurrentMethod из пространства имен У нее нет никаких аргументов, и она возвращает MethodBase (сейчас это либо Ме для обычного метода, либо для конструктора типа), который представляет вызвавшую функцию. Чтобы передать его в GenerateCommand, вы должны привести возвращаемое значение к Ме В окончательном виде вызов выглядит так:

public static DataSet connection, int { command = connection, new { moduleld });

// Остальной код опущен для } Небольшое отступление от основной темы:

thod крайне удобен при отладке, трассировке вызовов и т. д. Сколько раз вам приходилось писать простые тестовые функции, которые выводят на экран какое-то сообщение вроде «Inside Так вот, MethodBa вероятно, станет вашим новым лучшим другом. Строковое представление класса, производного от MethodBase, включает полную сигнатуру метода вместе с возвращаемым значением.

Это настоящее спасение от болевого синдрома запястий — особенно если 284 Доступ к данным из приложений вы пользуетесь перегрузкой метода и вам лень возиться с выводом сооб щений типа «Inside Fool» и Потом я покажу вам еще один способ, позволяющий добраться до мета данных метода даже без hod или MethodBase.GetCurrent Следующая проблема. А что, если у вашей хранимой процедуры и ее па раметров не те имена, которыми вы хотели бы пользоваться в своем коде?

Например, во многих проектах была принята схема именования, согласно которой имена хранимых процедур начинались с префикса Более того, иногда параметры нужно передать в С#-функцию просто как данные — без пересылки хранимой Здесь можно вспомнить все тот же пример с первый параметр которой — объект SqlCon nection. Если вы не уберете его в этом случае из списка параметров объек SqlCommand, рано или поздно возникнет исключение.

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

Библиотека-пример Эта библиотека, которую можно скачать вместе с другим исходным кодом для моей статьи по ссылке http://download.microsoft.com/download/msdn решает все ранее упомянутые проблемы. Вероятно, вы воспользуетесь ею как отправной и адаптируете или расширите библиотеку под свои, более специ фические потребности. В моей библиотеке предполагается, что вы имеете дело с SQL Server, поэтому она работает в основном с объектами из про странства имен и типами данных, перечисленными в Она включает четыре базовых класса: Method Attribute, и SqlCommandGene rator. Как и подсказывают их имена, первые три класса представляют со бой нестандартные (custom) атрибуты, a SqlCommandGenerator — класс со статическими функциями для генерации объекта SqlCommand на основе метаданных какого-либо метода. Эта реализация сложнее показанной на рис. 5, поскольку она рассчитана на адаптацию через атрибуты.

Динамическое уровня данных Атрибут SqICommandMethodAttribute служит трем целям. Во-первых, он помечает функцию, написанную на С# или Visual Basic как ориен тированную на команду базы Для большей безопасности я сделал его обязательным, так что заглохнет и сообщит о проверки (assertion failure), если вы случайно подсунете ему ме тод без этого атрибута. (Наверное, вы предпочтете заменить эти проверки собственными исключениями.) Во-вторых, поскольку у SqICommand MethodAttribute нет конструктора по умолчанию, вам придется хотя бы указать тип представляемой функцией. Поддерживаются два значения из свойства перечислимого типа:

(для хранимой процедуры) и Command (для параметризованного SQL-запроса). В-третьих, у SqICom mandMethodAttribute имеется свойство CommandText, позволяющее зада вать имя целевой хранимой процедуры или SQL-оператора. В первом слу чае вам понадобится лишь предоставить имя хранимой процедуры, если оно вдруг отличается от имени вашей функции-оболочки. Например, если бы хранимой процедуре IBuySpy Portal было присвоено имя uncements, а вы захотели бы назвать свою функцию просто то вы могли бы применить этот атрибут следующим образом:

[ ] public DataSet connection, int Если же их имена идентичны, достаточно сделать так:

[ ] public static DataSet connection, int moduleld) Наконец, если ваша функция представляет параметризованное SQL-выра жение, укажите для конструктора атрибута как первый параметр и SQL как второй. Вот так вы могли бы получить из базы данных портала, используя вместо хранимой процедуры парамет ризованный SQL-запрос:

[ "SELECT * WHERE = AND ExpireDate > ] public static DataSet connection, 286 Доступ к из Реализация настолько прямолинейна, что упоминания заслуживает лишь один аспект;

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

В классе нет абсолютно никакого кода;

помимо того, что он наследует от его определение пусто. Это ти пично для действующих просто как метки. Фактически един ственный член в подобных атрибутах — конструктор по умолчанию, гене рируемый компилятором в отсутствие такового. NonCommandParameter Attribute полезен, если вы не хотите, чтобы определенные параметры вашей функции, написанной на или Visual Basic включались в ге нерируемый [ ] public static [ г ] connection, int moduleld) I Бот так я позаботился о том, чтобы объект SqlConnection передавался как первый параметр, Он — часть интерфейса функции, а не хранимой проце дуры в базе данных и, кроме того, не параметром в параметризо ванном SQL-выражении. Класс пропускает любой параметр, помеченный этим атрибутом;

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

У класса NonCommandParameterAttribute, как и у Attribute, имеется атрибут Attribute Usage — только на этот раз он нацелен исключительно на параметры. Кстати, я поместил этот атрибут в про странство имен а не Sample.Data.Sql, поскольку он не специ фичен для определенного провайдера данных. Хотя это вынуждает вас уровня включать в свою программу два пространства имен, зато вы можете где угодно повторно использовать данный атрибут в аналогичных целях.

обрабатывает любые расхождения между парамет ром функции и параметром целевой команды. В отличие от SqlCommand Attribute и этот атрибут не предназначен просто для того, чтобы помечать некий параметр, — хотя и это возможно при использовании его конструктора по умолчанию. Реальная необходи мость в нем возникает, только когда между параметрами есть какие-то раз личия;

иначе пытается извлечь максимум инфор мации из метаданных соответствующего параметра метода.

У SqlParameterAttribute шесть свойств, контролируемых индивидуально;

Name, Size, Precision, Scale и Direction. Его конструктор пере гружен несколькими чтобы вы могли задавать Name, SqlDb Type, Size или комбинацию этих свойств в зависимости от того, в чем именно проявляются различия между параметрами. Если вам нужно ука зать направление (direction), точность (precision) или масштаб (scale) па раметра, вы должны вручную настроить свойства Direction, Precision и Scale. Несколько примеров использования SqlParameterAttribute показано на рис. 7.

Рис. 7. Применение } static E...

public [ 1 string state) {... } public static DataSet E Мол int. public E 3 connection, ] [ = 9, = 4) ] {. • 288 Доступ к данным из приложений При проектировании сложного атрибута важно определить, какие свой ства естественнее инициализировать в а какие — устанавли вать явно. Поскольку у немало, возмож ность инициализации их всех через комбинации перегруженных конструк торов привела бы только к путанице, а исходный код было бы трудно читать. Бот почему я предпочел инициализировать через конструктор лишь самые популярные свойства параметра* — имя, тип данных и раз мер. Помните об этом, собственные атрибуты.

Реализация SqlParameterAttribute требует пояснений. У этого класса име ется шесть свойств только для чтения: IsSizeDefined, IsScaleDefined, IsTypeDefined и SqlPara meterAttribute поддерживает для каждого свойства два состояния: опреде ленное (defined) или неопределенное (undefined). Все состояния по умолчанию инициализируются как неопределенные, а это означает, что имя, размер, точность, масштаб, тип и направление объекта параметра не заданы явным образом и поэтому должны быть установлены на основе контекста. Для SqlCommandGenerator контекстом являются метаданные параметра функции. Неопределенное состояние свойств Size, Preci sion и Scale отражается специфическим значением, допустимым для их типа. Например, имя считается неопределенным, если закрытое поле равно null или занято пустой строкой;

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

SqlCommandGenerator Именно этот класс в конечном счете принимает метаданные метода, при меняет все переопределения, заданные моими и генерирует готовый к выполнению объект SqlCommand. единственный открытый метод GenerateCommand представляет собой более полную реализацию того, что вы уже видели на рис. 5. Как и раньше, второй параметр в Gene rateCommand идентифицирует функцию, на основе метаданных которой следует генерировать команду, — только на этот раз я сделал его необяза тельным. Если вы передаете NULL (или Nothing в Visual Basic Generate автоматически использует метаданные вызвавшей фун кции. Свою работу он начинает с класса StackTrace из пространства имен чтобы инициировать трассировку стека (stack trace).

* Здесь подразумевается объект — Прим. сост.

Динамическое уровня 2S Затем он захватывает метод из предыдущего фрейма стека, передавая его индекс в (индекс, равный 0, соответствовал бы само му вызову GenerateCommand):

if (method == null) method = (new Теперь можно одним махом получить метаданные для функции из фрейма стека. При этом нет никакой необходимости в сложном вызове Черт возьми, даже вызывать и то не нужно. Проще некуда! Но два требования вы обязаны со блюдать: вызвавшей функцией должна быть для коман ды базы данных и она не должна быть конструктором. Последнее требова ние вызвано тремя причинами. это просто бессмысленно, даже если бы было возможно с технической точки зрения. Во-вторых, Attribute Usage в все равно запрещает применение этого атрибута к конструктору. И в-третьих, хотя StackTrace.GetFrame воз вращает (который является надклассом Methodlnfo и абстра гирует методы и конструкторы), GenerateCommand приводит его к Me thodlnfo. Поэтому, если бы конструктор попытался вызвать Generate Command, возникло бы исключение Из-за новой функциональности (распознавания вызвавшей добавленной в генератор, у вас может появиться соблазн всегда передавать NULL во втором параметре, но берегитесь: эта простота в использовании больно бьет по производительности. Прогнав серию тестов на своей маши не, я обнаружил, что максимальное быстродействие дает Type.GetMethod, лишь немного уступает ему, а проход по стеку с помощью StackTrace обойдется вам 12-кратным падением произ водительности. На практике разницей в быстродействии между первыми двумя способами можно пренебречь.

Тем не менее вариант на основе следует выделить как основной — по возможности выбирайте именно его и не пе редавайте NULL. Кстати, если подумали, что Method тоже использует какую-то разновидность трассировки стека, вы не ошиблись, но разработчики Framework, похоже, оптимизировали его для частых вызовов. Достаточно посмотреть, сколько раз сама обращает ся к стеку для проверки разрешений (permissions) у вызвавшей функции.

Покончив с этой задачей, GenerateCommand приступает к основной рабо те и проверяет, дополнен ли метод атрибутом SqlCommandMethodAttri bute. (Если нет, проверка заканчивается неудачей, и сообщается, кто вино ват в этом.) Далее GenerateCommand создает объект SqlCommand и ини циализирует его свойства Connection, и 290 к данным из Настраивая свойство он проверяет размер значения в одно именном свойстве атрибута. Если это значение представляет собой пустую строку, в свойство CommandText объекта команды записывается имя тода;

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

• получить параметр метода;

• если у него есть атрибут пропустить его;

• если у него есть атрибут использовать определенные в атрибуте, для настройки соответствующих свойств объекта SqlParameter;

• если атрибута SqlParameterAttribute нет, создать временный атрибут через конструктор по умолчанию;

• настроить аспекты SqlParameter на основе метаданных для этого параметра метода.

Кроме того, в исходном коде присутствуют две контрольные точки (asser tions), срабатывающие, когда число параметров (кроме помеченных атри бутом объявленных для метода, не со впадает с количеством значений, переданных в GenerateCommandPara meters. Это полезно в тех случаях, когда объявляешь какой-то параметр метода, но забываешь передать его значение генератору.

Проблема выходных параметров Одно из ограничений, упоминания, связано с направле нием (direction) объекта SqlParameter. Если в атрибуте оно не определено, то выбирается в соответствии с тем, как передается параметр метода. В С# параметр можно передавать по значению (по умолчанию), по ссылке (клю чевое слово ref) или только для возврата (output only) (ключевое слово out), поэтому направление соответственно задается как ParameterDirec или как put. В Visual Basic никакой разницы между последними двумя слу чаями нет, и направление задается либо как (для Динамическое данных параметров с либо как (для пара метров с Задав направление для вы сможе те получать нужные значения от хранимой процедуры или параметризо ванного SQL-запроса, но проблема в том, что их нельзя просто так (без дополнительных усилий) передать вызвавшей функции. Это связано с тем, что они возвращаются в по значению и в итоге не мо гут быть автоматически переданы вызвавшей функции. То же самое (и даже в большей степени) относится к типам значений, которые упаковы ваются (boxed) и копируются из-за того, что массив значений является массивом объектов. Поэтому после выполнения команды вам придется вручную копировать все выходные параметры из объекта в вызвавшую функцию.

Один пример. Возьмем хранимую процедуру AddAnnouncements из Spy Portal (рис. 8). Как и любая другая хранимая процедура такого она вставляет строку в таблицу и возвращает в выходном параметре авто матически назначенный идентификатор (auto-assigned identity). На рис, показано, как передать выходной параметр из хранимой процедуры в out параметр вызвавшей Хотя SqlCommandGenerator устанавли вает правильное направление для параметров с ключевым словом он не может автоматически передать в них значения, возвращенные храни мой процедурой.

Рис. 8. Вставка строки и возврат CREATE PROCEDURE OUTPUT AS ( см. след. стр.

Доступ к данным из приложений строки и ExpireDate, ) SELECT 9. хранимой с выходными значениями public static void ] connection, int [ ] string С ] string title, [ ] string С ] string expireDate, ] string description, int = 0;

// использование // null, new raorelink, expireDate, description, itemld itemld = (int) Кстати, та же проблема возникает и при использовании механизма Reflec tion для вызова метода, принимающего параметры по ссылке. На рис. показан метод Swap, вызываемый с применением позднего связы вания (late binding). После того как Swap заканчивает свою работу, Swap данных Proxy копирует значения из массива параметров в переменные, выделен ные вызвавшей функцией.

Заодно интересно посмотреть, как с этой ситуацией справляется Visual Basic потому что он позволяет использовать позднее связывание (в Option Strict Off) по более естественному и элегантному синтак сису, чем С#. Итак, на рис. 11 показана версия того же кода, что и на рис. 10, но написанная на Visual Basic Функция исчезла, так как все, что нужно для вызова члена по механизму Reflection, Visual Basic делает сам. В конечном счете он вызывает и это всего лишь еще один способ выполнения Рис. 10. Reflection и объекты Parameters, передаваемые по ссылке Sample public int a, ref int b;

b a - temp;

public static void int a, ref int b) = b a (int) void args) a = 1;

2;

a, b);

= b Рис. 11. Применение к Swap позднего связывания в Basic Sample a As Integer, b As temp As b, b a a см. след, стр.

к из Рис. 11. Применение к Swap позднего связывания End Sub Class Sub a As = b As = Dim о As Object New b) = b a, b) End Sub End Module Этот код обманчиво простым, но за кулисами Visual Basic генерирует тот же что и С#. Дамп метода Main в том виде, в каком он показывается ILDASM, приведен на рис. 12. Те, кто не хочет лишней головной боли от чтения должны поверить мне на слово: IL-код копирует элементы из временного массива обратно в пере менные а и Рис. 12. Дамп Main в public static void managed {.entrypoint void * 01 00 00 // size : € init int32 a, int [2] object [4] // and Studio Sub пор a As Integer = t см. след.

Динамическое уровня Рис. 12, Дамп функции Main в As Integer = Din b о As Object = void b) "Swap" Object box :

call void : class string, пор ref call 1L 004d:

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

Доступ к данным из Рис. 12. Дамп функции Main call s = b "a = b = box call void End пор // of method вы могли бы полностью решить проблему выходных пара метров, генерируя код так, как это делает Visual Basic и используя для этого средства, предлагаемые пространством имен tion.Emit, но это тема для другой статьи.

Обработка О чем я вам еще не так это об обработке NULL-значений. Если хранимая процедура допускает в одном или двух парамет рах, в вашем нельзя использовать предопределенные типы вроде в С# и Integer в Visual Basic Вместо этого вы должны объя свой метод принимающим один из типов значений из пространства имен Допустим, в базе данных Pubs имеется храни мая процедура:

CREATE PROCEDURE NULL) AS SELECT * FROM [employee] WHERE [job_id] = Динамическое связывание данных Поскольку параметр может быть NULL, вам следует объявить свой с использованием вместо short, как показано в следующем фрагменте кода:

[ public static DataSet GetEmployeesByJob( [ ] connection, [ ] { command = null, new { DataSet dataSet = new dataAdapter = new return dataSet;

I А вот как вы должны метод в тех случаях, когда параметр jobld равен NULL и когда он не равен NULL;

DataSet dataSet;

dataSet = new dataSet = 5);

// неявное приведение // к dataSet = GetEmployeesByJob(connection, Второй вызов возможен только в С#, поскольку Visu al Basic не поддерживает напрямую операторы преобразований. Тем не менее вы вправе сами вызвать даже если прячет ее от вас в Visual Studio, тогда ByJob получается таким громоздким, что с тем же успехом можно пользо ваться версией конструктора. Вот версия предыдущего кода для Visual Basic Dim dataSet As DataSet dataSet = New dataSet = dataSet = Определение пользовательских типов данных АЛЯ параметра через наследование Из всех атрибутов в моей библиотеке только класс Sql Attribute определен без ключевого слова sealed (или в Visual Basic Это диктуется его структурой — пусть даже в нем нет открытых или членов, которые могли бы быть переопределены каким нибудь подклассом. Обычно атрибуты предназначены для хранения лишь 298 Доступ к данным из приложений метаданных и не несут в себе никакой функциональнос ти, которая выходила бы за рамки того, что определено классом Attribute;

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

Допустим, в нескольких таблицах базы данных вы храните адреса элект ронной почты. Если такой адрес определен как то со здание для него пользовательского типа в SQL Server обеспечит согласо ванность определения адресов электронной почты во всех объектах базы данных. Когда вам понадобится изменить длину адресов, вы просто моди фицируете пользовательский тип — единственную точку измене ний. Аналогичным образом можно создать подкласс SqlParameterAttribute для централизации определения параметров, используемых во многих ко Ниже показано, как определить новый основанный на SqlParameterAttribute и служащий оболочкой пользовательского типа в SQL Server. Заметьте, что сам запе чатан:

: SqlParameterAttribute { public :

128} {} public name) :

base(name, 128) {} Бонус Итак, вы видели, как с помощью атрибутов можно автоматически генери ровать команды в период выполнения, но они же позволяют создавать вспомогательные утилиты и инструменты, полезные при разработке при ложений. Чтобы вы получили представление о том, как создать простой инструмент, посмотрите на приведенную на рис. 13. Если при ее запуске в командной строке указывается какая-нибудь сборка, програм ма просматривает все экспортируемые типы и их методы и сообщает о тех из них, у которых есть атрибут со свойством CommandType, установленным в Какой-то десяток строк кода — и вы сможете находить в сборках все ции хранимых процедур!

уровня данных Рис. Перечисление всех хранимых процедур using class static void Assembly assembly foreach type in foreach aietlwdlnfo iR attribute = if (attribute 1= 0) -> И последнее. В исходный код, прилагаемый к статье, я включил образец SQL-сценария (рис. 14), который генерирует для вызова хра нимой процедуры, при необходимости соответствующие ат рибуты параметров. Этот сценарий особенно полезен, когда вам нужны оболочки для хранимых процедур. Только учтите, что он не рас считан на все случаи жизни (в частности, он не решает проблему выход ных параметров). Но если вы имеете дело с наиболее распространенным классом хранимых процедур, сценарий сделает всю работу за вас.

Рис. 14. для генерации сигнатуры set = здесь имя см. след. стр.

Доступ к из Рис. 14. SQL-сценарий для генерации сигнатуры...

declare int select = sysobjects о where = declare — select = с where select c.colid when 1 ]' + + static + ' + else end ' ' case then С ' [ [ then else when when 'nchar' then then when then when then when then then when else /* ' t.name + ' */' end ' + + case when char(13) "{' см. стр.

связывание 14. генерации...

front dbo.syscolumns с left outer t on c.xusertype = t.xusertype where = order by — генератора case c.colld when 1 then return + else end ' 2, 1» + 3, 100) when + + else с where order by Чтобы использовать сценарий, просто загрузите его в (SQL Query Analyzer), перейдите в свою базу данных, присвойте переменной @sp имя хранимой процедуры — и вперед! Потом скопируйте выходной код из секции результатов (result pane), включите его в свое решение и при необходимости внесите в него изменения. В сценарии предполагается, что вы будете возвращать из своей функции объект а это полез но в основном для тех запросов, выбор метода сбора данных оставля ется на усмотрение вызвавшей функции. В одних ситуациях вызвавшая функция может подключить команду к а в других — бу дет достаточно Для получения корректных результатов надо также сообщить isqlw, чтобы тот удалил заголовки и показал результаты в виде текста, а не сетки Соответствующие параметры настраиваются на вкладке диалого вого окна Options (рис. 15). Кроме того, учтите, что сценарий ся только в SQL Server 2000, хотя, по идее, он должен нормально работать и в версии 7.0.

Перенастроить сценарий на генерацию кода для Visual Basic не сложно, и эту задачу я оставляю как упражнение для читателей.

Доступ к из приложений output Maximum per [ Выберите в списке results ("] «Results (о Text» Output numerics Discard флажок a Print column headers» Рис. 15. Удаление заголовков и отображение результатов в виде текста Заключение Поддержка создания собственных атрибутов, их с различны ми элементами программы и запроса метаданных через механизм Reflec tion открывает колоссальные возможности в автоматизации и в разработ ке совершенно нового класса динамичных приложений. Я продемонстри ровал использование атрибутов и механизма Reflection на примере решения реальной проблемы (упрощения вызовов хранимых процедур) и надеюсь, что вы теперь понимаете, как применить их на практике в других ситуациях. Моя библиотека годится для любого язы ка программирования. В ней много чего можно усовершенствовать. Так, вы могли бы реализовать кэширование, чтобы часто используемые и сложные команды с массой параметров не становились «узким местом» в вашей программе. Однако я не стал бы слишком увлекаться кэшированием без предварительного профилирования кода. В целом, по сравнению с тради ционной настройкой объекта команды мой Command Generator должен работать лишь чуть медленнее.

Атиф Азиэ (Atif Aziz) — главный консультант в AG и бывший направление его деятельности — помощь в переходе на Framework. Регулярно выступает на конференциях С ним можно связаться по адресу Майкл Говард и Кит Браун Советы по защите Десять лучших приемов защиты кода, о которых должен знать каждый Когда дело касается безопасности, есть много способов попасть в неприятно сти: доверять любому коду, выполняемому в вашей предоставлять доступ к важным файлам кому угодно и никогда проверять, не модифици рован ли код на вашей машине. А еще можно работать антивирусных программ, не встраивать в собственный код и выдавать чрезмерные привилегии слишком широкому кругу лиц. Или вручить все отмычки для взлома, легкомысленно используя некоторые встроенные функции.

можно оставить все порты сервера открытыми и не следить за ними.

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

Безопасность — проблема многоплановая. Угроза безопасности может ис ходить откуда угодно. От плохо написанного кода для обработки ошибок или от слишком щедро розданных разрешений. От забытых сервисов, ра ботающих на вашем сервере. От неразборчивости приеме вводимых дан ных... список можно продолжать еще долго. Вот 10 советов, которые дадут вам фору в борьбе щита и меча — помогут в защите компьюте ров, сеги, кода и в выборе более безопасной сетевой стратегии.

Публиковалось в Magazine/Русская Редакция. 2002. №3 (сентябрь). — Прим. изд.

304 к данным из 1. Не доверяйте данным, вводимым пользователями Даже если вы не прочтете больше ни строчки из этой статьи, запомните одно: не доверяйте тому, что вводят пользователи. Если поверить, будто пользователь всегда вводит данные, до неприятностей — один шаг. Большинство уязвимых мест в безопасности открывается взломщику, когда сервер получает заведомо некорректные данные.

Доверие к любой вводимой информации может приводить к переполне нию буфера, атакам с использованием сценариев (cross site scripting attacks) или с внедрением SQL-кода (SQL injection attacks) и д.

Рассмотрим каждую из этих потенциально возможных атак детальнее.

2. Защищайтесь от переполнения буфера Переполнение буфера происходит, когда атакующий предоставляет боль ше данных, чем ожидает приложение, в результате чего излишек данных во внутреннее пространство памяти. Переполнение буфе ра — проблема, характерная в для Это, конеч но, угроза, но обычно легко устранимая. Мы сталкивались лишь с двумя случаями, когда буфера было неочевидным и избавиться от него было непросто.

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

что переполнение буфера или обращение за его нижнюю границу происходит из-за ошибок в индексировании массива, но такие случаи встречаются реже.

Взгляните на фрагмент С++-кода:

void DWORD В чем здесь проблема? С этим кодом все в порядке, если значения cBuffSrc и cbBuffSrc передаются из доверенного источника, например, от кода, ко торый проверяет правильность структуры и размера данных. Но если данные поступят из ненадежного источника, атакующий (собственно источник) может запросто сделать так, чтобы значения cBuffSrc и cbBuff Src были больше значения Когда скопирует данные в она затрет адрес возврата из поскольку cBuffDest размещается во фрейме стека функции сразу за адресом возврата. Таким образом, атакующий заставит код выполнить деструктивные действия.

Десять лучших приемов защиты кода Чтобы избежать этого, нельзя доверять ни данным, вводимым пользовате лем, ни данным, хранящимся в cBuffSrc и cbBuffSrc:

void DWORD { const DWORD 32;

char ttifdef 0x33, cbBuffSrc);

cBuffSrc, i У этой функции три особенности, которые отличают правильно написан ный код, не допускающий переполнения буфера. Во-первых, она чтобы при вызове ей сообщили размер буфера. Конечно же, этому значе нию слепо верить нельзя! Во-вторых, в отладочной версии код проверяет, достаточна ли емкость буфера, чтобы вместить данные из исходного буфе ра. Если это не так, код скорее всего вызовет нарушение доступа (ошибку защиты памяти), и запустится отладчик. (Просто удивительно, сколько вы при этом обнаружите ошибок!) Наконец;

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

Когда в Microsoft был объявлен месячник по устранению связан ных с защитой (под названием Windows Security Push), мы создали для программистов, пишущих на С, список функций, которые безопасно обра батывают строки. Просмотрите его на 3. Не позволяйте запускать сценарии Уязвимость, связанная с возможностью запуска сценари ев, — специфичная для Web которая может привести к компро метации клиентских данных из-за изъяна на единственной Web-странице.

Вообразите фрагмент кода на ASP.NET:

+ Подобный код видел почти каждый. Наверное, вы удивитесь, но он кишит ошибками! Обычно пользователь обращается к этому коду по примерно такому г. com/welcome.

Код на С# предполагает, что данные всегда корректны и не содержат ни кроме имени. Но атакующие, злоупотребляя его доверчивостью, под ставляют имя сценария и нужный HTML-код. Набрав 306 к данным из приложений вы получили бы Web-страницу, которая отображает окно со словом «Ну и — спросите вы. Представьте, что пользователь щелкнул эту ссылку, — тогда в строке запроса окажется какой-нибудь опасный сценарий и HTML-код, который получает ваш cookie и передает его на сайт атакующего. В результате он получит информацию из вашего cookie или сделает что-нибудь похуже.

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

Regex г = new if { // Хорошо! Со строкой в } else { // А это не Строка неправильна.

} Этот код проверяет строку по выражению, чтобы она содержала от 1 до алфавитно-цифровых символов и чтобы ничего больше в ней не было. Это единственный безопасный способ убедиться в корректности значения, HTML-код или сценарий через такое выражение не пролезет! Но не пола гайтесь на эти выражения в поиске недопустимых символов и не откло няйте запрос, если таковые символы будут найдены, потому что вы обяза тельно что-нибудь упустите.

способ — кодировать в формате HTML всю входную информацию, когда она используется как выходная. Это превратит опасные HTML-тэги в более безопасные escape-символы. С помощью метода ty.HtmlEncode (в ASP.NET) или Server.HTMLEncode (в ASP) вы обезопа сите себя от любых потенциально опасных строк.

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

Десять лучших приемов защиты кода Взгляните на этот фрагмент кода:

void Id) { SqlConnection sql= new + sqlstring= "SELECT + FROM shipping WHERE Id + ;

= new В нем три серьезных изъяна. Web-сервис подключается к SQL Server по учетной записи системного администратора (sa). Чуть позже вы поймете, почему это плохо. Во-вторых, учетной записи sa назначен жутко хитрый пароль «password»!

Но настоящий повод для беспокойства — в конкатенации строк, из кото рых формируется SQL-выражение. Если пользователь вводит идентифи катор 1001, получается следующее SQL-выражение (абсолютно верное и допустимое):

SELECT hasshipped FROM shipping WHERE id = Но изобретательные атакующие идут дальше и вводят следующий идентифи катор: DROP table shipping что приводит к выполнению запроса:

SELECT hasshipped FROM shipping WHERE id = table shipping - ';

Это изменяет работу запроса. Код не только пытается определить, отгру жен ли некий товар, но и удаляет таблицу shipping! Оператор — обознача ет в SQL комментарий, который облегчает атакующему создание набора допустимых, но от того не менее SQL-выражений!

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

вместо этого используйте Windows-средства аутентифика ции или создайте специальную учетную запись с соответствующими (ог раниченными!) правами.

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

308 Доступ к из Педех г = new if throw new new string cmd = = Переполнение буфера, запуск сценариев и внедрение SQL-кода — все это примеры атак, возможных доверия к входной информации. От атак этого типа можно если считать любой ввод злонамеренным, пока не доказано обратное.

5. Избегайте доморощенной криптографии!

Теперь рассмотрим нечто близкое и дорогое нам. Я бы более 30% кода, который предназначен для защиты и который проходит через нашу экспертизу, содержит ошибки, создающие дыры в этой самой защите. Ве роятно, самая распространенная из них — доморощенный код шифрова ния, обычно весьма ненадежный и уязвимый перед взломом. Никогда не пишите собственный код для шифрования: как надо, у вас все равно не получится. Не воображайте, будто, если вы создали свой криптографический алгоритм, его никто не разгадает. У атакующих есть отладчики и достаточ но знаний, чтобы точно выяснить, как работает ваша а зачастую и взломать ее за пару часов. Для лучше использовать CryptoAPI;

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

6. Сводите вероятность атак к минимуму Если какая-то функциональность не нужна 90% ваших клиентов, не давай те устанавливать ее по умолчанию. Internet Information Services (IIS) 6. следует именно такому плану установки. В основе этого подхода к уста новке лежит следующая идея: если в системе работают неиспользуемые сервисы, они остаются без присмотра, что опасно. А если такая функцио нальность все же устанавливается по умолчанию, она должна работать по принципу наименьшей привилегии.

7. Используйте принцип наименьшей привилегии Политика безопасности нужна операционной системе и общеязыковой исполняющей среде (common language по ряду причин.

Многие считают, что главная из них — не дать возможности пользовате лям открывать файлы, не предназначенные для их глаз, перенастраивать Десять лучших приемов защиты кода сеть под свои потребности и совершать прочие нехорошие поступки. Уг роза внутренних атак, несомненно, существует, и гарантированная защи та от них но есть и другая причина. Политика безопасности при звана оградить код от преднамеренных или непреднамеренных действий пользователей, сеющих хаос в сети. Например, программа, полученная как вложение в почтовом сообщении и запущенная на машине пользователя, получит доступ лишь к тем ресурсам, которые доступны этому пользова телю. Если во вложении окажется троянский конь, хорошая политика бе зопасности резко ограничит возможный ущерб.

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

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

Когда случится какая-нибудь неприятность (а по закону Мэрфи это неиз бежно), вы будете рады, что эти стены есть. Вот несколько конкретных идей насчет того, как сделать, чтобы код работал с минимально возможной привилегией.

Выбирайте для своего серверного кода такой контекст защиты, дает доступ только к ресурсам, нужным для его работы, Если какая-то часть вашего кода требует существенно больших привилегий, подумайте об отделении ее от остального кода. Лучший способ безопасно изолиро вать код с другими удостоверениями защиты — выполнять его в отдельном процессе, работающем в более привилегированном контексте защиты. Это значит, что вам понадобится механизм для межпроцессного взаимодей ствия, например СОМ или Microsoft Remoting;

кроме того, придет ся разработать такой интерфейс, который позволит минимизировать об мен данными.

Если вы используете Framework, продумайте необходимый уровень привилегий для каждой сборки. Вполне вероятно, что изолировать код, требующий высоких привилегий, легче в отдельных сборках, которым можно дать больше разрешений, а остальные сборки пусть работают с 310 Доступ к приложений меньшими привилегиями — это позволит окружить код дополнительной защитной стеной. Простой способ ограничить привилегии конкретной сборки — запрашивать разрешения на уровне сборки, как показано на А рис. 2 иллюстрирует, как создавать XML-файлы, используемые при таких запросах. Только не что таким способом вы ограничи ваете разрешения не только для своей сборки, но и для любых сборок, вызываемых из нее.

Рис. 1. Запросы разрешений для сборки System;

// минимальные разрешения, необходимые // для этой сборки [assembly:

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

Объявив объект или набор разрешений и вызвав или Deny, вы добавляете в стек маркер, отнимающий все разрешения у любого вызыва емого вами кода. Делая так перед вызовом подключаемого модуля, вы ог раничите операции, которые сможет выполнять этот модуль. Так, подклю чаемому модулю, выполняющему расчет амортизации, вовсе не нужен до ступ к файловой системе. Это просто еще один пример использования принципа наименьшей привилегии, позволяющего заранее обезопасить себя. Обязательно документируйте эти ограничения и помните, что под ключаемые модули с высокими привилегиями способны обойти эти огра ничения через оператор Assert.

Десять лучших приемов кода Рис. 2. запросов разрешений using App static void { // набора разрешений a new fleat), IPermission new ps 8. Обращайте внимание на обработку ошибок Признайтесь: вы, как и все, ненавидите писать код для обработки ошибок.

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

Печально, но такой настрой — не лучший с точки зрения безопасности.

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

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

Оставляете ли вы систему в согласованном и безопасном состоянии? Во вторых, написав несколько раз пройдитесь по ней отладчиком, чтобы попасть в каждый обработчик ошибок. Но даже столь пристрастная проверка может не выявить малозаметные ошибки синхронизации. Воз 312 Доступ н данным из приложений можно, придется передать функции заведомо недопустимые аргументы или как-то изменить состояние системы, чтобы активизировать обработ чики ошибок. Пошаговое выполнение кода в отладчике отнимает много времени, однако не стоит жалеть его, чтобы лишний раз проверить код и состояние системы во время его работы. Таким способом нам удавалось обнаруживать массу изъянов в логике наших программ. Это проверенная временем методика — используйте ее. Наконец, убедитесь, что ваши тес товые комплексы действительно позволяют моделировать неудачное за вершение ваших функций. Старайтесь создавать такие тестовые комплек сы, которые заставят работать каждую строку кода в функции.

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

bool = // оптимистичное допущение!

try { II Проверить, есть ли доступ к c:\test.txt.

catch (SecurityException x) { // В доступе отказано accessGranted = catch { // Случилось что-то другое i Допустим, CLR не запрещает доступ к файлу, поэтому исключение Securi tyException не возникает. А что, если доступ к этому файлу запрещен, на пример, сопоставленным с ним списком управления избирательным дос тупом (DACL)? Тогда возникнет исключение другого типа, но оптимис тичное допущение в первой строке кода никогда не позволит нам узнать об этом.

Лучше писать этот код в пессимистичной манере:

accessGranted = false;

// пессимистичное допущение!

{ // Проверить, есть ли доступ к c:\test.txt new FileMode.Open, // Если дошли до этого места, все в accessGranted = } catch {} Десять лучших приемов защиты Это куда более отказоустойчивый код, поскольку независимо от сбойной ситуации он возвращается в самое безопасное состояние.

9. Олицетворение — опасная штука При написании серверных приложений вы нередко (прямо или косвенно) используете удобную функциональность Windows, называемую олицетво рением (impersonation). Олицетворение позволяет каждому потоку про цесса работать в собственном контексте защиты — как правило, в клиент ском. когда редиректор файловой системы получает через сеть запрос к файлу, он аутентифицирует удаленный клиент, проверяет, не на рушает ли его запрос DACL сетевого ресурса, а затем назначает потоку, который обрабатывает запрос, так называемый маркер олицетворения (impersonation token) клиента. В результате этот поток подменяет клиен та. Далее поток получает доступ к локальной файловой системе на серве ре в контексте зашиты клиента. Локальная файловая система проверяет права с учетом запрошенного типа доступа, DACL файла и маркера оли цетворения, присвоенного потоку, Если эта проверка заканчивается неуда чей, она сообщает об этом редиректору файловой системы, который воз вращает соответствующую ошибку удаленному клиенту. Для редиректора файловой системы это невероятно удобно, поскольку он просто передает эстафету локальной файловой системе и предоставляет ей самой разбирать ся с правами доступа — так, будто запрос поступил от локального клиента.

Все это хорошо для простых шлюзов наподобие редиректоров файловых систем. Однако олицетворение часто применяется и в более сложных слу чаях. Возьмем, к примеру Web-приложение. Если вы пишете классическое неуправляемое ISAPI или приложение ASP.NET, у которого в файле указано:

ftSPflfT Рис. 3. Проверка Как решить эту маленькую проблему? Ну, для начала следите, чтобы не было переполнения буфера, и помните о принципе наименьшей привиле гии. Если ваш код не нуждается в божественных полномочиях, предостав ляемых SYSTEM, не настраивайте Web-приложение на выполнение в про цессе Web-сервера. Если вы просто сконфигурируете его на выполнение со средним или высоким уровнем изоляции, ваш процесс получит маркер IWAM_MACHINE, вообще не дающий никаких привилегий, — в этом слу чае атаки будут далеко не так эффективны, как в предыдущем. Заметьте, что по умолчанию в IIS 6.0, который станет компонентом Windows Server, никакой пользовательский код не может работать как SYSTEM.

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

должны учитывать один прокол. Если вызвать СОМ-сервер (in-process COM server), чья модель потоков отличается от таковой для вызывающего потока, СОМ выполнит вызов в другом потоке. При этом СОМ не станет использовать маркер олицетво рения вызывающего потока, из-за чего вызов будет выполнен в контексте защиты процесса. Какой сюрприз!

Еще один сценарий, где олицетворение может подставить вас под удар.

Пусть ваш сервер принимает запросы через именованные каналы, DCOM или RPC. Вы аутентифицируете клиенты, олицетворяете их и открываете объекты ядра от имени этих клиентов. Теперь допустим, что вы забыли закрыть один из таких объектов (например, файл) после отключения кли ента. Затем подключается следующий клиент, вы снова его аутентифици руете, олицетворяете и... угадайте, что будет? У вас по-прежнему сохраня ется доступ к тому файлу, и, даже если у нового клиента нет прав на дос Десять лучших приемов защиты кода гуп к он сможет обращаться к этому ресурсу. А все дело в том, что для большего быстродействия ядро проверяет права доступа к объектам, только когда вы впервые открываете их. Даже если ваш контекст защиты потом изменится (из-за олицетворения очередного клиента), вы все рав но сможете обращаться к этому файлу.

Все упомянутые нами сценарии что олицетворение — штука, хоть и удобная разработчикам серверного ПО, но небезопасная. Если ваш код работает с маркером олицетворения, обратите на него самое присталь ное внимание.

10. Пишите приложения, с которыми смогут нормально работать не только администраторы На самом деле это следствие принципа наименьшей привилегии. Если программисты будут и дальше писать код, нормально работающий в Win dows, только когда его пользователем является администратор, то как же, черт возьми, мы избавим эту систему от клейма Windows предоставляет внушительный набор средств защиты, но, если приложение требует от пользователей прав администратора, а иначе отказывается что либо делать, то какой толк от всех этих средств?

Что здесь посоветовать? Прежде всего, не берите на себя слишком много.

Перестаньте работать по учетной записи администратора, и вы очень ско ро поймете, сколько мучений доставляет использование программ, чьи разработчики не учитывали требования защиты. Однажды я (Кит) устано вил одну программу, которая синхронизирует данные между карманным и настольным компьютером (она шла в комплекте с устрой ством), Так вот, я, как всегда, вышел из системы, где работал по обычной учетной записи пользователя, и, снова войдя в систему, но уже по встро енной учетной записи администратора, установил эту программу. Затем, вернувшись к обычной учетной записи, попытался запустить установлен ную программу. Она тут же показала мне окно с сообщением, что не мо жет получить доступ к нужным файлам данных, а потом все кончилось сообщением об ошибке защиты памяти (access violation). Ребята, но это же была программа от одного из крупнейших в мире производителей карман ных устройств. Такому просто нет оправдания!

Запустив утилиту (http://sysinternals.com), я быстро обнару жил, что злополучная программа пытается открыть для записи файл дан ных, установленный в один каталог с ее исполняемыми файлами. Когда приложения устанавливаются в каталог Program Files (как и должно быть), они ни в коем случае не должны записывать в этот каталог свои данные. Дело в том, что на каталог Program Files распространяется 316 Доступ к иэ приложений ничение доступа. Мы не хотим, чтобы пользователи что-то записывали в этот каталог, так как иначе из них может оставить там троянского коня, а другой — запустить его. Поддержка такой политики — фактически одно из базовых требований к приложениям с эмблемой Windows XP Слишком часто приходится слышать от программистов оправдания за раз работку программ под администраторской учетной записью. Если мы бу дем по-прежнему игнорировать проблему, это лишь усугубит ее. Ребята, для редактирования текстового файла не нужны привилегии администра тора — равно как и для компиляции или отладки ваших программ. А в тех случаях, когда такие привилегии действительно нужны, запускайте соот ветствующие программы с повышенными привилегиями через RunAs (см.

колонку «Security Briefs» за ноябрь 2001 по ссылке http://msdn.micro Если те инструменты для разработчиков, на вас ложится дополнительная ответ ственность. И нужно разорвать порочный круг, когда разработчики пишут код, который могут запускать только администраторы, — но добиться этого можно, только если инициативу поддержат широкие массы разработчиков.

За дополнительной информацией на этот счет обращайтесь на Web-сайт Кита Брауна (http://www.develop.com/kbrown);

и непременно прочтите книгу Майкла Говарда «Writing Secure (Microsoft Press, в которой даны рекомендации по написанию приложений, корректно рабо тающих без привилегий.

Майкл Говард (Michael Howard) — менеджер программы Security Program группы Secure Initiative в Microsoft. Соавтор книги Secure Code» и автор книги "Design Secure Web-based Applications for Microsoft Windows 2000» (обе опубликованы Microsoft Press).

Кит Браун (Keith Brown) работает в как исследователь, технический писатель и преподаватель. Разъясняет программистам концеп ции безопасного кода. Автор книги "Programming Windows Security» {Addison 2000), соавтор «Effective COM». Сейчас работает над книгой по безопасности в С ним можно связаться по адресу готовит к выпуску перевод издания этой кни ги (ориентировочно в третьем квартале 2003 г.).

Джонни Папа Доступ к данным Объекты DataRelation Одно из главных отличий от традиционной ADO в что технология позволяет истинно реляционные наборы Здесь рассматриваются все за и против представления данных в как многоуровневой реляционной структуры по сравнению с отдельным набором записей, получаемым через JOIN. Автор поясняет, как усовершенство вать приложение с помощью объектов DataRelation и какие решения принимать при работе с этими объектами. В заключение демонст рируется группы объектов DataTable. связанных через DataRelation, выборка родительских и дочерних наборов записей, а также выполнение каскадных обновлений DataSet.

Одно из главных отличий ADO.NET от традиционной ADO в том, что новая технология позволяет использовать истинно реляционные наборы записей Допустим, в DataSet имеется объект DataTable, содер жащий информацию о клиентах, и еще один объект DataTable, в котором содержатся сведения о заказах клиентов. В ADO.NET эти DataTable мож но связать друг с другом, воспроизведя отношение между ними, существу ющее в реляционной базе данных. Считав два набора записей (называе мых родительским и дочерним) и связав их между собой, можно извлечь все дочерние записи для данной родительской, отобразить любой из Data Table в сетке (grid) или модифицировать объекты и записать все изменения в базу данных за одно пакетное обновление. Все эти возможно сти реализуются объектами DataRelation — неотъемлемой частью прило жений ADO.NET.

* Публиковалось в MSDN Magazine/Русская 2002. №5 (ноябрь). — Прим. изд.

318 Доступ н данным из приложений В этой статье рассматриваются все за и против представления данных в как многоуровневой реляционной структуры по сравнению с отдельным набором записей, получаемым через INNER JOIN. Я поясню, как усовершенствовать приложение с помощью объектов DataRelation и какие решения приходится принимать при работе с этими объектами. В заключение я продемонстрирую, как создавать группы объектов DataTable, связанных через DataRelation, как извлекать родительские и дочерние на боры записей, а также как выполнять каскадные обновления DataSet, Зачем нужны DataRelation?

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

В свой февральской колонке за этот год я продемонстрировал, как расши рить приложение так, чтобы сохранять изменения в нескольких иерархи ях данных с применением DataRelation (http://msdn.

Пример, кото рый я тогда показывал, — стандартное приложение, отображающее и со храняющее данные в элементе управления DataGrid на Web-форме.

Grid идеален для отображения связанных объектов DataTable, так как в него встроена поддержка отношений между объектами.) Сегодня я уделю основное внимание объектам DataRelation и поясню преимущества и не достатки этих объектов в сравнении с традиционными наборами записей, возвращаемыми SQL-операторами.

В примерах я буду ссылаться на иерархическую структуру (custo заказы (orders), позиции заказа (order в базе данных Nor Решая, использовать один объект DataTable, содержащий объеди ненный (joined) набор записей, или группу связанных объектов DataTable, учитывайте, что две основные функции управления данными заключают ся в отображении информации и контроле изменений (как в базе данных, гак и в самой структуре данных).

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

Объекты в ADO.NET хранения нескольких связанных объектов DataTable не означает, что раз работчики обязательно должны ее использовать. Здесь полная аналогия с отверткой со сменными насадками: если нужно открутить всего один винт, она менее удобна, чем обычная отвертка. Точно так же и группа связанных объектов DataTable может оказаться не самым эффективным решением, если программа лишь отображает данные в одной сетке. Если же програм ма отображает данные, но не в одной, а в нескольких сетках, правильнее хранить данные в связанных объектах DataTable.

Приложения, просто показывающие данные, весьма распространены. Од нако не менее распространены и приложения, которые позволяют не толь ко отображать, но и модифицировать данные. В таких приложениях важно сбалансировать средства удобного и эффективного отображения данных с возможностью управления изменением данных. Здесь-то и проявляется гибкость ADO.NET. При хранении информации о клиентах, заказах и по зициях заказов в трех раздельных объектах DataTable, связанных друг с другом, вы сможете легко показывать эти объекты в нескольких сетках и управлять изменениями в любом из них. Объекты DataRelation, связыва ющие эти наборы записей, позволяют фильтровать данные (с использова нием метода GetChanges) перед отправкой изменений в базу данных. И тог да источнику данных передается лишь модифицированная информация.

Отдельные запросы и запросы с объединением Связанные наборы записей, использующие объекты DataTable и Data Relation, помогают избавиться от избыточности данных, неизбежной в единственном объединенном наборе записей. Так, объединение информа ции о клиентах, заказах и позициях заказов в единый набор записей при водит к дублированию данных, относящихся к клиенту (например, фами лии представителя клиента и названия компании), в каждом заказе и каж дой позиции заказа. Если клиент делает 5 заказов, а в каждом заказе по позиций, то информация о клиенте повторяется во всех 25 записях. Это давняя проблема разработчиков на SQL. Однако есть отличный способ избавиться от такой избыточности данных: используйте иерархические структуры данных вроде связанных объектов DataTable.

Обычно наборы записей формируются в результате запросов с объедине нием (join queries). Например, стандартный SQL-запрос для выборки кли ентов и их заказов возвращает один набор записей, в котором данные о клиенте дублируются в каждом заказе, т. е. возникает избыточность дан ных. А если вы запрашиваете еще и позиции заказов, избыточность данных увеличивается еще больше (рис. 1).

Изменение единого набора записей, полученного из нескольких таблиц, — операция весьма обременительная, если приходится обновлять родитель к данным из приложений ские данные. Чтобы модифицировать информацию о клиенте в наборе за писей «клиент, заказ, позиции и распространить изменения на весь набор записей, вам придется заново запрашивать из базы большой объем избыточных данных. Однако с объектов набор за писей можно разделить на связанные друг с другом наборы, представляю щие исходные таблицы. Благодаря этому получаемый в результате DataSet в большей мере соответствует реальной структуре данных. Это же способ ствует уменьшению избыточности данных. Обновление данных значи тельно упрощается, а отображение информации о клиентах и их заказах вообще становится элементарной задачей.

Рис. 1. Объединение информации о заказах и позициях заказов с JOIN Orders о ON = ШЕЯ JOIN Details] od ON = INNER JOIN Products p ON = Еще одно преимущество связанных объектов DataTable — увеличение гиб кости в обновлении базы данных. Когда данные разделены на три объекта DataTable (клиенты, заказы, позиции заказов), связанные объектами Data Relation, данные каждого DataTable можно обновлять с помощью команд, относящихся только к этому DataTable. Например, сохраняя изменения в DataTable, содержащем набор клиентам, можно использовать объект специфичный для таблицы Customers в базе данных Объект SqlCommand может ссылаться на SQL-оператор или хранимую процедуру, сопоставленную с объектом Adapter, кото рый и записывает изменения DataTable в источник данных.

Формирование данных XML-программисты уже пару лет используют иерархические структуры данных, и с появлением ADO.NET эти структуры стало возможным легко реализовать в В предыдущих версиях ADO для рабо с иерархическими структурами применялась называемая формированием данных (data Хотя формирование данных Объекты DataRelation в ADO.NET использовать иерархические структуры, у этой технологии были до статочно серьезные недостатки, помешавшие ее широкому внедрению.

Кроме того, она требовала изучения синтаксиса SHAPE.

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

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

Определение объектов DataRelation Как определить иерархический набор записей с помощью DataRelation?

Рассмотрим DataSet, содержащий три раздельных объекта DataTable, в которых хранится информация о клиентах, их заказах и о позициях зака зов. Эти DataTable можно связать друг с другом через объекты DataRela tion. Объект DataSet с DataTable, связанными через позволя ет выполнять каскадное обновление, перемещаться по родительским запи сям, объединять записи из разных источников данных и даже реализовать агрегацию данных, не делая того же в самой базе. Для начала вы должны создать объект DataSet с тремя наборами записей, полученными в резуль тате трех отдельных запросов (рис. 2).

В этом коде создается DataSet с тремя объектами DataTable: Data Table содержит информацию обо всех клиентах, второй — о заказах, тре тий — о позициях заказов. На данный момент мы определили DataSet, содержащий три набора записей, но пока не установили отношения. Я вос пользуюсь двумя объектами DataRelation: один свяжет клиентов с заказа ми, другой — заказы с позициями. Создание этих объектов DataRelation показано рис. 3 (данный код продолжает код, представленный рис. 2).

В результате создания объектов и их добавления в набор Relations объекта DataSet три набора записей DataTable связываются друг с другом по указанным полям. У объектов DataRelation, как и у большин ства других объектов ADO.NET, несколько разных конструкторов. Я за действовал конструктор, параметрами которого являются имя отношения, а также поля родительской и дочерней таблиц. Если бы отношение уста навливалось по нескольким полям, можно было бы передать массивы по Доступ к данным из приложений лей родительской и дочерней таблиц. Еще один вариант — указать те же три аргумента, что и на рис. 3, добавить четвертый аргумент (значение типа Boolean), определяющий, следует ли автоматически создавать огра ничения (constraints). Об этих ограничениях я расскажу чуть позже.

Рис. 2. DataSet Создаем соединение = "Data (sCn);

DataSet = DataTable клиентов string Customers";

= Заполняем OataTable заказов string FROM Orders";

= new //- OataTable позиций string = p.

FROM [Order INNER JOIN Products p od.ProductIO = = new Рис. 3.

//- Создаем DataReiation и клиентов = new и с DataReiation = Объекты в После заполнения DataSet тремя наборами и установления отно шений, связывающих объекты DataTable, DataSet можно показать в эле менте DataGrid на Web-форме, настроив свойство DataSource:

= oDs;

DataGrid достаточно «интеллектуален», чтобы понять: вы выводите не сколько объектов DataTable и нужно перемещаться по наборам записей в порядке, заданном объектами DataRelation.

Ограничения и каскадные операции Как и в базах данных, ограничения внешнего ключа (foreign key const raints) в объектах DataSet помогают обеспечивать целостность данных.

Когда я создаю DataRelation-объект Customer2Order (рис. 3), автомати чески создаются ограничение уникальности (unique constraint) для ключа родительской таблицы и ограничение внешнего ключа для дочерней таблицы Ограничение внешнего ключа автоматически вступает в силу, когда DataSet отображается в Data Grid на Web-форме. Поэтому, если изменить значение внешнего ключа дочерней на значение, отсутствующее в родитель ском объекте DataTable, возникнет исключение, которое связано с наруше нием целостности данных и которое можно перехватить. Чтобы убедить ся в том, что это происходит благодаря ограничениям, можно отключить ограничения, присвоив false свойству объекта DataSet.

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

Одно из новшеств SQL Server 2000 — поддержка каскадного обновления и удаления. Например, включив эту функцию в SQL Server 2000 и удалив родительскую запись, вы автоматически удалите и дочерние записи. В ADO.NET имеется аналогичная функциональность, контролируемая через свойства и объекта По умол чанию эти правила (rules) разрешают каскадное изменение связанных дан ных. Так, если вы измените значение в DataTable клиентов, соответствующим образом изменится и CustomerlD в DataTable заказов.

Это поведение можно изменить, присваивая свойствам одно из значений Rule перечислимого типа: Cascade (по умолчанию), None, или SetNull.

Чтобы создать собственное ограничение внешнего ключа, определите объект ForeignKeyConstraint. Он позволяет указывать поля и правила пе ред применением ограничения. Вот как создать ForeignKeyConstraint и сопоставить его с DataRelation:

324 Доступ из приложений oFKey = = = true;

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

Рис. 4. Перечисление дочерних записей //- Получаем записи клиента = string "The orders for first are:

записи для клиента for (int < в значения из дочерних записей = sMsg + + + + } эти значения А как быть, если вам нужно найти одну или несколько родительских за писей DataTable? Если родительских записей несколько, применяется ме тод Однако в нашем примере для каждого заказа может быть лишь одна родительская запись, поэтому я воспользовался методом Get Parent Row. В следующем фрагменте кода показано, как получить зна чения из родительского объекта DataTable клиентов по ссылке на первую запись в DataTable заказов:

DataRow oRow = Объекты Заключение Еще одно ADO.NET — возможность представления в формате Дело в что XML лежит в основе ADO.NET, поэтому DataSet легко преобразовать в Используя методы WriteXml, Write XmlSchema, GetXml или GetXmlSchema объекта DataSet, вы получите XML-представление его внутренней структуры. Вызов метода GetXml для DataSet, используемого в моих примерах, вернет вам строку с XML-пред ставлением этого объекта. Метод GetXmlSchema возвращает структуру данных (их схему), а метод GetXml — и и данные:

Вы также можете создать DataSet, считав его из XML-строки или файла методами и ReadXmlSchema. Например, сохранив структуру и данные DataSet в XML-файле методом WriteXml, вы сможете в дальней воссоздать DataSet из этого XML-файла методом ReadXml.

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

Джонни Папа Papa) — вице-президент компании MJM Investigations по информационным технологиям штат Северная Каролина). Автор нескольких книг по ADO, XML и SQL Server. Часто выступает на различных в том числе на VSLive. С ним можно связаться по адресу datapoints@1ancelotweb.com.

Разработка распределенных Операции над данными с иерархической структурой* В этой статье рассматриваются операции над иерархическими наборами строк с помощью Исходный код к этой статье можно скачать с сайта Online Code Center по ссылке Введение Эта статья демонстрирует методику чтения и записи иерархических набо ров строк в источнике данных. В примерах кода, приведенных в этой ста тье, для соединения с базой данных Microsoft SQL Server или Microsoft Desktop Engine (MSDE) используется управляемый провайдер SQL (SQL managed provider). Для соединения с другими ис точниками данных следует применять управляемый провайдер ADO (ADO managed provider).

Для доступа к иерархическим строкам, источником дан ных, в используются объекты DataReader и DataSet. Объект DataReader обеспечивает простой и быстрый доступ к данным только для чтения. С помощью этого объекта можно обращаться либо к иерархичес Building Distributed Applications with Data Operations on Row Library. 2002. February. - Прим.

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

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

Кроме того, SQL Server Data Provider позволяет получить XML-по ток напрямую от SQL Server 2000. Для этого предоставляется специальная API-функция, доступная через объект SQLCommand.

Метод ExecuteXml Reader SQL-запрос применительно к SQL соединению и на основе XML, возвращенного запросом, создает объект XmlReader. ExecuteXmlReader используется только в выражениях, резуль татом которых являются XML-данные, и эффективен в запросах, где в выражениях с SELECT присутствует блок FOR XML.

Операции чтения Иерархические строки часто структурированы как несколько наборов свя занных строк. Для большей эффективности зачастую лучше извлекать несколько наборов строк за одно обращение к базе данных, чем по отдель ности запрашивать каждый из них. Обычно для этого выполняется пакет SQL-выражений (batch of SQL expressions) или хранимая процедура с не сколькими выражениями SELECT. Кроме того, если вы используете в опе раторе SELECT блок FOR XML, SQL Server 2000 возвращает иерархичес кие строки в виде XML.

Применение ADO.NET-объекта DataReader В следующем примере исполняется пакет из двух SQL-выражений SE LECT и соответствующие наборы результатов (result sets) извлекаются через объект DataReader. Этот объект обеспечивает навигацию по данным в направлении только вперед, так что для дальнейшей работы данные ско рее всего следует перенести в другой контейнер. Так как DataReader остав ляет соединение с базой данных открытым, с точки зрения масштабируе мости данные надо переносить быстро и как можно быстрее закрывать соединение. Передавать объект DataReader между уровнями распределен ного приложения нельзя.

Для перебора строки в наборах результатов применяются методы Next Result и Read объекта DataReader.

328 Доступ к приложений Dim As Dim As As Integer Dim As String Try ' Подготовить объект команды, исполняющий хранимую процедуру, ' которая Orders и OrderDetails sqlCmd = New With sqlCmd = = - New End With ' Открыть соединение ' Выполнить команду;

результат передается в DataReader sqlDataRdr = ' Перебирать строки в первом наборе результатов Do While ' Здесь со строкой можно выполнить действия rptLine = = 0 То - rptLine = rptLine & If < - 1 Then rptLine = rptLine & End If Next End While Переместить указатель на следующий набор результатов If Not Then Exit Do End If Loop Catch E As Exception Finally Закрыть DataReader End Try Примечание См. 1» в исходном коде BDAdotNetData4.vb для скачивания указана в самом начале Применение и В Microsoft SQL Server 2000 встроена поддержка XML. Чтобы результаты выражений SELECT возвращались в виде XML, в этих выражениях нуж но указывать блок FOR XML. Метол ExecuteXmlReader объекта SQLCom mand позволяет получать XML-данные напрямую от SQL Server 2000.

Операции над данными с структурой ExecuteXmlReader возвращает объект содержащий XML-данные, полученные от SQL Server 2000.

В следующем примере хранимая использующая FOR XML, запускается вызовом метода ExecuteXmlReader объекта Dim sqlConn As Dim As Dim As XmlReader Try Создать новый объект соединения sqlConn = New SqlConnection(myConnString) Создать новый объект команды = New исполняемую With sqlCmd = = sqlConn End With Открыть соединение sqlConn.

' Выполнить команду и извлечь строку в DataReader xmlRdr = ' Перейти к корневому элементу ' Что-то делаем с данными ' к следующему элементу xmlRd Считать атрибут текущего элемента xmlRd Catch e As Exception ' Обработать исключение Finally End Примечание См. «Example 2» в исходном коде (ссылка для скачивания указана в самом начале статьи).

Применение DataSet Объект DataAdapter извлекает данные из источника и заполняет объекты DataTable внутри DataSet. Для выполнения запросов к базе данных объек ту DataAdapter требуется объект Connection.

330 Доступ к из Если запрос наборов результатов, DataSet сохраня ет каждый из них в отдельной таблице. Между таблицами может суще ствовать отношение (relationship).

Отношение между таблицами Как только вы связываете две таблицы в DataSet через отношение (с по мощью объекта навигация по ним упрощается. Кроме того, облегчает выборку всех дочерних строк (объектов DataRow) одной таблицы для родительской строки в другой таблице (объекте Data Table). Для выборки дочерних строк используется перегруженный метод объекта DataRow.

Отношения устанавливаются созданием объекта DataRelation, который строки одной таблицы со строками другой. Эти отношения хранятся в объекте который содержится в объек те DataSet.

Связывать таблицы в объекте DataSet не обязательно. Их можно оставить несвязанными. Однако, если между двумя таблицами существуют какие либо отношения, например через внешний ключ (foreign key relation), то связывание таблиц упростит доступ к дочерним строкам в одном объекте DataTable из родительской строки в другом объекте DataTable.

В следующем примере кода демонстрируется выборка заказов и их пози ций (order details) из таблиц Orders и OrderDetails. Объект DataSet содер жит таблицы Orders и Details, соответствующие таблицам в базе данных, Связью между двумя служит столбец присутствую щий в обоих объектах DataTables.

Dim As Dim As DataSet Dim As DataRow Dim As DataRow Dim As DataRow Dim As Integer Try ' Создать новый sqlDA = New ' Создать новый объект DataSet hierDS = New ' Задать сопоставления таблиц With sqlDA Добавить объект = New Операции над с иерархической структурой ' Указать команду объекта With.

=. = = New End With ' Заполнить DataSet данными "Orders") End With Так как до вызова метода Fill в объекте DataSet нет таблиц, объект автоматически создает таблицы для DataSet и заполняет их возвращаемыми данными. Если таблицы созданы до вызова Fill, объект SQL Adapter просто заполняет существующие таблицы.

' Указать первичный ключ таблиц = New { = New { ' Установить между двумя таблицами отношение внешний ключ ' Выбрать один заказ из таблицы = Выбрать соответствующие ему дочерние строки detailRows = ' Работа с дочерних строк i = 0 То - detailRow = Что-то делаем со строкой табпицы OrderDetails strDetail = & & & ", & _ & & & ", & Catch E As Exception ' Обработать исключение Finally Закрыть соединение и выполнить другую очистку End Примечание См. «Example 3» в исходном коде BDAdotNetData4.vb (ссылка для скачивания указана в самом начале статьи).

332 Доступ к из приложений Операции записи Фиксация (committing) изменений иерархических данных, содержащих несколько наборов результатов из двух или более связанных таблиц, тре бует сохранения целостности Например, ссылочная целостность означает, что внешний ключ в любой ссылающейся таблице (referencing table) должен указывать на существующую строку в на которую делается ссылка (referenced table). Следовательно, родительскую строку в этой таблице нельзя удалять до тех пор, пока на нее есть ссылка в другой таблице. Точно так же в ссылающуюся таблицу нельзя вставлять если нет соответствующих строк в таблице, на которую она ссылается.

Так как ADO.NET-объект DataSet позволяет извлекать, обрабатывать и модифицировать данные в он гарантирует ссылочную целостность таблиц при добавлении, изменении и удалении строк. Кроме того, этот объект позволяет выполнять каскадные обновления и удаления с сохране нием целостности данных.

Применение Метод Update объекта DataAdapter передает изменения, в объекте DataSet, источнику данных. Для добавления новых строк Data Adapter использует для изменения строк — а для удаления строк из базы данных — DeleteCommand. Когда вы вызываете метод Update, DataAdapter анализирует измененные строки и определяет, какой из объектов Command нужно выполнить для передачи отложенных изменений в каждой строке.

Прежде чем Update, вы должны настроить свойства InsertCom mand, или DeleteCommand — в зависимости от того, ка кие изменения были внесены в данные в DataSet. Например, если из DataSet удалялись строки, следует установить свойство DeleteCommand.

Для автоматического формирования команд Insert, Update и Delete мож но задействовать преимущества Command Builder. Если вы указы ваете DataAdapter-свойства UpdateCommand или Delete Command, метод Update соответственно выполняет команды insert, update или delete для каждой вставленной, обновленной или удаленной строки в DataSet. В ином случае — в зависимости от значения свойства SelectCommand объекта DataAdapter — генерирует SQL-коман ды, необходимые для внесения изменений в базу данных. Поэтому, чтобы CommandBuilder генерировал команды Insert, Update и Delete, вы долж ны соответственно настроить свойство SelectCommand.

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

Для свойств и объекта DataAdapter можно указывать параметризованные запросы или хранимые процедуры. Параметры параметризованных запросах или процедурах соответствуют столбцам в объекте DataTable. Таким образом, один объект DataAdapter поддерживает обновления только одной таблицы в вашей базе данных. Поэтому при обновлении базы данных для каждой таблицы в объекте DataSet потребуется отдельный объект DataAdapter.

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

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

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

Автоматическое формирование команды Insert Проблема с автоматически генерируемыми командами вставки в том, что объекту DataSet не возвращается ключ Id для столбца Identity.

Для решения этой проблемы мы используем хранимую которая возвращает первичный ключ для родительской строки, Тогда появляется возможность применять автоматически формируемые команды вставки для дочерних строк. В следующем примере мы создаем два объекта Data Adapter, заполняющие две таблицы в одном объекте DataSet. Мы задаем отношение между этими двумя таблицами и вставляем в них новые стро ки. Метод Update объекта DataAdapter, соответствующего родительской таблице (в нашем случае — Order), вызывается первым. Затем вызывает ся метод Update объекта DataAdapter, соответствующего дочерней це (в нашем случае — sqlConn As Dim As SqlDataAdapter Dim As SqlDataAdapter 334 Доступ к из приложений Dim As DataSet Dim As Dim As Dim As DataRow ' Создать новый = New ' Создать новый для таблицы Order = New ' Создать новый объект для таблицы = New ' новый DataSet hierDS = New ' Создать новый объект автоматической генерации ' выражений Update sqlCmdBldrDetail = New With sqlDAOrder ' Добавить объект SelectCommand = New Указать команду Select With = "Exec GetOrderHeader = sqlConn End With ' объект = New ' Указать команду Insert With = = = sqlConn ' Определить параметризованного Insert (New ' Задать свойство Direction = Задать свойство SourceColumn = (New ' Задать свойство Direction = ' Задать свойство = (New Задать свойство Direction = ' Задать SourceColumn над с иерархической структурой = "Orderld" End With ' Заполнить таблицу Orders данными "Orders") End With sqlDADetail Добавить объект = New Select With = = "Exec GetOrderDetails = sqlConn End With таблицу Details данными "Details") End With ' Установить между таблицами _ ' Создать новую строку для таблицы Orders = Указать каждого столбца в таблице Orders = = = ' Добавить строку к OataSet Синхронизировать с источником данных "Orders") Создать новую строку для таблицы Details detailRow = = = Добавить строку к DataSet ' Синхронизировать изменения с источником данных Catch e As Exception ' Обработать исключение Finally Выполнить очистку End Try Примечание См. «Example 4- в исходном коде для скачивания указана в самом начале статьи).

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

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

Чтобы указать собственное выражение INSERT, при вызове метода Update применительно к следует задать свойство InsertCommand. Значением этого свойства может быть параметризирован запрос или хранимая процедура. Параметры InsertCommand опреде ляются так же, как и параметры объекта Command. Управляемый провай дер SQL поддерживает именованные параметры.

Для каждого параметра нужно установить свойство SourceCoIumn. Оно сообщает объекту в каком столбце таблицы содержится зна чение параметра.

Dim As Dim As SqlDataAdapter Dim sqlDADetail As SqlDataAdapter Dim As DataSet Try ' Создать новое sqlConn = New ' новый объект SqlDataAdapter таблицы Order sqlDAOrder = New Создать новый объект SqlDataAdapter для таблицы sqlDADetail = New новый DataSet hierDS = New With ' Добавить объект. = New ' Указать команду Select With = "Exec = sqlConn End With Добавить объект = New SqlCommandO ' команду Insert Операции над с структурой With = = = sqlConn ' Задать параметры выражения Insert Установить Direction = (New Установить свойство Direction = ' Установить свойство = End With ' Заполнить DataSet возвращенными данными "Orders") End With With sqlDADetail ' Добавить объект = New ' команду Select для объекта sqlDADetail With = = "Exec GetOrderDetails = sqlConn End With Заполнить DataSet возвращенными данными "Details") End With Установить между двумя таблицами отношение внешний ключ _ ' Создать новую строку в таблице Orders = Задать значение каждого столбца в таблице Orders - = = ' Добавить строку к DataSet ' Создать новую строку в таблице Details detailRow = = = ' Добавить строку к DataSet 338 Доступ к данным из приложений ' Создать новую строку в Details = = = Добавить строку к DataSet Value = ' Синхронизировать изменения с данных Catch E As Exception ' Обработать исключения Finally ' Выполнить End Примечание См. «Example 5» в исходном коде (ссылка для скачивания указана в самом начале статьи).

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

Автоматическая генерация команды Update Изменения передаются источнику данных после обновления строки в таб лице объекта DataSet и вызова метода Update объекта DataAdapter. Пос ледний автоматически генерирует команду Update на основе предостав ленной вами команды Select.

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

Dim As Dim As Dim sqlDADetail As SqlDataAdapter Dim Dim As DataSet Try новое соединение sqlConn = New Создать новый SqlDataAdapter для таблицы Order sqlDAOrder = New ' Создать новый объект SqlDataAdapter для таблицы над данными с структурой sqlDADetail New • Создать новый DataSet = New ' новый объект автоматически формирующий ' команды Update = New With Добавить объект = New ' команду Select With = = "Exec = End With ' Заполнить DataSet возвращенными данными "Orders") End With With sqlDADetail • Добавить объект = New SqlCommandO ' Указать команду Select With = = "Exec = sqlConn End With ' DataSet возвращенными данными "Details") End With Установить между двумя таблицами отношение через внешний ключ _ _ = False ' Перенести одного в другой = = ' Синхронизировать изменения "Details") Catch E As Exception Обработать Finally Выполнить очистку End Try Примечание См. «Example 6» в исходном коде BDAdotNetData4.vb (ссылка для скачивания указана в самом начале статьи).

340 Доступ к данным из приложений Использование свойства Автоматически сформированная команда перемещала каждую строку за одно обращение к базе данных, а хранимая процедура могла бы перемес тить за одно обращение все строки. Чтобы указать собственное выражение Update, исполняемое при вызове метода Update применительно к Data Adapter, задайте свойство UpdateCommand. Его значением может быть запрос или хранимая процедура. Параметры Up dateCommand так же, как и параметры объекта Command.

Dim sqlConn As SqlConnection Dim As Dim sqlDADetail As SqlDataAdapter Dim hierDS As DataSet Try Создать sqlConn = New Создать новый объект SqlDataAdapter для таблицы Order sqlDAOrder New ' Создать новый объект для таблицы sqlDADetail = New ' DataSet hierDS = New With sqlDAOrder ' Добавить объект SelectCommand. = New Указать команду Select With = = "Exec = sqlConn End With ' Добавить объект UpdateCommand = New ' команду Update With = = ' Указать параметры = = = = = "Orderld" = = sqlConn над данными с структурой End With ' DataSet данными "Orders") End With With sqlDADetail ' Добавить объект New команду Select With = = "Exec = sqlConn End With Заполнить DataSet возвращенными данными "Details") End With Установить между двумя таблицами отношение внешний ключ = False ' Перенести позиции из одного заказа в другой = = Синхронизировать Catch E Exception Обработать исключение Finally Выполнить очистку End Try Примечание См. «Example 7» в исходном коде BDAdotNetData4.vb (ссылка для скачивания указана в самом начале Свойство позволяет передавать исходное и текущее значе ние в соответствующие параметры хранимой процедуры.

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

В объект DataSet поддерживает каскадное удаление, которое позволяет удалять дочерние строки одновременно с соответствующими родительскими строками. Чтобы определить действия, необходимые при 342 к данным приложений каких-либо изменениях строк в таблицах, вы должны указать внешнего ключа для двух таблиц в DataSet. Настройте метод DeletePro perty свойства на один из подходящих режимов ра боты (по умолчанию операции выполняются в каскадном режиме).

Dim sqlConn As Dim As Dim sqlDADetail As SqlDataAdapter Dim As DataSet Dim As Dim sqlCmdBldrDetail As Try ' Создать новый sqlConn = New ' Создать новый объект SqlDataAdapter для таблицы Order sqlDAOrder = New ' Создать новый объект SqlDataAdapter для таблицы OrderDetails sqlDADetail = New SqlDataAdapterO ' Создать новый объект DataSet hierDS = New ' объекты автоматически формирующие команды sqlCmdBldrOrder = sqlCmdBldrDetail = New SqlCommandBuilder(sqlDADetail) With sqlDAOrder Добавить объект = New ' команду Select With = = = sqlConn End With DataSet возвращенными данными "Orders") End With With sqlDADetail ' Добавить объект SelectCommand = New ' Указать команду Select для объекта sqlDADetail With = "Select * from OrderDetails" = sqlConn End With DataSet данными "Details") End With Установить связь Операции над с иерархической структурой ' Найти последнюю строку в таблице Orders = - 1) ' Удалить строку из таблицы В результате этого дочерние строки ' из таблицы Details в DataSet.

orderRow.

' Синхронизировать с источником данных "Details") "Orders") Catch E As Exception ' Обработать исключение Finally ' Выполнить очистку End Примечание См. 8» в исходном коде (ссылка для скачивания указана в самом начале статьи).

Для поддержания целостности данных дочерние строки должны удалять ся Удалить родительские строки до удаления соответствующих дочерних строк Поэтому обновление Detail для таблицы Details выполняется до обновления DataAdapter объекта Использование свойства DeleteCommand Чтобы указать собственное выражение Delete, выполняемое при вызове метода Update применительно к DataAdapter, используйте свойство Dele teCommand. Его значением является запрос или хранимая процедура. Параметры для DeleteCommand определяются так же, как и для объекта Command.

Для каждого параметра нужно задать свойство SourceColumn. Оно сооб DataAdapter, в каком столбце содержится значение параметра.

Dim As SqlConnection sqlDAOrder As SqlDataAdapter Dim sqlDaOetail As SqlDataAdapter Dim As DataSet Try ' Создать новый sqlConn = New новый объект SqlDataAdapter для таблицы Order sqlDAOrder = New ' Создать новый объект SqlDataAdapter для таблицы 344 к из приложений sqlDaDetail = New ' новый DataSet hierDS = New With Добавить объект SelectCommand = New SqlCommandO Указать команду Select With = = = End With ' Добавить объект = New SqlCommandO ' Задать свойства DeleteCommand With = = = sqlConn ' Определить параметры хранимой процедуры ' Настроить свойство = End with ' Заполнить DataSet данными "Orders") End With With sqlDaDetail Добавить SelectCommand = New SqlCommandO Указать команду Select With = = "Select * = sqlConn End With ' Заполнить DataSet данными End With ' Установить связь между таблицами Найти последнюю строку в таблице Orders orderRow = - 1) ' Удалить строку из таблицы Orders. В результате автоматически ' удаляются соответствующие дочерние строки таблицы ' Details в DataSet.

orderRow.

над данными с иерархической структурой Синхронизировать изменения с источником данных Catch E As Exception ' Обработать Finally ' Выполнить очистку End Примечание См. «Example 9» в исходном коде для скачивания указана в самом начале статьи).

Заключение При доступе к данным только для чтения объекты DataReader и Reader просты в использовании и работают быстро, хотя соединение с ба зой данных поддерживается до тех пор, пока приложение читает данные.

Если приложение достаточно долго держит эти объекты, может возник нуть конкуренция (contention), что ограничит масштабируемость такого приложения.

Объект DataSet представляет отсоединенный реляционный кэш, а также навигацию по иерархическим данным и их модификацию. Кас кадная запись облегчает фиксацию изменений базе данных, но автома тически генерируемые выражения Insert, Update и Delete менее эффектив ны по сравнению с теми, которые пишутся вручную, — особенно с точки зрения уменьшения частоты обращений к базе данных и кэширования зап росов. Сокращение числа обращений к базе данных станет еще актуальнее, когда XML-средства SQL Server 2000 будут полнее использовать возмож ности ADO.NET.

Джонни Папа Доступ к данным Модификация приложения для отображения данных Автор как создать универсальный инструмент для отображения данных в Web с поддержкой управления страницами и ких других функций на основе ADO. Все это может быть реализовано в среде или Microsoft Transaction От однообразия тупеешь. Меня раздражает без конца писать одно и то же, зная что код можно было бы использовать повторно. Например, на Web-сайтах нужно отображать результаты поиска, данные отчетов и дру гие списки. Хорошо бы упростить разработку всех этих страниц! Сегодня я покажу, как ускорить создание Web-страниц для отображения данных с использованием Visual Basic, и JScript. Так что сядьте в крес ло и откиньтесь - мы начинаем.

В декабрьском издании «Microsoft Internet за 1998 г. Чарльз Кейсон (Charles Caison) и я рассказали о методике отображения любых данных SQL на Web-странице. Читатели просили меня обновить код с учетом более современных технологий создания масштабируемых ративных (Да, я слышал.) Поэтому в своей первой ста в Magazine» я опишу, как создать универсальный инструмент для отображения данных в Web с поддержкой сортировки, управления страницами и нескольких других, новых функций. Все это реализуется в среде или Microsoft Transaction Services (MTS).

* Публиковалось в Magazine. 2001. (июнь). — Прим. изд.

Модификация приложения для данных в Web Начнем с результатов — о коде поговорим потом (его, кстати, можно ска чать с сайта Magazine по ссылке http://msdn.microsoft.com/msdn в разделе за июнь). Мое решение рассчитано на поддерж ку разных браузеров и работает с последними версиями Internet Explorer и Netscape Navigator.

Страница На рис. 1 показан пример универсальной Web-страницы, использующей управления страницами (paging) и сортировки, а также многие средства и DHTML. Обратите внимание: на каждой стра нице отображается лишь пять записей. Пользователи могут листать стра ницы при помощи ссылок Previous и Next или вводом номера страницы в поле со списком, которое находится в верхнем правом углу страницы.

Функция управления страницами позволяет настраивать число единовре менно отображаемых записей и перемещаться между страницами в любом направлении. Так, если на первой странице с данными (рис. 1) пользова тель щелкнет ссылку Next, браузер выведет вторую страницу (рис. 2).

Authors Display О Michel Gary Arbor Управление Рис. 1. Отображение данных На рис. 1 показаны первые пять записей из отчета, на рис. 2 — следующие пять. Можно перейти на определенную страницу, выбрав ее номер из спис ка Go to page (рис. 3).

Число записей на одной странице указывается пользователем в текстовом поле Records per page. Чтобы изменить его, введите новое число и щелк ните ссылку Refresh в верхнем левом углу страницы. Это приводит к из менению числа записей на странице;

кроме того, запоминается текущий к данным из Рис. 2. Вторая страница Рис. З. Переход по номеру страницы номер отображаемой страницы данных. Например, пользователь находил ся на второй странице отчета из 25 записей, просматривая по пять запи сей. Затем он увеличил число записей на одной странице с пяти до семи и щелкнул Refresh. Теперь на каждой странице отображается по семь запи приложения для отображения в Web сей (рис. и весь отчет занимает четыре страницы, но пользователь по прежнему находится на второй отчета. Однако, если пользова тель увеличивает число записей при просмотре последней страницы (ска жем, пятой из пяти), то отображается уже не пятая, а последняя страница отчета. Но вы, как разработчик, можете изменить эту схему в соответствии с тем, что вам.

3 кипи Purr Hunter Alto CA San Francisco СЙ Рис. 4. Семь записей на странице Сортировка данных Механизм сортировки ADO позволяет упорядочивать информацию по возрастанию, когда пользователь щелкает гиперссылку заголовка колонки с данными. Повторный щелчок упорядочивает данные по убыванию. На пример, на рис. 5 информация отсортирована по фамилиям авторов в ал фавитном порядке по убыванию.

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

Например, если на рис. 6 щелкнуть кнопку Edit в записи Akiko Yokomoto, появится сообщение, показывающее первичный ключ этой записи. Други к данным из приложений ми словами, вы можете извлечь первичный ключ записи и передать его в другое окно для отображения или изменения записи и т. д.

Authors Display Рис. 5. Сортировка по фамилиям Authors Рис. 6. Получение первичного ключа Модификация для отображения данных в Web Помните, что все эти функции адаптируемы. А когда вы увидели, что позволяет делать моя программа, разберемся, как она работает.

За кулисами У кода многоуровневая архитектура: база данных (я использовал Pubs из комплекта поставки Microsoft SQL Server 7.0 и SQL Server 2000), бизнес объекты (ActiveX-сервер на Visual Basic под MTS), ASP- и клиентский код (рис. 7). ASP- и HTML-код, а также сценарии помещены в одну Web-стра ницу. Но логически разделяют презентационные и бизнес сервисы. (В ASP.NET подход несколько иной, но это тема для отдельного t Интернет/LAN к данных Рис. 7. архитектура ASP- и клиентский код Рассмотрим основные компоненты приложения, начиная с верхнего уров ня (ASP/HTML). Проект состоит из файлов Generic GenericDisplayToolPart2of3.asp, и Styles.css. Файл — Web-страница, где пользователи вводят URL для запуска программы.

Доступ к из Это единственная страница приложения, требующая настройки. (Осталь ные страницы универсальны, и их можно использовать повторно для лю бых наборов данных без всяких изменений.) Как же ее настроить? Для этого укажите заголовок страницы и картинки, которые надо выводить в каждой строке, а также определите обработчики событий, инициируемых щелчком этих картинок. Потом напишите код для выборки данных из сво его Базовые настройки для заголовка и картинок показаны на рис. 8. При щелчке первой картинки вызывается клиентская функция Edit, а при щел чке второй — функция Browse. Создайте код для этих функций и включи те в страницу. Пример таких функций приведен на рис. 9. В данном слу чае выводится сообщение с указанием первичного ключа выбранной запи си. Значение первичного ключа устанавливается автоматически другими страницами, о чем я расскажу чуть позже. Просто замените мой код для функций Edit и Browse на нужный вам. А изменив код в обработчике со бытия onclick (рис. 8), вы сможете вызывать любую другую клиентскую функцию.

Рис, 8. Настройка картинок '- Заголовок страницы sTitle = "Authors Display" для = при щелчке в строке = *- Картинка в = - щелчке в строке '- Картинка в строке = Рис. Клиентские щелчков в строке

© 2011 www.dissers.ru - «Бесплатная электронная библиотека»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.