Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Framework »

Обсудить на форуме Обсудить на форуме

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

Ссылки на смежную тематику:

Анджела Крокер (Angela Crocker), Энди Олсен (Andy Olsen) и Эдвард Джезирски (Edward Jezierski)
Microsoft Corporation

Август 2002 г.

Относится к:
приложениям Microsoft® .NET

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

Данный документ в формате PDF можно скачать по ссылке Download Designing Data Tier Components and Passing Data Through Tiers.

Содержание

Введение

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

На рис. 1 показана типичная структура распределенного приложения. Мы проводим различие между бизнес-данными и бизнес-процессами, которые с ними работают; уровень бизнес-процессов рассматривается, только когда требуется внести ясность. Аналогичным образом презентационный уровень рассматривается, только когда он имеет прямое отношение к способу представления данных, например при доступе к бизнес-данным с помощью Web-страниц Microsoft® ASP.NET. На рис. 1 вводятся два новых понятия: компоненты Data Access Logic (DAL-компоненты, компоненты логики доступа к данным) и компоненты бизнес-объектов. Об этих понятиях мы расскажем в дальнейшем.

Presentation Tier - Презентационный уровень
Business Process Tier - Уровень бизнес-процесса
Data Tier - Уровень данных
Business Process Components - Компоненты бизнес-процессов
Business Entites - Бизнес-объекты
Data Access Logic Components - DAL-компоненты
Application Data - Данные приложения

Рис. 1. Доступ к данным и представление данных в распределенном приложении

В большинстве приложений данные хранятся в реляционных базах данных. Хотя имеются другие способы хранения данных, мы уделим основное внимание взаимодействию .NET-приложений с реляционными базами данных; вопросы взаимодействия с данными, получаемыми из других источников, например из плоских (flat) файлов или нереляционных баз данных, не рассматриваются.

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

  • Использование отдельных компонентов доступа к данным позволяет добиться независимости приложения от параметров базы данных, таких как имя источника данных, информация о соединении и имена полей.
  • Многие современные приложения имеют слабосвязанную архитектуру и используют технологии обмена сообщениями, например, MSMQ (Microsoft Message Queuing) и Web-сервисы XML. Такие приложения обычно взаимодействуют, обмениваясь бизнес-документами, а не объектами.

Примечание: Для знакомства с Web-сервисами XML см. статью .NET Web Services: Web Methods Make it Easy to Publish Your App´s Interface over the Internet в номере MSDN® Magazine за март 2002 г. Дополнительную информацию о Message Queuing см. в Message Queuing Overview.

Чтобы отделить логику доступа к данным от самих данных, мы предлагаем использовать два типа компонентов.

  • Компоненты Data Access Logic (DAL-компоненты). DAL-компоненты считывают информацию из базы данных и сохраняют в базе данных информацию бизнес-объекта. Кроме того, в DAL-компонентах содержится вся бизнес-логика, необходимая для выполнения операций над данными.
  • Компоненты бизнес-объектов (business entity components). Данные используются для представления сущностей предметной области, например товаров или заказов. Эти сущности можно представить в приложении разными способами, например, с помощью XML, объектов DataSet или пользовательских объектно-ориентированных классов, - все зависит от ограничений физической или логической инфраструктуры приложения. Далее в документе подробно исследуются различные способы проектирования этих компонентов.

DAL-компоненты

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

  • создавать записи в базе данных;
  • читать записи из базы данных и возвращать данные бизнес-объектов вызвавшему компоненту;
  • обновлять записи базы данных измененными данными бизнес-объектов, переданными вызвавшим компонентом;
  • удалять записи базы данных.

Методы, решающие перечисленные выше задачи, часто называют CRUD-методами (create, read, update, delete).

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

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

Представление бизнес-объектов

Каждый DAL-компонент работает с определенным типом бизнес-объектов. Например, DAL-компонент Customer работает с бизнес-объектом Customer (покупатель). Существует много разных способов представления бизнес-объектов. Выбор способа определяется следующими факторами.

  • Требуется ли связывать данные бизнес-объекта с элементами управления формы в Microsoft Windows® или на странице ASP.NET?
  • Нужно ли выполнять сортировку или поиск данных бизнес-объекта?
  • Работает ли приложение с одним бизнес-объектом единовременно или обычно оно имеет дело с наборами бизнес-объектов?
  • Как развертывается приложение - локально или удаленно?
  • Будут ли работать с бизнес-объектом Web-сервисы XML?
  • Насколько важны нефункциональные требования, такие как производительность, масштабируемость, удобство поддержки и удобство программирования?

Мы рассмотрим преимущества и недостатки следующих вариантов реализации.

  • XML. Для представления данных бизнес-объекта используется XML-строка или DOM-объект (XML Document Object Model). XML - открытый и гибкий формат представления данных, применяемый при интеграции различных типов приложений.
  • DataSet. Объект DataSet - это размещаемый в памяти кэш таблиц, считываемых из реляционной базы данных или XML-документа. В компоненте логики доступа к данным можно использовать DataSet для представления информации бизнес-объекта, считываемой из базы данных, и обращаться к этому DataSet из приложения. Для знакомства с объектами DataSet см. раздел "Introducing ADO.NET" в .NET Data Access Architecture Guide.
  • Типизированный DataSet. Это класс, унаследованный от класса DataSet ADO.NET и содержащий строго типизированные методы, события и свойства, которые позволяют обращаться к таблицам DataSet и их полям.
  • Компонент бизнес-объекта. Это пользовательский класс, используемый для представления каждого типа бизнес-объектов, с которыми работает приложение. В этом классе определяются поля для хранения данных бизнес-объекта и свойства, обеспечивающие доступ клиентского приложения к этим данным. Кроме того, в компоненте бизнес-объекта содержатся методы, работающие с полями компонента и инкапсулирующие простую бизнес-логику. При использовании этого варианта CRUD-методы, выступающие в роли промежуточного звена и обращающиеся к DAL-компоненту, не разрабатываются; для выполнения CRUD-операций клиентское приложение напрямую взаимодействует с DAL-компонентом.
  • Компонент бизнес-объекта, поддерживающий CRUD-методы. Как и в предыдущем варианте, определяется пользовательский класс бизнес-объекта. Но в этом классе реализуются CRUD-методы, которые обращаются к DAL-компоненту, связанному с этим бизнес-объектом.

    Примечание: Если вы предпочитаете использовать при работе с данными более объектно-ориентированный подход, то можно выбрать еще один вариант: определить уровень хранения объектов, основанный на функциональности отражения (reflection), предоставляемой общеязыковой исполняющей средой (CLR). Можно создать инфраструктуру, применяющую отражение для чтения свойств объектов и использующую файл сопоставления (mapping file) для описания связей объектов и таблиц. Однако эффективная реализация такой инфраструктуры потребует массы усилий на написание кода. Эти издержки приемлемы для независимых поставщиков программного обеспечения (independent software vendors, ISV) или поставщиков решений (solution providers), но большинству организаций они не по силам. Поэтому мы не будем рассматривать такой подход.

Технические соображения

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

How do I represent a single instance? - В каком виде представить один экземпляр?
Business Entity - Бизнес-объект
Business Entity´s state - Состояние бизнес-объекта
How do I represent a collection? - В каком виде представить один набор?
How do I represent a hierarchy? - В каком виде представить одну иерархию?
What is my data format to data access logic components? - Какой формат данных использовать в DAL-компонентах?
Subset of business entity´s state - Подмножество состояний бизнес-объекта
Data Access Logic Component - DAL-компонент
How do I abstract database schema? - Какое абстрагировать схему базы данных?
Application Data - Данные приложения
When to use stored procedures? - Когда использовать хранимые процедуры?
When not to? - И когда их не использовать?

Рис. 2. Технические факторы, которые следует учитывать при проектировании DAL-компонентов и бизнес-объектов

Связывание реляционных данных с бизнес-объектами

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

Рассмотрим гипотетическую базу данных розничного магазина, показанную на рис. 3.

Рис. 3. Отношения между таблицами в гипотетической реляционной базе данных

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

 

Тип отношения Пример Описание
Один-ко-многим Покупатель:
Адрес

Покупатель:
Заказ

У покупателя может быть несколько адресов, например, адрес доставки, адрес предъявления счета и контактный адрес.

Покупатель может сделать несколько заказов.

Многие-ко-многим Заказ:
Товар
В одном заказе можно указать несколько товаров; данные о каждом товаре хранятся в отдельной записи таблицы OrderDetails. С другой стороны, один и тот же товар может присутствовать в нескольких заказах.

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

  • получение (или обновление) информации о покупателе, в частности, о его адресах;
  • получения списка заказов данного покупателя;
  • получение списка позиций данного заказа;
  • ввод нового заказа;
  • получение (или обновление) информации о товаре или группе товаров.

Чтобы приложение могло решать эти задачи, оно должно работать с тремя логическими бизнес-объектами: Customer (покупатель), Order (заказ) и Product (товар). Для каждого бизнес-объекта определяется свой DAL-компонент:

  • Customer - класс, считывающий и обновляющий данные таблиц Customer и Address;
  • Order - класс, считывающий и обновляющий данные таблиц Order и OrderDetails;
  • Product - класс, считывающий и обновляющий данные таблицы Product.

На рис. 4 показаны отношения между DAL-компонентами и таблицами базы данных, которые представляют эти компоненты.

Customer Data Access Logic Component - DAL-компонент Customer
Order Data Access Logic Component - DAL-компонент Order
Product Data Access Logic Component - DAL-компонент Product
Customer table - Таблица Customer
Address Table - Таблица Address
Order Table - Таблица Order
OrderDetails Table - Таблица OrderDetails
Product Table - Таблица Product

Рис. 4. Определение DAL-компонентов, предоставляющих .NET-приложениям доступ к реляционным данным

О том, как реализовать DAL-компоненты, см. далее в разделе Реализация DAL-компонентов.

Рекомендации по связыванию реляционных данных с бизнес-объектами

При связывании реляционных данных с бизнес-объектами примите во внимание следующие рекомендации.

  • Вместо того чтобы определять по одному бизнес-объекту для каждой таблицы, потратьте некоторое время на анализ и моделирование логических бизнес-объектов приложения. Один из способов моделирования работы приложения - использование UML (Unified Modeling Language). UML - формальная нотация проектирования, применяемая при моделировании объектов в объектно-ориентированных приложениях и при сборе информации о том, как с помощью объектов представить автоматизированные процессы, их взаимодействие с людьми и различные связи. Дополнительную информацию см. в Modeling Your Application and Data.
  • Не определяйте раздельные бизнес-объекты, чтобы представить таблицы, связанные отношением "многие-ко-многим"; с такими отношениями можно работать, обращаясь к методам, реализованным в DAL-компоненте. Например, таблица OrderDetails из предыдущего примера не связывается с отдельным бизнес-объектом; вместо этого используется DAL-компонент Orders, инкапсулирующий операции с таблицей OrderDetails, чтобы реализовать отношение "многие-ко-многим" между таблицами Order и Product.
  • Если у вас есть методы, возвращающие определенный тип бизнес-объекта, помещайте их в DAL-компонент для этого типа. Например, если требуется получить информацию обо всех заказах данного клиента, реализуйте эту функцию в DAL-компоненте Order, так как возвращаемое значение имеет тип Order. А при выборке информации обо всех клиентах, заказавших данный продукт, реализуйте эту функцию в DAL-компоненте Customer.
  • DAL-компоненты обычно обращаются к информации из одного источника данных. Если необходимо объединить данные из нескольких источников, рекомендуется определить отдельные DAL-компоненты для доступа к каждому источнику данных. Эти DAL-компоненты будут вызываться высокоуровневыми компонентом бизнес-процесса, который при необходимости объединит данные. Имеется две причины, чтобы так поступить:
    • управление транзакциями выполняется централизованно компонентом бизнес-процесса, и не требуется явно управлять транзакциями из DAL-компонента. Если обращаться к нескольким источникам данных из одного DAL-компонента, то DAL-компонент должен стать корнем транзакции, что приведет к дополнительным издержкам при разработке функций, которые всего лишь считывают данные;
    • объединение данных обычно не является необходимым во всех областях применения приложения, и реализация отдельных компонентов доступа к данным позволяет использовать типы по отдельности, а при необходимости объединять их с данными из других источников.

Реализация DAL-компонентов

DAL-компонент - это класс, не поддерживающий состояния (stateless class), т. е. все сообщения, принимаемые этим классом, обрабатываются независимо друг от друга. В промежутке между вызовами состояние не запоминается. DAL-компонент содержит методы обращения к одной таблице или нескольким связанным таблицам одной базы данных либо - в некоторых случаях [например, при горизонтальном разделении базы данных (horizontal database partitioning)] - к нескольким базам данных. Обычно методы DAL-компонентов для выполнения своих функций вызывают хранимые процедуры.

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

  • управление схемами блокировки и их инкапсуляция;
  • поддержку защиты и авторизации;
  • поддержку транзакций;
  • разбиение данных на страницы;
  • маршрутизацию в зависимости от данных (при необходимости);
  • реализацию стратегии кэширования для запросов нетранзакционных данных (при необходимости);
  • работу с данными как с потоками и сериализацию данных.

Некоторые из перечисленных выше задач мы подробно рассмотрим в этом разделе.

Сценарии применения DAL-компонентов

На рис. 5 показано, как DAL-компоненты могут вызываться различными типами приложений: приложениями Windows Forms, ASP.NET, Web-сервисами XML и бизнес-процессами. В зависимости от того, как развертывалось приложение, могут выполняться локальные или удаленные вызовы.

External Callers - Внешние вызывающие
Windows Forms - Windows Forms
ASP.NET - ASP.NET
XML Web Service - Web-сервис XML
Remote Call - Удаленный вызов
Local Call - Локальный вызов
Remote or Local Call - Локальный или удаленный вызов
Business entity data - Данные бизнес-объекта
Business process workflows, .NET components, BizTalk Orchestration - Бизнес-процессы, .NET-компоненты, BizTalk Orchestration
CRUD and other data logic functions - CRUD и другие функции логики доступа к данным
Data Access Logic Components - DAL-компоненты
Database - База данных

Рис. 5. Сценарии применения DAL-компонентов (щелкните микрокартинку, чтобы увеличить изображение)

Реализация классов DAL-компонентов

DAL-компоненты для выполнения SQL-операторов и вызова хранимых процедур обращаются к ADO.NET. Пример класса DAL-компонента см. в приложении в разделе Определение класса DAL-компонента.

Если в приложении несколько DAL-компонентов, можно упростить реализацию DAL-компонента, применив вспомогательный компонент доступа к данным (data access helper component). Этот компонент служит для управления соединениями с базой данных, выполнения SQL-команд и кэширования параметров. DAL-компоненты, как и раньше, инкапсулируют логику доступа к определенным бизнес-данным, тогда как во вспомогательном компоненте доступа сосредоточены API доступа к данным и средства настройки подключения к данным, что уменьшает дублирование исходного кода. Microsoft поставляет Data Access Application Block for .NET, который выступает в роли универсального вспомогательного компонента доступа к данным в приложениях, работающих с базами данных Microsoft SQL Server™. На рис. 6 показано, как вспомогательный компонент доступа к данным упрощает реализацию DAL-компонентов.

Data Access Logic Component - DAL-компонент
Data Access Helper Component - Вспомогательный компонент доступа к данным

Рис. 6. Реализация DAL-компонентов с применением вспомогательного компонента доступа к данным

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

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

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

  • Передача данных бизнес-объекта методам DAL-компонента. Можно передавать данные в различных форматах: как последовательности скалярных значений, как XML-строки, как объекты DataSet или как пользовательский компонент бизнес-объектов.
  • Возвращение данных бизнес-объекта методами DAL-компонента. Данные также можно возвращать в нескольких разных форматах: как скалярные значения, являющиеся выходными параметрами, как XML-строки, как объекты DataSet, как пользовательский компонент бизнес-объектов или как классы чтения.

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

Передача входных и выходных данных как скалярных значений

Такой подход дает следующие преимущества.

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

А недостатки этого подхода таковы.

  • Жесткое сопряжение (tight coupling) и неудобство поддержки. При изменениях в схеме приходится изменять сигнатуры методов, что влияет на вызывающий код.
  • Наборы бизнес-объектов. Чтобы сохранить или обновить в DAL-компоненте несколько объектов, приходится вызывать методы по отдельности. При работе в распределенной среде это может значительно снизить производительность.
  • Поддержка параллельного доступа с нежесткой блокировкой (optimistic concurrency). Для поддержки параллельного доступа с нежесткой блокировкой нужно определить в базе данных поля временных меток, значения которых передаются вместе с данными.

Передача входных и выходных данных как XML-строк

Такой подход имеет следующие преимущества.

  • Свободное сопряжение (loose coupling). Вызывающие должны знать только о том, какими данными описывается бизнес-объект, и иметь доступ к схеме, содержащей метаданные бизнес-объекта.
  • Интеграция. При передаче данных в формате XML поддерживаются вызывающие, реализованные разными способами, например, как .NET-приложения, как правила BizTalk Orchestration или механизмы поддержки правил, разработанные сторонними производителями
  • Наборы бизнес-объектов. В XML-строке могут содержаться данные нескольких бизнес-объектов.
  • Сериализация. Для строк обеспечивается встроенная поддержка сериализации.

Однако у этого подхода есть ряд недостатков.

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

Передача входных и выходных данных как объектов DataSet

При таком подходе обеспечиваются следующие преимущества.

  • Встроенная функциональность. Объекты DataSet сами поддерживают управление параллельным доступом с нежесткой блокировкой (при использовании с адаптерами данных) и сложные структуры данных. Кроме того, типизированные объекты DataSet поддерживают проверку данных на допустимость.
  • Наборы бизнес-объектов. DataSet предназначены для работы с наборами данных и сложными отношениями между данными, поэтому для реализации этих возможностей не нужно писать свой код.
  • Сопровождение. Изменения в схеме не влияют на сигнатуры методов. Однако при использовании типизированных DataSet и указании строгого имени сборки необходимо перекомпилировать класс DAL-компонента для работы с новой версией, использовать политику издателя в кэше глобальных сборок или определить элемент <bindingRedirect> в файле конфигурации. О том, как исполняющая среда ищет сборки, см. в How the Runtime Locates Assemblies.
  • Сериализация. В объекте DataSet реализована встроенная поддержка сериализации в формате XML. Сериализованное представление этих объектов можно передавать между уровнями.

Недостатки этого подхода.

  • Производительность. Создание экземпляров и маршалинг объектов DataSet ведет к издержкам в период выполнения.
  • Представление одного бизнес-объекта. Объекты DataSet предназначены для работы с наборами данных. Если приложение в основном работает с данными экземпляров, скалярные значения или пользовательские объекты являются более подходящим вариантом, так как не оказывают негативного влияния на производительность.

Передача входных и выходных данных как пользовательских компонентов бизнес-объектов

При таком подходе вы получаете следующие преимущества.

  • Сопровождение. При изменениях в схеме сигнатуры методов DAL-компонента могут не измениться. Однако если бизнес-компонент помещен в сборку со строгим именем, возникают те же проблемы, что и в случае типизированных объектов DataSet.
  • Наборы бизнес-объектов. Массив или набор пользовательских бизнес-объектов может быть входным или выходным параметром методов.

Но у этого подхода есть свои недостатки.

  • Поддержка параллельного доступа с нежесткой блокировкой. Для поддержки такого доступа нужно определить в базе данных поля временных меток, значения которых передаются вместе с данными.
  • Ограниченность интеграции. Когда пользовательский компонент бизнес-объекта является входным параметром метода DAL-компонента, вызывающий должен знать тип бизнес-объекта; это ограничивает интеграцию вызывающего кода, который не используют .NET. Однако эта проблема не обязательно приводит к ограничению интеграции, когда вызывающий код работает с бизнес-объектом как с данными, возвращаемыми DAL-компонентом. Например, Web-метод может возвращать пользовательский компонент бизнес-объекта, который в свою очередь возвращается DAL-компонентом, - тогда компонент бизнес-объекта автоматически сериализуется в формат XML при XML-сериализации.

Возвращение выходных данных в классах чтения

Это дает следующее преимущество.

  • Производительность. Достигается выигрыш в производительности, когда требуется быстро отобразить данные и можно развернуть DAL-компонент в коде презентационного уровня.

А недостаток таков.

  • Удаленное взаимодействие (remoting). Классы чтения данных не рекомендуется использовать, когда применяется удаленное взаимодействие, так как возможно, что клиентскому приложению в течение длительного времени придется поддерживать открытое соединение с базой данных.

Использование хранимых процедур, взаимодействующих с DAL-компонентами

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

Преимущества

  • Использование хранимых процедур обычно повышает производительность, так как база данных может оптимизировать план доступа к данным, используемым процедурой, и кэшировать этот план для последующего повторного использования.
  • Для хранимых процедур можно устанавливать индивидуальную защиту на уровне базы данных. Администратор может разрешить клиентам выполнять хранимую процедуру, не предоставляя никаких разрешений на доступ к таблицам, с которыми она работает.
  • Хранимые процедуры облегчают сопровождение приложения, так как обычно проще изменить хранимую процедуру, чем изменить "зашитый" SQL-оператор в развернутом компоненте. Однако чем больше бизнес-логики реализуется в хранимых процедурах, тем менее ощутимым становится это преимущество.
  • Хранимые процедуры - дополнительный уровень абстракции, реализуемый над схемой базы данных. Клиент хранимой процедуры изолируется от деталей реализации хранимой процедуры и схемы базы данных.
  • Использование хранимых процедур сокращает сетевой трафик. SQL-операторы могут выполняться пакетами, благодаря чему приложению не приходится отправлять многочисленные SQL-запросы.

Несмотря на перечисленные выше преимущества, в некоторых случаях применение хранимых процедур не рекомендуется или является не лучшим решением.

Недостатки

  • Приложения, в которых реализованы сложные бизнес-логика и обработка данных, могут создать чрезмерную нагрузку на сервер, если вся эта логика реализована в хранимых процедурах. Примерами таких видов обработки являются передача данных, обход деревьев или графов, преобразования данных или интенсивные вычисления. Следует переносить такую обработку данных на уровень бизнес-процесса или в DAL-компоненты, которые являются более масштабируемым ресурсом, чем сервер баз данных.
  • Не помещайте всю бизнес-логику в хранимые процедуры. Когда приходится изменять бизнес-логику, реализованную на T-SQL, возникают проблемы поддержки и обновления приложения. Например, в ISV-приложениях, поддерживающих несколько реляционных СУБД, не следует размещать свои хранимые процедуры для каждой СУБД.
  • Написание и сопровождение хранимых процедур часто требует специальных навыков, которыми владеют не все разработчики. Из-за этого могут возникнуть "узкие места", приводящие к срыву сроков разработки проекта.

Рекомендации по использованию хранимых процедур, взаимодействующих с DAL-компонентами

При использовании хранимых процедур, взаимодействующих с DAL-компонентами, примите во внимание следующее.

  • Предоставление доступа к хранимым процедурам. Желательно, чтобы DAL-компоненты были единственными компонентами, имеющими доступ к информации о схеме базы данных, такой как хранимые процедуры, параметры, таблицы и поля таблиц. Реализации бизнес-объектов не должны что-либо знать о схеме базы данных или как-то от нее зависеть.
  • Связывание хранимых процедур с DAL-компонентами. Каждая хранимая процедура должна вызываться только из одного DAL-компонента, и ее следует связать с DAL-компонентом, выполняющим операцию. Допустим, покупатель размещает заказ в магазине. Можно написать хранимую процедуру OrderInsert, заносящую информацию о заказе в базу данных. При разработке приложения необходимо решить, откуда вызывать хранимую процедуру: из DAL-компонента Customer или из DAL-компонента Order. Более правильным выбором является DAL-компонент Order, так как он управляет всей обработкой данных, связанных с заказами (DAL-компонент Customer работает с информацией о клиенте, например с именами или адресами клиентов).
  • Именование хранимых процедур. При разработке хранимых процедур, используемых DAL-компонентами, выбирайте их имена так, чтобы из них было понятно, к каким DAL-компонентам относятся процедуры. Такая схема именования позволяет легко идентифицировать хранимые процедуры и логически группировать хранимые процедуры в SQL Enterprise Manager. Например, можно сначала написать хранимые процедуры CustomerInsert, CustomerUpdate, CustomerGetByCustomerID и CustomerDelete, используемые DAL-компонентом Customer, а затем написать более специализированные хранимые процедуры, поддерживающие бизнес-функции приложения, например процедуру CustomerGetAllInRegion.

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

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

Управление блокировками и параллельным доступом

В некоторых приложениях при обновлении информации в базе данных используется подход "последний побеждает" ("Last in Wins"). При таком подходе, когда обновляется база данных, не затрачивается усилий на сравнение обновлений с исходной записью, что может привести к перезаписи изменений, которые с момента последней актуализации успели внести другие пользователи. Однако в ряде случаев для нормальной работы приложения перед обновлением данных важно определить, изменялись ли данные после их первоначального чтения.

DAL-компоненты содержат код управления блокировками и параллельным доступом. Имеется два способа управления блокировками и параллельным доступом.

  • Параллельный доступ с жесткой блокировкой (pessimistic concurrency). Пользователь, который считывает запись и собирается ее изменять, блокирует эту запись в источнике данных. Никто другой не может изменить запись, пока пользователь не снимет блокировку.
  • Параллельный доступ с нежесткой блокировкой (optimistic concurrency). Пользователь не блокирует запись при считывании. Тем временем другие пользователи также могут обращаться к записи. Когда пользователь собирается обновить запись, приложение должно установить, не изменял ли эту запись другой пользователь с момента ее считывания. При попытке обновить уже измененную запись, возникает конфликт параллельного доступа.

Параллельный доступ с жесткой блокировкой

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

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

Параллельный доступ с нежесткой блокировкой

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

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

Выявление конфликтов параллельного доступа с нежесткой блокировкой

Существует несколько способов определения конфликтов параллельного доступа с нежесткой блокировкой.

  • Использование распределенных временных меток. Распределенные временные метки - подходящий вариант, когда не требуется синхронизация данных. Поле временной метки или версии добавляется в каждую таблицу базы данных и возвращается при каждом запросе содержимого таблицы. При попытке обновления значение временной метки в базе данных сравнивается со значением временной метки, хранящимся в измененной записи. Если значения совпадают, то выполняется обновление и в поле временной метки записывается текущее времея, чтобы запомнить момент обновления. Если значения не совпадают, значит, возник конфликт параллельного доступа с нежесткой блокировкой.
  • Хранение копии исходных значений данных. При запросе данных из базы сохраняется копия исходных значений данных. При обновлении базы данных проверяется, соответствуют ли текущие значения, хранящиеся в базе данных, исходным.
  • Объекты DataSet содержат исходные значения, которые могут использоваться адаптером данных для выявления конфликтов параллельного доступа с нежесткой блокировкой при обновлении базы данных.
  • Использование централизованных временных меток. В базе данных заводится централизованная таблица временных меток, в которой отслеживаются все изменения всех записей всех таблиц. Например, в таблице временных меток может содержаться следующая информация: "Запись с идентификатором 1234 в таблице XYZ обновлена пользователем John 26 марта 2002 в 14:56".

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

Реализация вручную параллельного доступа с нежесткой блокировкой

Рассмотрим следующий SQL-запрос:

SELECT Column1, Column2, Column3 FROM Table1

Чтобы проверить, не возник ли конфликт параллельного доступа с нежесткой блокировкой при обновлении записи таблицы Table1, выполним следующий оператор UPDATE:

UPDATE Table1 Set Column1 = @NewValueColumn1,
              Set Column2 = @NewValueColumn2,
              Set Column3 = @NewValueColumn3
WHERE Column1 = @OldValueColumn1 AND
      Column2 = @OldValueColumn2 AND
      Column3 = @OldValueColumn3

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

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

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

UPDATE Table1 Set Column1 = @NewColumn1Value
WHERE (@OldColumn1Value IS NULL AND Column1 IS NULL) OR Column1 =
@OldColumn1Value

Использование адаптеров данных и объектов DataSet для реализации параллельного доступа с нежесткой блокировкой

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

Обработчик события RowUpdated принимает объект RowUpdatedEventArgs, у которого имеется свойство RecordsAffected, показывающее, сколько записей изменено командой обновления, выполняемой для измененной записи таблицы. Если команда обновления учитывает конфликты параллельного доступа с нежесткой блокировкой, то свойство RecordsAffected при конфликте параллельного доступа будет равно 0. Чтобы указать, как обрабатывать эту ситуацию, задайте значение свойства RowUpdatedEventArgs.Status; например, при указании значения UpdateStatus.SkipCurrentRow обновление текущей записи пропускается, но изменение других записей, указанных в команде обновления, продолжается. Дополнительную информацию о событии RowUpdated см. в Working with DataAdapter Events.

Другой способ выявления ошибок параллельного доступа - присвоение true свойству DataAdapter.ContinueUpdateOnError перед вызовом метода Update. По завершении обновления вызывается метод GetErrors объекта DataTable, чтобы определить записи, при обновлении которых произошли ошибки. Затем с помощью свойства RowError этих записей можно получить подробную информацию об ошибке. Дополнительные сведения об обработке ошибок обновления записей см. в Adding and Reading Row Error Information.

В следующем фрагменте кода показано, как с помощью DAL-компонента Customer определять конфликты паралелльного доступа. Здесь предполагается, что клиент считывает DataSet, вносит изменения в данные и передает DataSet методу UpdateCustomer DAL-компонента. Метод UpdateCustomer вызывает хранимую процедуру, обновляющую соответствующую запись таблицы клиентов; хранимая процедура обновляет запись, только если идентификатор клиента и название компании не изменены другим пользователем:

CREATE PROCEDURE CustomerUpdate
{
  @CompanyName varchar(30),
  @oldCustomerID varchar(10),
  @oldCompanyName varchar(30)
}
AS
  UPDATE Customers Set CompanyName = @CompanyName
  WHERE CustomerID = @oldCustomerID AND CompanyName = @oldCompanyName
GO

В методе UpdateCustomer, показанном ниже, присваивается значение свойству UpdateCommand адаптера данных. В данном случае это свойство задает хранимую процедуру обновления, определяющую конфликты параллельного доступа с нежесткой блокировкой. В дальнейшем в обработчике события RowUpdated проверяется, был ли конфликт параллельного доступа. Если да, то приложение сообщает о нем, присваивая значение свойству RowError записи, при обновлении которой возникла проблема. Обратите внимание, что параметрам, передаваемым в раздел WHERE команды UPDATE, присваиваются исходные значения (поле Original) соответствующих полей DataSet.

// Метод UpdateCustomer класса CustomerDALC
public void UpdateCustomer(DataSet dsCustomer)
{
  // Соединяемся с базой данных Northwind
  SqlConnection cnNorthwind = new SqlConnection(
    "Data source=localhost;Integrated security=SSPI;Initial
Catalog=northwind");

  // Создаем адаптер данных для доступа к таблице Customers базы 
  // данных Northwind
  SqlDataAdapter da = new SqlDataAdapter();

  // Задаем команду UPDATE адаптера данных, которая будет вызывать хранимую 
  // процедуру CustomerUpdate
stored procedure
  da.UpdateCommand = new SqlCommand("CustomerUpdate", cnNorthwind);
  da.UpdateCommand.CommandType = CommandType.StoredProcedure;

  // Добавляем в команду UPDATE адаптера данных два параметра, задающие 
  // информацию для раздела WHERE (чтобы определять конфликты 
  // параллельного доступа с нежесткой блокировкой)
  da.UpdateCommand.Parameters.Add("@CompanyName", SqlDbType.NVarChar, 30,
"CompanyName");

  // Заносим исходное значение CustomerID в первый параметр раздела WHERE 
  SqlParameter myParm = da.UpdateCommand.Parameters.Add(
                            "@oldCustomerID", SqlDbType.NChar, 5,
"CustomerID");
  myParm.SourceVersion = DataRowVersion.Original;

  // Заносим исходное значение CustomerName во второй параметр раздела WHERE 
  myParm = da.UpdateCommand.Parameters.Add(
                            "@oldCompanyName", SqlDbType.NVarChar, 30,
"CompanyName");
  myParm.SourceVersion = DataRowVersion.Original;

  // Устанавливаем обработчик события обновления записи
  da.RowUpdated += new SqlRowUpdatedEventHandler(OnRowUpdated);

  // Обновляем базу данных
  da.Update(ds, "Customers");

  foreach (DataRow myRow in ds.Tables["Customers"].Rows)
  {
    if (myRow.HasErrors)
      Console.WriteLine(myRow[0] + " " + myRow.RowError);
  }
}

// Метод обработки события обновления записи. Если зарегистрировать событие, 
// но не обрабатывать его, то будет генерироваться SQL-исключение.
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs
args)
{
  if (args.RecordsAffected == 0)
  {
    args.Row.RowError = "Optimistic Concurrency Violation Encountered";
    args.Status = UpdateStatus.SkipCurrentRow;
  }
}

При выполнении нескольких SQL-операторов в одной хранимой процедуре SQL Server можно для большей производительности установить флаг SET NOCOUNT ON. Тогда SQL Server не отправляет клиенту сообщение после выполнения каждого оператора, что сокращает сетевой трафик. Однако становится невозможной проверка свойства RecordsAffected, показанная в предыдущем фрагменте кода. Свойство RecordsAffected принимает значение -1. Альтернативный вариант - сделать так, чтобы хранимая процедура возвращала функцию @@ROWCOUNT (или передавала ее как выходной параметр); @@ROWCOUNT содержит число записей, обработанных последним оператором хранимой процедуры, и ее значение обновляется даже при использовании SET NOCOUNT ON. Таким образом, если последним SQL-оператором, выполняемым хранимой процедурой, является оператор UPDATE и если возвращается @@ROWCOUNT, то код приложения можно изменить так:

// Добавляем в команду UPDATE адаптера данных еще один параметр, который
// будет содержать возвращаемое значение. Этот параметр можно назвать как 
// угодно.
myParm = da.UpdateCommand.Parameters.Add("@RowCount", SqlDbType.Int);
myParm.Direction = ParameterDirection.ReturnValue;

// Изменяем метод OnRowUpdated, в котором теперь нужно проверять значение
// этого параметра, а не свойство RecordsAffected.
protected static void OnRowUpdated(object sender, SqlRowUpdatedEventArgs
args)
{
  if (args.Command.Parameters["@RowCount"].Value == 0)
  {
    args.Row.RowError = "Optimistic Concurrency Violation Encountered";
    args.Status = UpdateStatus.SkipCurrentRow;
  }
}

Взаимодействие с COM

Если вам нужно, чтобы класс DAL-компонента могли вызывать COM-клиенты, рекомендуем разработать DAL-компоненты согласно описанным выше принципам и написать компонент-оболочку. Кроме того, если вы хотите, чтобы COM-клиенты могли обращаться к вашим DAL-компонентам, руководствуйтесь следующими рекомендациями:

  • определите класс и его члены как открытые;
  • не используйте статические члены;
  • определите в управляемом коде интерфейсы источников событий (event-source interfaces);
  • создайте конструктор, не принимающий параметров;
  • не используйте перегруженные методы - применяйте вместо них методы с другими именами;
  • используйте интерфейсы для предоставления доступа к общим операциям;
  • используйте атрибуты, чтобы указать дополнительную COM-информацию о вашем классе и его членах;
  • во всех исключениях, генерируемых .NET-кодом, передавайте значения HRESULT;
  • в сигнатурах методов указывайте параметры, совместимые с Automation.

Подробнее о взаимодействии с COM см. руководство Microsoft .NET/COM Migration and Interoperability.

Реализация бизнес-объектов

Бизнес-объекты обладают следующим характеристиками.

  • Бизнес-объекты предоставляют программный доступ с поддержкой состояний к бизнес-данным и (в некоторых случаях) к функциональности, связанной с этими данными.
  • Бизнес-объекты могут формироваться по данным со сложными схемами. Данные обычно берутся из нескольких связанных таблиц базы данных.
  • Данные бизнес-объектов могут передаваться в составе параметров ввода-вывода бизнес-процессов.
  • Бизнес-объекты можно сериализовать, чтобы сохранять текущее состояние объектов. Например, может потребоваться, чтобы приложения могли сохранять данные объекта на локальном диске, в настольной базе данных, если приложение работает в офлайновом режиме, или в сообщении Message Queuing.
  • Бизнес-объекты не обращаются к базе данных напрямую. Весь доступ к базе данных осуществляется через связанный с бизнес-объектом DAL-компонент.
  • Бизнес-объекты не инициируют никакие виды транзакций. Транзакции инициируются приложением или бизнес-процессом, работающим с бизнес-объектами.

Как упоминалось выше, бизнес-объекты представляются в приложениях по-разному - от модели, основанной на данных (data-centric model), до представлений, в большей мере ориентированных на объекты:

  • XML;
  • универсальный объект DataSet;
  • типизированный объект DataSet;
  • пользовательские компоненты бизнес-объектов;
  • пользовательские компоненты бизнес-объектов с поддержкой CRUD-операций.

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

  • работать с наборами бизнес-объектов;
  • связывать данные бизнес-объектов с элементами управления пользовательского интерфейса;
  • сериализовать данные бизнес-объекта;
  • передавать данные бизнес-объекта между уровнями.

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

Представление бизнес-объектов как XML-данных

Ниже показывается, как представить простой бизнес-объект в XML-формате. Бизнес-объект содержит данные об одном товаре.

<?xml version="1.0"?>
<Product xmlns="urn:aUniqueNamespace">
  <ProductID>1</ProductID>
  <ProductName>Chai</ProductName>
  <QuantityPerUnit>10 boxes x 20 bags</QuantityPerUnit>
  <UnitPrice>18.00</UnitPrice>
  <UnitsInStock>39</UnitsInStock>
  <UnitsOnOrder>0</UnitsOnOrder>
  <ReorderLevel>10</ReorderLevel>
</Product>

Дополнительную информацию см. в разделе Как представить наборы и иерархии данных в XML-формате приложения.

При использовании XML для представления данных бизнес-объекта придерживайтесь такой схемы.

  • Решите, будет ли XML-документ содержать один бизнес-объект или набор бизнес-объектов. В предыдущем примере представлен один бизнес-объект Product.
  • Используйте пространства имен для уникальной идентификации XML-документов, чтобы избежать конфликтов имен с содержимым других XML-документов. В предыдущем примере используется пространство имен по умолчанию - urn:aUniqueNamespace.
  • Выберите подходящие имена элементов и атрибутов. В предыдущем примере используются имена полей таблицы Product, но это не обязательно. Выбирайте имена, имеющие смысл для вашего приложения.
  • Для представления бизнес-объектов в XML-формате применяйте один из следующих подходов:
    • при работе с SQL Server 2000 в запросах и хранимых процедурах можно использовать раздел FOR XML. Тестирование производительности показало, что применение FOR XML лишь чуть-чуть быстрее, чем возврат объекта DataSet;
    • считать DataSet и преобразовать его или сохранить как XML-поток. При таком подходе неизбежны издержки, связанные с созданием объекта DataSet; кроме того, если выполняется преобразование, возникают дополнительные издержки;
    • сформировать XML-документ по выходным параметрам или с помощью класса чтения (data reader). Классы чтения - самый быстрый способ считывания нескольких записей из базы данных, но издержки формирования XML-данных могут снизить выигрыш в производительности.

Дополнительную информацию и рассмотрение вопросов производительности см. в Performance Comparison: Data Access Techniques.

Представление бизнес-объектов в XML-формате дает следующие преимущества.

  • Поддержка стандартов. XML - стандартный формат представления данных, поддерживаемый консорциумом W3C (World Wide Web Consortium). Дополнительную информацию об этом стандарте см. по ссылке http://www.w3.org/xml.
  • Гибкость. XML позволяет представлять иерархии и наборы данных. Дополнительную информацию см. в разделе Как представить наборы и иерархии данных в XML-формате приложения.
  • Взаимодействие. XML - идеальный выбор для обмена информацией со сторонними организациями и торговыми партнерами независимо от типа платформы. Если с XML-данными работает приложение ASP.NET или Windows Forms, XML-данные можно загрузить в DataSet, чтобы задействовать поддержку связывания с данными, предоставляемую объектами DataSet.

У представления бизнес-объектов в XML-формате имеются следующие недостатки:

  • Поддержка строгой типизации. В XML не поддерживается строгая типизация. Однако для простого контроля типов данных можно применять XSD-схемы.
  • Проверка XML на допустимость. Чтобы проверить XML-данные на допустимость, можно вручную проанализировать код или воспользоваться XSD-схемой. Оба способа относительно медленные. Пример проверки XML-данных на допустимость в соответствии с XSD-схемой см. в разделе Как проверить XML-данные на соответствие XSD-схеме.
  • Показ XML. XML-данные невозможно автоматически показать в пользовательском интерфейсе (UI). Можно написать таблицу стилей XSLT, выполняющую преобразование данных в DataSet; однако писать таблицы стилей не так просто. С другой стороны, с помощью таблицы стилей можно преобразовать XML в какой-либо отображаемый формат вроде HTML. Дополнительную информацию см. в разделе Как программно применить таблицу стилей в .NET-приложении приложения.
  • Анализ XML. Чтобы проанализировать XML, можно воспользоваться DOM (Document Object Model) или классом XmlReader, входящим в состав библиотеки классов Microsoft .NET Framework. Класс XmlReader предоставляет быстрый доступ к XML-данным только для чтения с перемещением только вперед, тогда как DOM - более гибкая модель, обеспечивающая произвольный доступ для чтения и записи. Однако анализ XML-документа с помощью DOM выполняется медленнее: вы должны создать экземпляр XmlDocument (или другого класса XML-анализатора) и загрузить весь XML-документ в память.
  • Сортировка XML. Автоматически отсортировать XML-данные нельзя. Но можно воспользоваться одним из следующих способов:
    • передавать данные в нужном порядке, не требующем сортировки. При таком варианте динамическая пересортировка данных в вызывающем приложении не поддерживается;
    • применить таблицу стилей XSLT для динамической сортировки данных. При необходимости в период выполнения можно с помощью DOM добавить критерий сортировки в таблицу стилей XSLT;
    • преобразовать XML-данные в DataSet и использовать объект DataView для сортировки и поиска элементов данных.
  • Закрытые поля. Возможность скрыть информацию отсутствует.

Представление бизнес-объектов как универсальных объектов DataSet

Универсальный DataSet - это экземпляр класса DataSet, определенного в ADO.NET-пространстве имен System.Data. Объект DataSet содержит один или несколько объектов DataTable, в которые помещается информация, считываемая DAL-компонентом из базы данных.

На рис. 7 показан универсальный объект DataSet, представляющий бизнес-объект Product. В этом DataSet содержится единственный объект DataTable с информацией о товарах. Объект DataTable включает объект UniqueConstraint, указывающий, что поле ProductID является первичным ключом. Объекты DataTable и UniqueConstraint создаются при создании объекта DataSet DAL-компонентом.

Product DataTable - DataTable-объект Product
UniqueConstraint - UniqueConstraint
Defines ProductID as primary key in Product DataTable - Указывает, что Product ID - первичный ключ DataTable-объекта Product

Рис. 7. Универсальный DataSet для бизнес-объекта Product

На рис. 8 показан универсальный объект DataSet для бизнес-объекта Order. В этом DataSet содержатся два объекта DataTable, в которые заносится информация соответственно о заказах и позициях заказов. В каждом DataTable есть объект UniqueConstraint, идентифицирующий первичный ключ этой таблицы. Кроме того, в DataSet имеется объект Relation, связывающий данные о заказах с данными о позициях заказов.

Order DataTable - DataTable-объект Order
UniqueConstraint - UniqueConstraint
Defines OrderID as primary key in Order DataTable - Указывает, что OrderID - первичный ключ DataTable-объекта Order
Relation - Relation
Associates Order and OrderDetails DataTables - Связывает DataTable-объекты Order и OrderDetails
OrderDetals DataTable - DataTable-объект OrderDetals
Defines OrderID and ProductID as composite primary key in OrderDetals DataTable - Указывает, что OrderID и ProductID образуют первичный ключ DataTable-объекта OrderDetals

Рис. 8. Универсальный DataSet для бизнес-объекта Order

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

// Создаем объект ProductDALC
ProductDALC dalcProduct = new ProductDALC();

// Вызываем метод ProductDALC, чтобы получить DataSet с информацией
// обо всех товарах
DataSet dsProducts = dalcProduct.GetProducts();

// Используем DataSet на клиенте. Например, связываем DataSet с элементами
// управления UI
dataGrid1.DataSource = dsProducts.Tables[0].DefaultView;
dataGrid1.DataBind();

// Когда все изменения внесены, передаем обновленный DataSet объекту 
// ProductDALC, чтобы сохранить изменения в базе данных
dalcProduct.UpdateProducts(dsProducts);

Кроме того, в период выполнения можно запросить и изменить таблицы, ограничения и отношения, помещаемые в DataSet. Дополнительную информацию см. в Creating and Using DataSets.

Представление бизнес-объектов в виде универсального DataSet дает следующие преимущества:

  • Гибкость. Объекты DataSet могут содержать наборы данных и описывать сложные связи между данными.
  • Сериализация. В объекты DataSet встроена поддержка сериализации при передаче данных между уровнями (tiers).
  • Связывание с данными. Объекты DataSet можно связать с любыми UI-элементами управления приложений ASP.NET и Windows Forms.
  • Сортировка и фильтрация. Объекты DataSet можно сортировать и фильтровать с помощью объектов DataView. Приложение может создать несколько объектов DataView для одного и того же DataSet, чтобы работать с разными представлениями данных.
  • Взаимозаменяемость с XML. DataSet можно читать или записывать в XML-формате. Это удобно для удаленных и отсоединенных приложений, которые могут принимать DataSet в XML-формате, а затем воссоздавать этот DataSet на локальном уровне. Приложения, работающие в отсоединенном режиме, могут сохранять объекты DataSet в XML-формате.
  • Доступность метаданных. Полное представление метаданных объекта DataSet можно получить в виде XSD-схемы. Кроме того, можно программно получать метаданные DataSet, вызывая методы классов DataSet, DataTable, DataColumn, Constraint и Relation.
  • Параллельный доступ с нежесткой блокировкой. При обновлении данных удобно использовать DataSet и адаптеры данных для выявления конфликтов параллельного доступа с нежесткой блокировкой.
  • Расширяемость. Если схема базы данных изменилась, методы DAL-компонента при необходимости могут создать объекты DataSet, содержащие измененные объекты DataTable и DataRelation. Сигнатуры методов DAL-компонента не изменятся. Вызывающее приложение можно модифицировать так, чтобы оно работало с новыми элементами DataSet.

У представления бизнес-объектов в виде универсального DataSet имеются следующие недостатки.

  • Клиентскому коду приходится обращаться к данным через наборы объектов, содержащихся в DataSet. Чтобы обратиться к таблице объекта DataSet, в клиентском коде приходится указывать целочисленный или строковый индекс таблицы в наборе DataTable. Чтобы обратиться к конкретному полю, вы должны указать его в наборе DataColumn по номеру или имени. В следующем примере показано, как получить доступ к полю ProductName первой записи таблицы Products:
    …
    // Получаем наименование товара первой записи объекта DataSet 
    // dsProducts. Заметьте: в наборах отсчет ведется с нуля.
    String str = (String)dsProducts.Tables["Products"].Rows[0]["ProductName"];
    …
    

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

  • Большие издержки на создание экземпляров и маршалинг. При создании объекта DataSet создается большое количество подобъектов (DataTable, DataRow и DataColumn), следовательно, создание экземпляров и маршалинг объектов DataSet занимает больше времени, чем в случае XML-строк или пользовательских компонентов бизнес-объектов. С ростом объема данных относительная производительность объектов DataSet возрастает, так как издержки на создание внутренней структуры DataSet становятся менее значимыми по сравнению с временем, затрачиваемым на заполнение DataSet данными.
  • Закрытые поля. Возможность скрыть информацию отсутствует.

Представление бизнес-объектов как типизированных объектов DataSet

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

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

Представление бизнес-объектов как типизированных объектов DataSet дает следующие преимущества.

  • Читаемость кода. Для обращения к таблицам и полям типизированного DataSet можно использовать типизированные методы и свойства, как показано ниже:
    …
    // Получаем наименование товара, хранящееся в первой записи 
    // типизированного DataSet dsProducts. Обратие внимание, что 
    // в наборах отсчет ведется с нуля.
    String str = dsProducts.Products[0].ProductName;
    …
    

    Здесь dsProducts - экземпляр типизированного объекта DataSet. В DataSet содержится единственный объект DataTable, у которого имеется свойство Products. Доступ к полям DataTable предоставляется через такие свойства, как ProductName, возвращающее тип данных, который соответствует типу данных поля (а не просто Object).

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

  • Проверка типов при компиляции. Неправильные имена таблиц и полей обнаруживаются на этапе компиляции, а не выполнения.

У представления бизнес-объектов как типизированных объектов DataSet имеются следующие недостатки.

  • Развертывание. Сборку, содержащую класс типизированного DataSet, приходится развертывать на всех уровнях, на которых используется бизнес-объект.
  • Поддержка кода, вызывающего Enterprise Services (COM+). Если типизированный DataSet используется COM+-клиентами, нужно присвоить строгое имя сборке, содержащей типизированный класс, и зарегистрировать ее на клиентских компьютерах. Обычно сборка устанавливается в кэш глобальных сборок. Те же действия необходимы и для пользовательских классов бизнес-объектов (об этом рассказывается далее).
  • Проблемы расширения. Если схема базы данных изменена, то, возможно, для поддержки новой схемы потребуется заново генерировать типизированный класс DataSet. При повторной генерации может понадобиться внесение изменений в любой специализированный код, содержащийся в типизированном DataSet. Сборку, содержащую типизированный DataSet, придется заново развертывать во всех клиентских приложениях.
  • Создание экземпляров. Создать экземпляр типа оператором new нельзя.
  • Наследование. Ваш типизированный DataSet наследуется от базового DataSet, что не позволяет использовать другие базовые классы.

Определение пользовательских компонентов бизнес-объектов

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

  • Закрытые поля для локального кэширования данных бизнес-объекта. Эти поля содержат "снимок" данных в базе данных на момент их считывания DAL-компонентом.
  • Открытые свойства, позволяющие обращаться к состоянию бизнес-объекта, а также к наборам и иерархиям данных в объекте. Имена этих свойств могут совпадать с именами полей базы данных, но это не обязательное требование. Выбирайте имена свойств в соответствии с потребностями приложения, а не с именами полей базы данных.
  • Методы и свойства для локальной обработки данных, содержащихся в компоненте бизнес-объекта.
  • События, сообщающие об изменениях внутреннего состояния компонента бизнес-объекта.

На рис. 9 показано, как применять пользовательские классы бизнес-объектов. Обратите внимание, что класс бизнес-объекта ничего не знает о DAL-компоненте или базе данных; доступ к базе данных полностью выполняется DAL-компонентом, чтобы централизовать политики доступа к данным и бизнес-логику. Кроме того, способ передачи данных бизнес-объекта между уровнями напрямую не связан с форматом представления бизнес-объекта; например, при локальной работе можно представлять бизнес-объекты как пользовательские классы, а для передачи данных бизнес-объекта на другой уровень применять какой-то другой подход (например, передавать скалярные значения или XML).

Business Entity Component - Компонент бизнес-объекта
- Custom business entity classes expose state accessor functions - Пользовательские классы бизнес-объектов, содержащие функции-аксессоры для доступа к состоянию
Business Entity´s state - Состояние бизнес-объекта
Data Access Logic Component - DAL-компонент
Exposes CRUD operations that receive Order business entity data in some format - Содержит CRUD-методы, принимающие данные бизнес-объекта Order в определенном формате
Format: - Формат:

  • user-defined XML - XML-данные в формате, определенном пользователем
  • Dataset - Dataset
  • Business entity components - компоненты бизнес-объектов

Caller - Вызывающий код
Subset of business entity´s state - Подмножество состояний бизнес-объекта
Business entity data passed to business process components or Data Access Logic Component - Данные бизнес-объекта, передаваемые компонентам бизнес-процессов или DAL-компоненту
Business Process Components - Компоненты бизнес-процессов

Рис. 9. Роль пользовательских компонентов бизнес-объектов (щелкните микрокартинку, чтобы увеличить рисунок)

Рекомендации по созданию пользовательских компонентов бизнес-объектов

При реализации пользовательских компонентов бизнес-объектов примите во внимание следующие рекомендации.

  • Выбор между структурами и классами. Простые бизнес-объекты, не содержащие наборы или иерархические данные, лучше представлять в виде структур. Для представления сложных бизнес-объектов или бизнес-объектов, которые предполагается наследовать, лучше использовать классы. Сравнение структур и классов см. в Structures and Classes.
  • Представление состояния бизнес-объекта. Для представления простых значений, например чисел и строк, определите поля соответствующих .NET-типов. Пример кода, показывающий, как определяются пользовательские бизнес-объекты, см. в разделе Как определить компонент бизнес-объекта приложения.
  • Представление поднаборов (subcollections) и иерарархий пользовательского компонента бизнес-объекта. Имеется два способа представления поднаборов и иерархий данных пользовательского бизнес-объекта:
    • .NET-наборы, например ArrayList. Классы .NET-наборов предоставляют удобную модель программирования наборов переменного размера, и в эти классы встроена поддержка связывания UI-элементов управления с данными;
    • DataSet. Объекты DataSet отлично подходят для хранения наборов и иерархий данных, получаемых из баз данных или XML-документов. Кроме того, объекты DataSet удобнее использовать, когда требуются фильтрация и сортировка данных поднаборов или их связывание с данными.

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

  • Поддержка связывания с данными на клиентах с UI. Если бизнес-объект будет использоваться приложениями, поддерживающими UI, и вы собираетесь задействовать автоматическое связывание с данными, вам потребуется реализовать связывание с данными в бизнес-объекте. Рассмотрим следующие случаи:
    • связывание с данными в приложениях Windows Forms. Данные экземпляра объекта можно связывать с элементами управления, не прибегая к реализации интерфейсов связывания с данными в пользовательском бизнес-объекте. Кроме того, можно связывать с данными массив или .NET-набор бизнес-объектов;
    • связывание с данными в приложениях Web Forms. Для связывания данных экземпляра бизнес-объекта с элементами управления Web Forms нужно реализовать интерфейс IBindingList. Однако, если требуется связывание только с наборами данных, можно использовать массивы или .NET-наборы без реализации интерфейса IBindingList в пользовательском бизнес-объекте.

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

  • Предоставление доступа к событиям, уведомляющим о внутренних изменениях данных. Доступ к событиям полезен для полнофункциональных клиентов с UI, так как позволяет актуализировать показываемые данные. События должны отражать только изменения внутреннего состояния, а не изменения данных на сервере. Пример кода, демонстрирующий, как предоставляется доступ к событиям пользовательского класса бизнес-объекта, см. в разделе Как реализовать события в компоненте бизнес-объекта приложения.
  • Поддержка сериализации бизнес-объектов. Поддержка сериализации бизнес-объектов означает, что можно сохранять промежуточные состояния бизнес-объектов, не взаимодействуя с базой данных. Это упрощает разработку отсоединенных (автономных) приложений и проектирование сложных UI-процессов, которые изменяют бизнес-данные только после своего завершения. Существует два типа сериализации:
    • XML-сериализация с помощью класса XmlSerializer. Применяйте XML-сериализацию, когда требуется сериализовать в XML-формат только открытые свойства, доступные для чтения и записи, или открытые поля. Имейте в виду, что когда данные бизнес-объекта возвращаются Web-сервисом, объект автоматически сериализуется в XML-формат с помощью XML-сериализации.

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

    • Сериализация с форматированием, выполняемая с помощью класса BinaryFormatter или SoapFormatter. Применяйте сериализацию с форматированием, когда нужно сериализовать все содержащиеся в объекте открытые и закрытые поля и графы объектов или когда компонент бизнес-объекта передается на удаленный сервер или принимается с удаленного сервера.

      Классы форматирования (formatter classes) сериализуют все поля и свойства объекта - как открытые, так и закрытые. BinaryFormatter сериализует объект в двоичный формат, а SoapFormatter - в SOAP-формат. Сериализация с помощью BinaryFormatter выполняется быстрее, чем с помощью SoapFormatter. Чтобы применить любой из этих классов форматирования, пометьте класс бизнес-объекта атрибутом [Serializable]. Если вы хотите явно управлять форматом сериализации, то, помимо всего прочего, в классе следует реализовать интерфейс ISerializable. Дополнительную информацию об использовании сериализации с форматированием см. в разделах Как сериализовать компоненты бизнес-объектов в двоичный формат и Как сериализовать компоненты бизнес-объектов в SOAP-формат приложения.

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

Определение пользовательских классов бизнес-объектов дает следующие преимущества.

  • Читаемость кода. Для обращения к данным нестандартного класса бизнес-объекта используются типизированные методы и свойства, как, например, в следующем фрагменте кода:
    // Создаем объект ProductDALC
    ProductDALC dalcProduct = new ProductDALC();
    
    // С помощью объекта ProductDALC создаем и заполняем объект ProductEntity.
    // Предполагается, что у класса ProductDALC есть метод GetProduct,
    // принимающий в качестве параметра идентификатор товара (в примере
    // передается значение 21) и возвращающий объект ProductEntity,
    // который содержит все данные по этому товару.
    ProductEntity aProduct = dalcProduct.GetProduct(21);
    
    // Изменяем наименование данного товара
    aProduct.ProductName = "Roasted Coffee Beans";
    

    В приведенном выше примере aProduct - экземпляр пользовательского класса бизнес-объекта ProductEntity. У класса ProductDALC имеется метод GetProduct, создающий объект ProductEntity, заполняющий этот объект информацией о заданном товаре и возвращающий его. Вызывающее приложение может использовать такие свойства, как ProductName, чтобы обращаться к данным объекта ProductEntity и вызывать методы, работающие с этим объектом.

  • Инкапсуляция. Пользовательские бизнес-объекты могут содержать методы, инкапсулирующие простые бизнес-правила. Эти методы работают с данными бизнес-объекта, кэшированными в компоненте бизнес-объекта, а не обращаются к самой информации в базе данных. Рассмотрим следующий пример:
    // Вызываем метод, определенный в классе ProductEntity
    aProduct.IncreaseUnitPriceBy(1.50);
    

    В этом примере вызывающее приложение обращается к методу IncreaseUnitPriceBy объекта ProductEntity. Это изменение не запоминается, пока объект ProductEntity не будет записан в базу данных вызовом соответствующего метода объекта ProductDALC.

  • Моделирование сложных систем. Если моделируется сложная система, в которой взаимодействует большое количество различных бизнес-объектов, может оказаться удобным реализовать пользовательские классы бизнес-объектов, где сложная логика скрывается за четко определенными интерфейсами классов.
  • Локальная проверка на допустимость. В аксессорах свойств пользовательских классов бизнес-объектов можно выполнять простые проверки на допустимость, позволяющие выявить неправильные данные бизнес-объекта. Дополнительную информацию см. в разделе Проверка данных на допустимость в аксессорах свойств компонентов бизнес-объектов.
  • Закрытые поля. Информацию, которую вы не хотите предоставлять вызывающим приложениям, можно скрыть.

У определения пользовательских бизнес-объектов имеются следующие недостатки:

  • Наборы бизнес-объектов. Пользовательский бизнес-объект - это представление одного бизнес-объекта, а не набора бизнес-объектов. Для работы с несколькими бизнес-объектами вызывающему приложению приходится создавать массив или .NET-набор.
  • Сериализация. Для пользовательского бизнес-объекта необходимо разрабатывать свой механизм сериализации. Можно использовать атрибуты, управляющие сериализацией компонентов бизнес-объектов, или реализовать интерфейс ISerializable, чтобы управлять сериализацией по своему усмотрению.
  • Представление в бизнес-объекте сложных отношений или иерархий. Вы должны реализовать собственный механизм представления отношений и иерархий данных в компоненте бизнес-объекта. Как упоминалось выше, во многих случаях самый простой способ решения этой проблемы - применение объектов DataSet.
  • Поиск и сортировка данных. Приходится определять собственный механизм поддержки сортировки и поиска объектов. Например, реализовать интерфейс IComparable, что позволит помещать компоненты бизнес-объектов в наборы SortedList или Hashtable.
  • Развертывание. Сборку, содержащую пользовательский бизнес-объект, нужно развертывать на всех физических уровнях.
  • Поддержка клиентов Enterprise Services (COM+). Если пользовательский бизнес-объект используется COM+-клиентами, необходимо присвоить сборке, содержащей объект, строгое имя и зарегистрировать ее на клиентских компьютерах. Обычно сборка устанавливается в кэш глобальных сборок.
  • Проблемы расширения. При изменении схемы базы данных может потребоваться изменение пользовательского класса бизнес-объекта и повторное развертывание сборки.

Определение пользовательских компонентов бизнес-объектов с CRUD-методами

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

На рис. 10 показана роль пользовательских классов бизнес-объектов с CRUD-методами.

Caller - Вызывающий код
Business Entity Component - Пользовательский компонент бизнес-объекта

  • business entity expose state accessor functions - бизнес-объект содержит функции-аксессоры для доступа к информации о состоянии
  • business entity expose control functions (Save, Load, Delete) - бизнес-объект содержит функции управления (сохранение, загрузка, удаление)

Business Entity´s state - Состояние бизнес-объекта
Data Access Logic Component - DAL-компонент
Exposes CRUD operations that receive Order business entity data in some format - Предоставляет CRUD-методы, которые принимают данные бизнес-объекта Order, передаваемые в определенном формате
Format: - Формат:
  • user-defined XML - пользовательские XML-данные
  • Dataset - DataSet
  • Business entity components - компонент бизнес-объекта
  • Scalars - скалярные значения

Subset of business entity state - Подмножество состояния бизнес-объекта
business entity data passed to business process components or Data Access Logic Component - Данные бизнес-объекта, передаваемые компонентам бизнес-процессов или DAL-компоненту
Business process Components - Компоненты бизнес-процессов

Рис. 10. Роль пользовательских компонентов бизнес-объектов с CRUD-методами (щелкните микрокартинку, чтобы увеличить рисунок)

Определение пользовательских классов бизнес-объектов с CRUD-методами дает следующие преимущества:

  • Инкапсуляция. Пользовательский бизнес-объект инкапсулирует операции, реализованные в DAL-компоненте.
  • Интерфейс для вызывающего кода. Вызывающий код должен иметь дело только с интерфейсом сохранения данных, предоставляемым бизнес-объектом. Напрямую обращаться к DAL-компоненту не требуется.
  • Закрытые поля. Информацию, которую вы не хотите предоставлять вызывающим, можно скрыть.

У определения пользовательских бизнес-объектов с CRUD-методами имеются следующие недостатки:

  • Работа с группами бизнес-объектов. Методы пользовательского бизнес-объекта применяются к одному экземпляру бизнес-объекта. Для поддержки групп бизнес-объектов можно определить статические методы, возвращающие массив или набор компонентов бизнес-объектов.
  • Увеличение затрат времени на разработку. При использовании традиционного объектно-ориентированного подхода на проектирование и разработку обычно затрачивается больше усилий, чем при работе с существующими объектами, например с объектами DataSet.

Рекомендации по представлению данных и передаче данных между уровнями

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

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

  • Если приложение в основном работает с наборами и должно выполнять такие функции, как сортировка, поиск и связывание с данными, рекомендуется использовать объекты Dataset. Но если приложение работает с данными экземпляров, лучше подходят скалярные значения.
  • Если приложение в основном работает с данными экземляров, наилучшим выбором могут оказаться пользовательские компоненты бизнес-объектов, так как при их применении нет издержек, возникающих в случае объекта DataSet, содержащего одну запись.
  • В большинстве случаев проектируйте приложения в расчете на работу с форматами, ориентированными на данные, например, с XML-документами или объектами DataSet. Можно воспользоваться гибкостью и встроенными функциями объектов DataSet. При этом упрощается поддержка нескольких клиентов, сокращается объем разрабатываемого кода и применяется API, знакомый большинству программистов. Хотя объектно-ориентированный подход к работе с данными дает определенные преимущества, из-за необходимости написания кода сложных бизнес-объектов вручную стоимость разработки и сопровождения повышается пропорционально спектру предоставляемых возможностей.

Транзакции

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

  • Транзакции вручную. В компоненте или хранимых процедурах пишется код, напрямую использующий поддержку транзакций соответственно в ADO.NET или Transact-SQL.
  • Автоматические транзакции. Используются возможности Enterprise Services (COM+); в .NET-классы добавляются декларативные атрибуты, указывающие транзакционные требования объектов в период выполнения. Эту модель удобна для настройки нескольких компонентов на выполнение операций в рамках одной и той же транзакции.

В этом разделе излагаются принципы и рекомендации по реализации транзакций в DAL-компонентах и компонентах бизнес-объектов. Примеры и более глубокое рассмотрение транзакций в .NET см. в .NET Data Access Architecture Guide.

Реализация транзакций

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

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

  • вставка записи в таблицу Customer;
  • вставка одной или нескольких записей в таблицу Address.

Для добавления покупателя в базу данных необходимо успешное выполнение обеих операций. Если бизнес-объект никогда не будет частью более крупного бизнес-процесса, инициирующего транзакцию, лучше использовать в бизнес-объекте Customer ручные транзакции. Транзакции вручную значительно быстрее автоматических, так как они не требуют межпроцессного взаимодействия через DTC (Microsoft Distributed Transaction Coordinator).

На рис. 11 показано, как принять решение, какие транзакции использовать - ручные или автоматические. В связи с высокими издержками COM+-транзакций, рекомендуем по возможности обрабатывать транзакции на уровне базе данных и управлять их выполнением в хранимых процедурах.

Need transactions - Необходимо использовать транзакции
Component called by business process? - Компонент вызывается бизнес-процессом?
No - Нет
Yes - Да
Using stored procedures? - Используются ли хранимые процедуры?
Use ADO.NET transaction support - Использовать поддержку транзакций ADO.NET
Do not implement automatic transaction support - Не поддерживать автоматические транзакции
Component needs to vote in transaction? - Нужно ли, чтобы компонент голосовал за или против транзакций?
Push transaction processing to the database - Обрабатывать транзакции на уровне базы данных
Implement automatic transaction with the Supports attribute - Реализовать автоматические транзакции с помощью атрибута Supports

Рис. 11. Выбор способа реализации транзакций

Примечание: Если вы работаете с компонентами из клиента ASP.NET и транзакция не инициируется каким-либо бизнес-процессом, вам может показаться соблазнительным запустить транзакцию из внестраничного кода (code-behind) ASP.NET. Это неправильный подход; никогда не инициируйте транзакцию из клиента ASP.NET. Лучше отделить презентацию данных от бизнес-процесса. Так следует поступать и для того, чтобы не возникало проблем производительности, связанных с задержками при передаче данных по сети, поскольку бизнес-уровень - самый общий уровень, физически развертываемый отдельно.

Рекомендации по использованию ручных транзаций в DAL-компонентах

При реализации ручных транзакций в DAL-компоненте примите во внимание следующие рекомендации.

  • По возможности обрабатывайте транзакции в хранимых процедурах. Для управления транзакциями используйте операторы Transact-SQL: BEGIN TRANSACTION, END TRANSACTION и ROLLBACK TRANSACTION. Пример кода см. в разделе "How To Perform Transactions With Transact-SQL" .NET Data Access Architecture Guide.
  • Если вы не используете хранимые процедуры и DAL-компоненты не вызываются бизнес-процессом, можно программно управлять транзакциями средствами ADO.NET. Пример кода см. в разделе "How to Code ADO.NET Manual Transactions" .NET Data Access Architecture Guide.

Рекомендации по использованию автоматических транзакций в DAL-компонентах

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

  • DAL-компонент должен наследовать от класса ServicedComponent из пространства имен System.EnterpriseServices. У любой сборки, зарегистрированной в службе COM+, должно быть строгое имя. Дополнительную информацию о сборках со строгими именами см. в Creating and Using Strong-Named Assemblies.
  • Пометить DAL-компонент атрибутом Transaction(TransactionOption.Supported), чтобы можно было выполнять операции чтения и записи в одном и том же компоненте. При указании этого атрибута можно избежать издержек выполнения транзакций там, где они не нужны, - в отличие от атрибута Transaction(TransactionOption.Required), при задании которого транзакции выполняются всегда.

В следующем фрагменте кода показывается, как поддерживать автоматические транзакции в классе DAL-компонента:

using System.EnterpriseServices;

[Transaction(TransactionOption.Supported)]
public class CustomerDALC : ServicedComponent
{
  ...
}

Если вы используете автоматические транзакции, DAL-компоненты должны голосовать за или против транзакций, чтобы сообщать об успешном или неудачном выполнении операции. Чтобы голосовать неявно, помечайте методы атрибутом AutoComplete и генерируйте исключение, если операция потерпела неудачу. Чтобы голосовать явно, вызывайте методы SetComplete или SetAbort класса ContextUtil.

Дополнительную информацию об автоматических транзакциях см. в разделе "Using Automatic Transactions" .NET Data Access Architecture Guide.

Использование автоматических транзакций в компонентах бизнес-объектов

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

Примечание: Если компонент бизнес-объекта не содержит бизнес-логики, требующей голосования за транзакции, то компонент может вообще игнорировать контекст транзакции. Тогда не требуется наследовать пользовательский компонент бизнес-объекта от класса ServicedComponent; контекст транзакции по-прежнему существует, но компонент бизнес-объекта его игнорирует.

Проверка на допустимость

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

  • клиентское приложение может локально проверять данные бизнес-объекта перед их передачей;
  • бизнес-процессы при приеме бизнес-документов могут поверять эти документы на соответствие XSD-схеме;
  • DAL-компоненты и хранимые процедуры могут проверять данные на допустимость, чтобы обеспечить ссылочную целостность и соблюдение ограничений и нетривиальных бизнес-правил.

Существует два типа проверок на допустимость.

  • Проверка в заданное время. Выполняется в определенный момент. Например, бизнес-процесс проверяет XML-документ при его получении.
  • Постоянная проверка. Такая проверка выполняется постоянно на разных уровнях приложения. Ниже перечислены примеры такой проверки.
    • В UI ограничивается длина полей, чтобы пользователь не мог вводить слишком длинные строки.
    • В объекте DataSet задается максимальная длина полей данных.
    • Пользовательские компоненты бизнес-объектов проверяют вхождение значений полей в заданный диапазон, их длину, не являются ли значения пустыми и др.
    • DAL-компоненты, хранимые процедуры или сама база данных выполняют аналогичные проверки, чтобы убедиться в допустимости информации перед ее сохранением в базе данных.

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

Как проверить XML на соответствие XSD-схеме

Для проверки XML-документа на соответствие XSD-схеме выполняются следующие действия.

  1. Создается объект XmlValidatingReader, выступающий в роли оболочки объекта XmlTextReader:
    ´ Создаем объект XmlValidatingReader, считывающий и проверяющий
    ´ на допустимость документ Product.xml
    XmlTextReader tr = new XmlTextReader("Product.xml");
    XmlValidatingReader vr = new XmlValidatingReader(tr);
    
  2. Указывается тип проверки на допустимость (значением перечислимого ValidationType). В .NET Framework поддерживается три типа проверки XML-данных на допустимость:
    • DTD (Document type definition) - указывается значение ValidationType.DTD;
    • Microsoft XDR-схемы (XML data-reduced) - указывается ValidationType.XDR;
    • XSD-схемы (стандарт W3C) - указывается ValidationType.Schema.

      Ниже показано, как используется перечислимое ValidationType:

      ´ Выбираем проверку на соответстие XSD-схеме
      vr.ValidationType = ValidationType.Schema;
      
  3. Регистрируется обработчик события проверки на допустимость:
    vr.ValidationEventHandler += new ValidationEventHandler(MyHandlerMethod);
    
  4. Пишется реализация метода, обрабатывающего события проверки на допустимость:
    public void MyHandlerMethod(object sender, ValidationEventArgs e)
    {
       Console.WriteLine("Validation Error: " + e.Message);
    }
    
  5. Документ считывается и проверяется. Ошибки перехватываются обработчиком события проверки на допустимость.
    try
    {
       while (vr.Read())
       {
          // Выполняем требуемую обработку XML-данных...
       }
    }
    catch (XmlException ex)
    {
       Console.WriteLine("XmlException: " + ex.Message);
    }
    vr.Close();
    

Как проверить данные в аксессорах свойств компонентов бизнес-объектов

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

public class ProductDALC
{
 …
  public short ReorderLevel
  {
    get { return reorderLevel; }
  }
  set
  {
    if (value < 0)
    {
      throw new ArgumentOutOfRangeException("ReorderLevel cannot be
negative.");
    }
    reorderLevel = value;
  }

  // Остальные члены класса ProductDALC...
}

Управление исключениями

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

  • Технические исключения, например:
    • генерируемые ADO.NET;
    • генерируемые при соединении с базой данных;
    • связанные с недоступностью ресурсов (базы данных, общих сетевых ресурсов, Message Queueing и др.).
  • Исключения бизнес-логики, например:
    • ошибки проверки на допустимость;
    • ошибки в хранимых процедурах, реализующих бизнес-логику.

Рекомендации по управлению исключениями в DAL-компонентах

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

Приложение должно публиковать содержащуюся в исключениях информацию. Для публикации технических исключений предназначены журналы, за которыми наблюдают администраторы или средства мониторинга WMI (Windows Management Instrumentation), например Microsoft Operations Manager. Бизнес-исключения можно публиковать в журнале приложения. В общем, нужно обеспечивать передачу исключений из DAL-компонента и их публикацию вызывающим, чтобы был доступен весь контекст исключения.

Выполнение этих рекомендаций демонстрирует следующий пример:

public class CustomerDALC
{
  public void UpdateCustomer(Dataset aCustomer)
  {
    try
    {
      // Обновляем информацию о клиенте в базе данных...
    }
    catch (SqlException se)
    {
      // Перехватываем исключение, создаем для него оболочку
      // и генерируем заново
      throw new DataAccessException("Database is unavailable", se);
    }
    finally
    {
      // Код очистки
    }
  }
}

Рекомендации по управлению исключениями в компонентах бизнес-объектов

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

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

public class CustomerEntity
{
  public void Update()
  {
   // Проверяем, указал ли пользователь необходимую информацию. В данном
   // случае это имя покупателя.
    if (FirstName == "" )
    {
       // Создаем и генерируем определенное нами исключение приложения
       throw new MyArgumentException("You must provide a First Name.);
    }
    ...
  }
}

Подробнее об использовании исключений в .NET-приложениях см. в Exception Management in .NET. Можно создавать собственные технические исключения и бизнес-исключения наследованием от класса ApplicationException или BaseApplicationException из состава Exception Management Application Block.

Авторизация и защита

В этом разделе рассказывается о защите DAL-компонентов и компонентов бизнес-объектов. Чтобы реализовать механизм ограничения полномочий управляемого кода, в общеязыковой исполняющей среде .NET используются объекты разрешений (permissions objects). Существует три вида объектов разрешений, каждый из которых решает определенные задачи.

  • Защита по правам доступа кода (code access security). Эти объекты разрешений применяются, чтобы защищать ресурсы и операции от неавторизованного использования.
  • Идентификация (identity). Эти объекты разрешений задают идентификационные характеристики, которые должны быть у сборки, чтобы она получила разрешение на выполнение.
  • Защита на основе ролей (role-based security). Эти объекты разрешений предоставляют механизм, определяющий идентификацию пользователя (или агента, действующего от имени пользователя) либо его принадлежность какой-то роли. Единственный объект защиты на основе ролей - объект PrincipalPermission.

Управляемый код определяет идентификацию или роль участника безопасности (principal) с помощью объекта Principal, содержащего ссылку на объект Identity. Концепция идентификации и участников безопасности напоминает концепцию пользовательских и групповых учетных записей. В .NET Framework объекты идентификации задают пользователей, а роли описывают принадлежность к группам и контексты защиты. Объект участника безопасности инкапсулирует и объект идентификации, и роль. Приложения .NET Framework предоставляют права объектам участников безопасности в соответствии с их идентификацией либо, что встречается чаще, в соответствии с их принадлежностью к той или иной роли.

Подробнее о разрешениях и защите в .NET см. в Key Security Concepts.

Рекомендации по реализации защиты в DAL-компонентах

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

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

Выполняйте авторизацию на уровне DAL-компонента, если вам нужно:

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

Определив объекты идентификации и участников безопасности, проверяйте их права в соответствии с ролями одним из трех способов:

  • через объект PrincipalPermission для императивной проверки прав доступа;
  • используя атрибут PrincipalPermissionAttribute для декларативной проверки прав доступа;
  • с помощью свойств и метода IsInRole для явной проверки прав доступа.

Ниже показано, как использовать PrincipalPermissionAttribute для декларативной проверки прав доступа в методе класса DAL-компонента:

using System;
using System.Security.Permissions;

public class CustomerDALC 
{

  public CustomerDALC()
  {
  }

  // Атрибут PrincipalPermissionAttribute используется, чтобы запросить,
  // имеет ли код, вызывающий этот метод, идентификацию "MyUser"
  // и назначена ли ему роль "Administrator"
  [PrincipalPermissionAttribute(SecurityAction.Demand,
                                Name="MyUser", Role="Administrator")]
  public void DeleteCustomer(string customerID)
  {
    // Код удаления данных о покупателе
  }
}

А вот пример, где создается объект участника безопасности с требуемыми идентификацией и ролью, что позволяет успешно вызвать метод DeleteCustomer объекта CustomerDALC:

using System;
using System.Security.Principal;
using System.Threading;

public class MainClass
{
  public static int Main(string[] args)
  {
    Console.Write("User Name: ");
    string UserName = Console.ReadLine();

    Console.Write("Password: ");
    string Password = Console.ReadLine();

    if (Password == "password" && UserName == "MyUser")
    {
      // Создаем универсальную идентификацию с именем "MyUser"
      GenericIdentity MyIdentity = new GenericIdentity("MyUser");

      // Создаем роли
      String[] MyString = {"Administrator", "User"};

      // Создаем универсальный участник безопасности
      GenericPrincipal MyPrincipal = new GenericPrincipal(MyIdentity,
MyString);
      
      // Задаем текущего участника для данного потока, чтобы использовать
      // этого участника для доступа с защитой на основе ролей
      Thread.CurrentPrincipal = MyPrincipal;
    }

    // Создаем объект CustomerDALC и пытаемся вызвать его метод
    // DeleteCustomer. Вызов будет успешным, только если у текущего
    // участника безопасности должные роль и идентификация.
    CustomerDALC c = new CustomerDALC();
    c.DeleteCustomer("VINET");
  }
}

Аутентификация средствами Windows

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

Подробнее об аутентификации средствами Windows и о пулах соединений см. в разделе "Managing Database Connections" .NET Data Access Architecture Guide.

Рекомендации по обеспечению безопасного взаимодействия

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

  • Если ваши DAL-компоненты вызываются по сети различными уровнями и при обмене данными передается конфиденциальная информация, которую нужно защищать, используйте технологии безопасного взаимодействия: DCOM (Distributed Component Object Model), SSL (Secure Sockets Layer) или IPSec (Secure Internet Protocol).
  • Если информация хранится в базе данных в зашифрованном виде, то шифрование и дешифрование данных обычно выполняется DAL-компонентами. При высоком риске несанкционированного доступа к информации настоятельно рекомендуем обеспечить безопасный канал взаимодействия для приема и передачи данных DAL-компонентами.

Рекомендации по организации защиты в компонентах бизнес-объектов

Если вы реализуете бизнес-объекты как структуры данных (например, XML-данных или объектов DataSet), реализовать проверку прав доступа не требуется. Однако при реализации бизнес-объектов в виде пользовательских компонентов с CRUD-операциями примите во внимание следующие рекомендации.

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

Развертывание

В этом разделе даны рекомендации по выбору способа развертывания DAL-компонентов и компонентов бизнес-объектов.

Развертывание DAL-компонетов

Существует два способа развертывания DAL-компонентов.

  • Развертывание DAL-компонентов вместе с объектами бизнес-процессов. Такой способ развертывания позволяет добиться оптимальной скорости передачи данных и дает ряд технических преимуществ.
    • Бесшовное выполнение транзакций между объектами бизнес-процессов и DAL-компонентами. Однако транзакции, в которых используются каналы удаленного взаимодействия, не выполняются бесшовно. При удаленном взаимодействии необходимо реализовать транзакции через DCOM. Более того, если бизнес-процесс и DAL-компонент отделены друг от друга брандмауэром, вам придется открыть порты брандмауэера между этими двумя физическими уровнями, чтобы DTC-взаимодействие стало возможным.
    • Совместное развертывание объектов бизнес-процессов и DAL-компонентов сокращает число точек, где транзакции могут потерпеть неудачу.
    • Автоматически используется один и тот же контекст защиты для объектов бизнес-процессов и DAL-компонентов. Нет необходимости настраивать объекты участников безопасности.
  • Развертывание DAL-компонентов вместе с UI-кодом. DAL-компоненты иногда напрямую используются UI-компонентами и компонентами UI-процессов. Чтобы повысить производительность при работе через Web, можно развертывать DAL-компоненты совместно с UI-кодом; при таком развертывании UI-уровень для большей производительности может использовать преимущества потоков данных, создаваемых классом чтения данных (data reader streaming). Однако при этом способе развертывания учтите, что:
    • типичная причина отказа от развертывания DAL-компонентов вместе с UI-кодом - стремление предотвратить прямой сетевой доступ к источникам данных с Web-ферм;
    • если Web-ферма развертывается по сценарию DMZ (в демилитаризованной зоне), вы должны открыть порты брандмауэра для доступа к SQL Server. Если используются COM+-транзакции, нужно открыть дополнительные порты брандмауэра для взаимодействия через DTC. Дополнительную информацию см. в .NET Data Access Architecture Guide.

Развертывание бизнес-объектов

Бизнес-объекты используются на самых разных уровнях приложения. Точки развертывания компонентов бизнес-объектов зависят от способа реализации бизнес-объектов. Ниже описывается, как развертывать бизнес-объекты при выборе различных вариантов реализации.

  • Развертывание бизнес-объектов, реализованных как типизированные DataSet. К типизированному классу DataSet обращаются DAL-компоненты и вызывающее приложение. Поэтому рекомендуется определять типизированные классы DataSet в общей сборке, развертываемой на нескольких уровнях.
  • Развертывание бизнес-объектов, реализованных как пользовательские компоненты бизнес-объектов. К пользовательским классам бизнес-объектов могут обращаться DAL-компоненты (в зависимости от сигнатур методов DAL-компонента). Следуйте тем же рекомендациям, что и в случае типизированных объектов DataSet: определяйте пользовательские классы бизнес-объектов в общей сборке, развертываемой на нескольких уровнях.
  • Развертывание бизнес-объектов, реализованных как универсальные объекты DataSet или XML-строки. Универсальный DataSet и XML-строки - стандартные типы данных, поэтому проблем с их развертыванием нет.

Приложение

Следующий фрагмент кода - пример определения класса CustomerDALC, являющегося классом DAL-компонента с CRUD-методами. Класс используется при работе с бизнес-объектом Customer. В классе CustomerDALC реализованы CRUD-операции для бизнес-объекта Customer и дополнительные методы, инкапсулирующие бизнес-логику для этого объекта.

public class CustomerDALC
{
  private string conn_string;

  public CustomerDALC()
  {
    // Получаем строку подключения из защищенного хранилища или оттуда,
    // где она хранится в зашифрованном виде, и присваиваем ее
    // члену conn_string
  }

  public CustomerDataSet GetCustomer(string id)
  {
    // Код считывания данных о покупателе в типизированный DataSet
  }

  public string CreateCustomer(string name,
                               string address, string city, string state,
string zip)
  {
    // Код для добавления в базу данных записи о покупателе, заполняемой
    // скалярными параметрами, передаваемыми методу.
    // Возвращает поле customerID этой записи (идентификатор покупателя).
  }

  public void UpdateCustomer(CustomerDataSet updatedCustomer)
  {
    // Код для обновления информации о покупателе в соответствии с данными,
    // передаваемыми в параметре CustomerDataSet
  }

  public void DeleteCustomer(string id)
  {
    // Код удаления данных о покупателе с заданным идентификатором
  }

  public DataSet GetCustomersWhoPurchasedProduct(int productID)
  {
    // Код для считывания данных о покупателе в универсальный DataSet.
    // Такой тип возвращаемого значения объясняется тем, что считывается
    // вся информация, связанная с покупателем.
  }
}

Как представить наборы и иерархии данных в XML-формате

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

<Order xmlns="urn:aUniqueNamespace">
  <OrderID>10248</OrderID>
  <CustomerID>VINET</CustomerID>
  <OrderDate>1996-07-04</OrderDate>
  <ShippedDate>1996-07-16</ShippedDate>
  <OrderDetails>
    <OrderDetail>
      <ProductID>11</ProductID>
      <UnitPrice>14.00</UnitPrice>
      <Quantity>12</Quantity>
    </OrderDetail>
    <OrderDetail>
      <ProductID>42</ProductID>
      <UnitPrice>9.80</UnitPrice>
      <Quantity>10</Quantity>
    </OrderDetail>
    <OrderDetail>
      <ProductID>72</ProductID>
      <UnitPrice>34.80</UnitPrice>
      <Quantity>5</Quantity>
    </OrderDetail>
  </OrderDetails>
</Order>

Как программно применить таблицу стилей в .NET-приложении

Чтобы программно применить таблицу стилей в .NET-приложении, выполните следующие действия.

  1. Импортируйте пространство имен System.Xml.Xsl, как показано ниже. Пространство имен System.Xml.Xsl содержит классы XSLT-преобразований библиотеки классов .NET Framework.
    using System.Xml.Xsl;
    
  2. Создайте объект XslTransform:
    XslTransform stylesheet = new XslTransform();
    
  3. Загрузите требуемую таблицу стилей в объект XslTransform:
    stylesheet.Load("MyStylesheet.xsl");
    
  4. Вызовите метод Transform объекта XslTransform, как показано ниже. При вызове метода Transform указываются имена исходного и конечного XML-документа.
    stylesheet.Transform(sourceDoc, resultDoc);
    

Как создать типизированный объект DataSet

Для представления бизнес-объектов можно применять типизированные объекты DataSet. Создать типизированный DataSet можно несколькими способами:

  • в Microsoft Visual Studio® .NET с помощью адаптера данных;
  • в Visual Studio .NET по XSD-схеме;
  • в окне командной строки .NET Framework с помощью XSD Schema Definition Tool (xsd.exe).

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

Создание типизированного DataSet с помощью адаптера данных

Чтобы создать типизированный DataSet с помощью адаптера данных, выполните следующие действия.

  1. В Visual Studio .NET поместите адаптер данных на форму или компонент. В Configuration Wizard адаптера данных укажите информацию о соединении для адаптера. Также укажите SQL-строки для команд Select, Insert, Update и Delete адаптера данных.
  2. В Component Designer щелкните объект Data Adapter правой кнопкой мыши и выберите Generate DataSet.
  3. В диалоговом окне Generate DataSet выберите New, введите имя нового класса DataSet и щелкните OK.
  4. Чтобы убедиться в создании типизированного DataSet, щелкните в Solution Explorer кнопку Show All Files. Раскройте узел файла XSD-схемы и убедитесь в наличии файла кода, связанного с файлом XSD-схемы. В этом файле кода содержится определение созданного типизированного класса DataSet.

Создание типизированного DataSet по файлу XSD-схемы

Чтобы создать в Visual Studio .NET типизированный DataSet по файлу XSD-схемы, выполните следующие действия.

  1. В Visual Studio .NET создайте новый проект или откройте существующий.
  2. Добавьте в проект существующую XSD-схему или создайте XSD-схему в Component Designer.
  3. В Solution Explorer дважды щелкните файл XSD-схемы для ее просмотра в Component Designer.
  4. В Component Designer выберите главный элемент XSD-схемы.
  5. В меню Schema щелкните Generate DataSet.
  6. Чтобы убедиться в создании типизированного DataSet, в Solution Explorer щелкните кнопку Show All Files. Раскройте узел файла XSD-схемы и убедитесь в наличии файла кода, связанного с XSD-схемой. В этом файле кода содержится определение созданного типизированного класса DataSet.

Создание типизированного DataSet с помощью XML Schema Definition Tool (xsd.exe)

Утилита XML Schema Definition Tool позволяет генерировать типизированный DataSet по файлу XSD-схемы, файлу XDR-схемы или документу экземпляра XML. В следующей команде файл XSD-схемы XsdSchemaFile.xsd используется для генерации типизированного класса DataSet, исходный код которого на языке Visual C# помещается в файл XsdSchemaFile.cs текущего каталога:

xsd /dataset /language:C# XsdSchemaFile.xsd

Дополнительную информацию см. в Generating a Strongly Typed DataSet.

Как определить компонент бизнес-объекта

В следующем примере показывается, как определить пользовательский класс для бизнес-объекта Product:

public class ProductEntity
{
  // Закрытые поля, используемые для хранения состояния бизнес-объекта
  // Product
  private int productID;
  private string productName;
  private string quantityPerUnit;
  private decimal unitPrice;
  private short unitsInStock;
  private short unitsOnOrder;
  private short reorderLevel;

  // Открытые свойства, предоставляющие доступ к состоянию 
  public int ProductID
  {
    get { return productID; }
    set { productID = value; }
  }
  public string ProductName
  {
    get { return productName; }
    set { productName = value; }
  }
  public string QuantityPerUnit
  {
    get { return quantityPerUnit; }
    set { quantityPerUnit = value; }
  }
  public decimal UnitPrice
  {
    get { return unitPrice; }
    set { unitPrice = value; }
  }
  public short UnitsInStock
  {
    get { return unitsInStock; }
    set { unitsInStock = value; }
  }
  public short UnitsOnOrder
  {
    get { return unitsOnOrder; }
    set { unitsOnOrder = value; }
  }
  public short ReorderLevel
  {
    get { return reorderLevel; }
    set { reorderLevel = value; }
  }

  // Методы и свойства для выполнения локальной обработки
  public void IncreaseUnitPriceBy(decimal amount)
  {
    unitPrice += amount;
  }
  public short UnitsAboveReorderLevel
  {
    get { return (short)(unitsInStock - reorderLevel); }
  }
  public string StockStatus
  {
    get 
    { 
      return "In stock: " + unitsInStock + ", on order: " + unitsOnOrder;
    }
  }
}

Как представить наборы и иерархии данных в компоненте бизнес-объекта

Следующий пример иллюстрирует, как определить пользовательский класс для бизнес-объекта Order. В каждом заказе содержится несколько позиций, данные о которых хранятся в DataSet, входящем в состав класса OrderEntity.

public class OrderEntity
{
  // Закрытые поля, содержащие информацию о заказе
  private int orderID;
  private string customerID;
  private DateTime orderDate;
  private DateTime shippedDate;

  // Закрытое поле, содержащее информацию о позициях заказа
  private DataSet orderDetails;

  // Открытые свойства, предоставляющие доступ к информации о заказе
  public int OrderID
  {
    get { return orderID; }
    set { orderID = value; }
  }
  public string CustomerID
  {
    get { return customerID; }
    set { customerID = value; }
  }
  public DateTime OrderDate
  {
    get { return orderDate; }
    set { orderDate = value; }
  }
  public DateTime ShippedDate
  {
    get { return shippedDate; }
    set { shippedDate = value; }
  }

  // Открытое свойство, предоставляющее доступ к информации о позициях заказа
  public DataSet OrderDetails
  {
    get { return orderDetails; }
    set { orderDetails = value; }
  }

  // Дополнительный метод, упрощающий доступ к информации о позициях
  // заказа
  public bool IsProductOrdered(int productID)
  {
    // В DataTable должно быть поле первичного ключа
    DataRow row = orderDetails.Tables[0].Rows.Find(productID);
    
    if (row != null)
  return true;
    else
  return false;
  }

  // Дополнительное свойство, упрощающее доступ к информации о позициях
  // заказа
  public int NumberOfOrderItems
  {
    get
    {
      return orderDetails.Tables[0].Rows.Count;
    }
  }
}

При изучении класса OrderEntity обратите внимание на то, что у класса имеются:

  • закрытые поля, содержащие информацию о заказе. Кроме того, имеется дополнительное закрытое поле типа DataSet с информацией о позициях заказа. DAL-компонент заполняет все эти поля при создании объекта OrderEntity;
  • открытые свойства, предоставляющие доступ к информации о заказе. Кроме того, имеется свойство типа DataSet, предоставляющее вызывающему приложению доступ к информации о позициях заказа;
  • дополнительные метод и свойство, упрощающие доступ к позициям заказа:
    • метод IsProductOrdered принимает параметр, являющийся идентификатором товара, и возвращает значение типа Boolean, указывающее, присутствует ли этот товар в заказе;
    • свойство NumberOfOrderItems возвращает количество позиций в заказе.

Как связать компоненты бизнес-объектов с UI-элементами управления

В приложениях Windows Forms и ASP.NET можно связать бизнес-объекты с UI-элементами управления. Возможны два случая:

  • Связывание одного бизнес-объекта с UI-элементами управления. В следующем фрагменте кода показывается, как получить объект OrderEntity по объекту OrderDALC и связать объект OrderEntity с элементами управления Windows Forms. Когда пользователь изменяет значения, содержащиеся в этих элементах, данные объекта OrderEntity также автоматически изменяются.
    // Создаем объект OrderDALC
    OrderDALC dalcOrder = new OrderDALC();
    
    // По объекту dalcOrder получаем объект OrderEntity для заказа
    // с идентификтором 10248. Предполагается, что у класса OrderDALC
    // есть метод GetOrder(), возвращающий объект OrderEntity
    // для заказа с заданным идентификатором.
    OrderEntity order = dalcOrder.GetOrder(10248);
    
    // Связываем свойство OrderID объекта OrderEntity с элементом
    // управления TextBox
    textBox1.DataBindings.Add("Text", order, "OrderID");
    
    // Связываем свойство CustomerID объекта OrderEntity с другим
    // элементом управления TextBox
    textBox2.DataBindings.Add("Text", order, "CustomerID");
    
    // Связываем свойство OrderDate объекта OrderEntity с элементом
    // управления DatePicker
    dateTimePicker1.DataBindings.Add("Value", order, "OrderDate");
    
    // Связываем свойство ShippedDate объекта OrderEntity с другим
    // элементом управления DatePicker
    dateTimePicker2.DataBindings.Add("Value", order, "ShippedDate");
    
    // Связываем OrderDetails DataSet объекта OrderEntity с элементом
    // управления DataGrid. В DataGrid каждый DataRow объекта DataSet
    // показывается в отдельной строке сетки.
    dataGrid1.DataSource = order.OrderDetails.Tables[0].DefaultView;
    

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

    // Обращение к объекту dalcOrder для сохранения объекта OrderEntity в базе
    // данных. Предполагается, что у класса OrderDALC есть метод
    // UpdateOrder(), который принимает параметр OrderEntity и обновляет
    // соответствующую запись базы данных.
    dalcOrder.UpdateOrder(order);
    
  • Связывание набора бизнес-объектов с элементом управления DataGrid. В следующем фрагменте кода показывается, как получить массив объектов OrderEntity, обратившись к объекту OrderDALC, а затем связать этот массив с DataGrid, размещаемым на Windows-форме. В DataGrid каждый элемент массива (т. е. каждый объект OrderEntity) показывается в отдельной строке сетки.
    // Создаем объект OrderDALC
    OrderDALC dalcOrder = new OrderDALC();
    
    // Используем dalcOrder, чтобы получить массив объектов OrderEntity
    // для покупателя "VINET". Предполагается, что у класса OrderDALC
    // имеется метод GetOrdersForCustomer(), возвращающий массив объектов
    // OrderEntity с информацией о заказах данного покупателя.
    OrderEntity[] orderEntities = dalcOrder.GetOrdersForCustomer("VINET");
    
    // Связываем массив с элементом управления DataGrid
    dataGrid1.DataSource = orderEntities;
    

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

    // Обращаемся к объекту dalcOrder, чтобы сохранить изменения в базе данных.
    // Предполагается, что у класса OrderDALC имеется метод UpdateOrders(),
    // который принимает массив объектов OrderEntity и обновляет
    // соответствующие записи в базе данных.
    dalcOrder.UpdateOrders(orderEntities);
    

Как реализовать события в компоненте бизнес-объекта

Пользовательские бизнес-объекты могут генерировать события при изменении своего состояния. Эти события полезны для полнофункциональных клиентов и при разработке UI, так как позволяют актуализировать показываемые данные. В следующем фрагменте кода показывается, как в классе OrderEntity генерируются события бизнес-объекта:

// Определяем общий класс события для всех событий бизнес-объекта
public class EntityEventArgs : EventArgs
{
  // Определяем члены события, содержащие информацию о событии
}

// Определяем делегат, задающий сигнатуру событий бизнес-объекта
public delegate void EntityEventHandler(Object source, EntityEventArgs e);

// Определяем пользовательский класс бизнес-объекта, генерирующий события
// при изменении состояния бизнес-объекта
public class OrderEntity
{
  // Определяем для изменений состояния бизнес-объекта
  // Before- и After-события
  public event EntityEventHandler BeforeChange, AfterChange;

  // Закрытые поля, содержащие информацию о состоянии бизнес-объекта
  private int orderID;
  private int customerID;
  private DateTime orderDate;
  private DateTime shippedDate;
  private DataSet orderDetails;

  // Открытые свойства, предоставляющие доступ к состоянию бизнес-объекта
  public int OrderID
  {
    get { return orderID; }
    set
    { 
      // Генерируем Before-событие
      BeforeChange(this, new EntityEventArgs());
      orderID = value;
      // Генерируем After-событие
      AfterChange(this, new EntityEventArgs()); 
    }
  }
  public int CustomerID
  {
    get { return customerID; }
    set
    { 
      // Генерируем Before-событие
      BeforeChange(this, new EntityEventArgs());
      customerID = value;
      // Генерируем After-событие
      AfterChange(this, new EntityEventArgs());
    }
  }
  public DateTime OrderDate
  {
    get { return orderDate; }
    set
    {
      // Генерируем Before-событие
      BeforeChange(this, new EntityEventArgs());
      orderDate = value;
      // Генерируем After-событие
      AfterChange(this, new EntityEventArgs());
    }
  }
  public DateTime ShippedDate
  {
    get { return shippedDate; }
    set
    {
      // Генерируем Before-событие
      BeforeChange(this, new EntityEventArgs());
      shippedDate = value;
      // Генерируем After-событие
      AfterChange(this, new EntityEventArgs());
    }
  }

  // Если требуется, определяем другие члены...
}

Обратите внимание в показанном выше фрагменте кода на следующее.

  • Класс EntityEvent предоставляет информацию о событиях, связанных с бизнес-объектом. Делегат EntityEventHandler определяет сигнатуру всех событий бизнес-объекта, генерируемых пользовательскими классами бизнес-объектов. Сигнатура делегата соответствует правилам определения делегатов обработчиков событий, применяемым в .NET Framework. Принципы определения и использования событий см. в Event Usage Guidelines.
  • В классе OrderEntity определены два события: BeforeChange и AfterChange.
  • В set-аксессорах свойств класса OrderEntity перед изменением состояния бизнес-объекта генерируется событие BeforeChange, а после изменения состояния - AfterChange.

Как сериализовать компоненты бизнес-объектов в XML-формат

В этом разделе рассматриваются следующие вопросы:

  • использование класса XmlSerializer для сериализации пользовательских бизнес-объектов;
  • XML-сериализация объектов в Web-сервисах XML;
  • XML-формат, используемый по умолчанию для пользовательских бизнес-объектов;
  • управление XML-форматом, используемым при сериализации пользовательских бизнес-объектов.

Использование класса XmlSerializer для сериализации пользовательских бизнес-объектов

В следующем фрагменте кода показывается, как использовать класс XmlSerializer для сериализации объекта OrderEntity в XML: // В этом пространстве имен содержится класс XmlSerializer

using System.Xml.Serialization;
...
// Создаем объект XmlSerializer, используемый для сериализации объектов
// типа OrderEntity
XmlSerializer serializer = new XmlSerializer(typeof(OrderEntity));

// Сериализуем объект OrderEntity в XML-файл "MyXmlOrderEntity.xml"
TextWriter writer = new StreamWriter("MyXmlOrderEntity.xml");
serializer.Serialize(writer, order);
writer.Close();

Сериализация объектов в Web-сервисах XML

Следующий пример код показывает, как записывать объекты в Web-сервисе XML, работающем с пользовательскими бизнес-объектами:

namespace MyWebService
{
  [WebService(Namespace="urn:MyWebServiceNamespace")]
  public class OrderWS : System.Web.Services.WebService
  {
    [WebMethod]
    public OrderEntity GetOrder(int orderID)
    {
     // Создаем объект OrderDALC
     OrderDALC dalcOrder = new OrderDALC();

     // Обращаемся к объекту dalcOrder, чтобы получить объект OrderEntity
     // с информацией о заказе с заданным идентификатором.
     // Предполагается, что у класса OrderDALC имеется метод GetOrder,
     // принимающий идентификатор заказа и возвращающий объект OrderEntity,
     // который содержащит все данные об этом заказе.
     OrderEntity order = dalcOrder.GetOrder(10248);

      // Возвращаем объект OrderEntity. Этот объект автоматически
      // сериализуется.
      return order;
    }

    [WebMethod]
    public void UpdateOrder(OrderEntity order)
    {
     // Создаем объект OrderDALC
     OrderDALC dalcOrder = new OrderDALC();

     // Обращаемся к объекту dalcOrder, чтобы сохранить информацию объекта
     // OrderEntity в базе данных. Предполагается, что у класса OrderDALC
     // имеется метод UpdateOrder, принимающий объект OrderEntity
     // и сохраняющий информацию этого объекта в базе данных.
    dalcOrder.UpdateOrder(order);
    }

В показанном выше коде обратите внимание на следующее.

  • Метод GetOrder принимает параметр с идентификатором заказа и возвращает объект OrderEntity, содержащий данные об этом заказе.
  • Метод UpdateOrder принимает объект OrderEntity и сохраняет информацию этого объекта в базе данных.
  • Если клиентское приложение вызывает методы GetOrder и UpdateOrder, то при вызове методов объекты OrderEntity сериализуются в XML-формат автоматически.

XML-формат, применяемый по умолчанию для пользовательских бизнес-объектов

В следующем XML-документе показывается формат XML-сериализации, применяемый по умолчанию для объектов OrderEntity:

<?xml version="1.0" encoding="utf-8"?>
<OrderEntity xmlns:xsd="http://www.w3.org/2001/XMLSchema"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <OrderID>10248</OrderID>
  <CustomerID>VINET</CustomerID>
  <OrderDate>1996-07-04T00:00:00.0000000+01:00</OrderDate>
  <OrderDetails> ... see below ... </OrderDetails>
  <ShippedDate>1996-07-16T00:00:00.0000000+01:00</ShippedDate>
</OrderEntity>

Этот документ иллюстрирует следующие правила XML-сериализации:

  • имя корневого элемента XML-документа совпадает с именем класса, OrderEntity;
  • каждое открытое свойство (или поле) объекта OrderEntity сериализуется в элемент с тем же именем.

Свойство OrderDetails класса OrderEntity - это объект DataSet. Для объектов DataSet предоставляется встроенная поддержка XML-сериализации. DataSet OrderDetails сериализуется в следующий формат:

<OrderDetails>
  <xs:schema id="NewDataSet" xmlns=""
             xmlns:xs="http://www.w3.org/2001/XMLSchema"
             xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
    <xs:element name="NewDataSet" msdata:IsDataSet="true" msdata:Locale="en-
      UK">
      <xs:complexType>
        <xs:choice maxOccurs="unbounded">
          <xs:element name="OrderDetails">
            <xs:complexType>
              <xs:sequence>
                <xs:element name="OrderID" type="xs:int" minOccurs="0" />
                <xs:element name="ProductID" type="xs:int" minOccurs="0" />
                <xs:element name="UnitPrice" type="xs:decimal" minOccurs="0"
                  />
                <xs:element name="Quantity" type="xs:short" minOccurs="0" />
              </xs:sequence>
            </xs:complexType>
          </xs:element>
        </xs:choice>
      </xs:complexType>
    </xs:element>
  </xs:schema>
  <diffgr:diffgram xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
                   xmlns:diffgr="urn:schemas-microsoft-com:xml-diffgram-v1">
    <NewDataSet>
      <OrderDetails diffgr:id="OrderDetails1" msdata:rowOrder="0"
                    diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>11</ProductID>
        <UnitPrice>14</UnitPrice>
        <Quantity>12</Quantity>
      </OrderDetails>
     <OrderDetails diffgr:id="OrderDetails2" msdata:rowOrder="1"
                   diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>42</ProductID>
        <UnitPrice>9.8</UnitPrice>
        <Quantity>10</Quantity>
      </OrderDetails>
      <OrderDetails diffgr:id="OrderDetails3" msdata:rowOrder="2"
                    diffgr:hasChanges="inserted">
        <OrderID>10248</OrderID>
        <ProductID>72</ProductID>
        <UnitPrice>34.8</UnitPrice>
        <Quantity>5</Quantity>
      </OrderDetails>
    </NewDataSet>
  </diffgr:diffgram>
</OrderDetails>

Обратите внимание на следующие особенности сериализации объектов DataSet.

  • В разделе <xs:schema> описывается структура DataSet: таблицы, имена и типы полей.
  • В разделе <xs:diffgram> содержатся данные DataSet. Каждый элемент <OrderDetails> описывает одну запись таблицы OrderDetails, входящей в состав DataSet.

Управление XML-форматом, применяемым при сериализации пользовательских бизнес-объектов

С помощью .NET-атрибутов можно управлять сериализацией свойств и полей пользовательского класса бизнес-объекта в XML-формат. Рассмотрим доработанную версию класса OrderEntity:

 [XmlRoot(ElementName="Order", Namespace="urn:MyNamespace")]
public class OrderEntity
{
  [XmlAttribute(AttributeName="ID")]
  public int OrderID { ...тот же код get и set, что и раньше... }

  [XmlAttribute(AttributeName="CustID")]
  public string CustomerID { ...тот же код get и set, что и раньше... }

  [XmlElement(ElementName="Ordered")]
  public DateTime OrderDate { ...тот же код get и set, что и раньше... }

  public DataSet OrderDetails { ...тот же код get и set, что и раньше... }

  [XmlElement(ElementName="Shipped")
  public DateTime ShippedDate { ...тот же код get и set, что и раньше... }

  // Если требуется, определяем другие члены...
}

При сериализации этого объекта OrderEntity в XML-формат генерируются данные вида:

<?xml version="1.0" encoding="utf-8" ?>
<Order ID="10248"
       CustID="VINET"
       xmlns="urn:MyNamespace"
       xmlns:xsd="http://www.w3.org/2001/XMLSchema"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <Ordered>1996-07-04T00:00:00.0000000+01:00</Ordered>
  <OrderDetails> ... те же позиции, что и раньше... </OrderDetails>
  <Shipped>1996-07-16T00:00:00.0000000+01:00</Shipped>
</Order>

Дополнительную информацию об использовании атрибутов, управляющих XML-сериализацией, см. в Attributes that Control XML Serialization.

Как сериализовать компоненты бизнес-объектов в SOAP-формат

В следующем примере кода демонстрируется применение класса SoapFormatter для сериализации объекта OrderEntity в SOAP-формат. Кроме того, SOAP-сериализация выполняется (неявно) при использовании протокола SOAP для приема или передачи объекта Web-сервисом XML и при использовании канала удаленного взаимодействия по протоколу HTTP для приема или передачи объекта сервером Remoting. Можно также указать SOAP-форматирование при работе с каналом удаленного взаимодействия по протоколу TCP.

// Для доступа к классу SoapFormatter
using System.Runtime.Serialization.Formatters.Soap; 
...
// Создаем объект SoapFormatter, используемый для сериалиации объектов типа
// OrderEntity
SoapFormatter formatter = new SoapFormatter();

// Сериализуем объект OrderEntity в SOAP-файл (XML-файл)
// с именем "MySoapOrderEntity.xml"
FileStream stream = File.Create("MySoapOrderEntity.xml");
formatter.Serialize(stream, order);
stream.Close();

Чтобы сериализовать пользовательские компоненты бизнес-объектов в SOAP-формат, пометьте класс бизнес-объекта атрибутом Serializable:

 [Serializable]
public class OrderEntity
{
  // Те же члены, что и раньше

Если требуется настройка SOAP-формата, используемого при сериализации, нужно реализовать в классе бизнес-компонента интерфейс ISerializable. Напишите метод GetObjectData, вызываемый при сериализации объектом SoapFormatter, и специальный конструктор, вызываемый объектом SoapFormatter для воссоздания объекта при десериализации. Ниже показано, как использовать интерфейс ISerializable, метод GetObjectData и специальный конструктор.

// Для доступа к интерфейсу ISerializable и связанным с ним типам
using System.Runtime.Serialization;
...
[Serializable]
public class OrderEntity : ISerializable
{
  // Функция сериализации, вызываемая классом SoapFormatter
  // при сериализации
  void ISerializable.GetObjectData(SerializationInfo info, StreamingContext
ctxt)
  {
    // Добавляем каждое из полей в объект SerializationInfo
    info.AddValue("OrderID", orderID);
    // Если нужно, пишем дополнительный код...
  }

  // Конструктор десериализации, вызываемый классом SoapFormatter
  // при десериализации
deserialization
  public OrderEntity(SerializationInfo info, StreamingContext ctxt)
  {
    // Десериализация объекта SerializationInfo в поля объекта OrderEntity
    orderID = (int)info.GetValue("OrderID", typeof(int));
    // Если нужно, пишем дополнительный код...
  }
  
  // Остальные члены те же, что и раньше...
}

Подробнее о настройке SOAP-сериализации см. в Basic Serialization.

Как сериализовать компоненты бизнес-объектов в двоичный формат

В следующем фрагменте кода показывается применение класса BinaryFormatter для сериализации объекта OrderEntity в двоичный формат. Кроме того, двоичная сериализация выполняется (неявно) при использовании канала удаленного взаимодействия по протоколу TCP для приема или передачи объекта сервером Remoting. Для большей производительности можно указать двоичное форматирование и при использовании канала удаленного взаимодействия по протоколу HTTP.

// Для доступа к классу BinaryFormatter
using System.Runtime.Serialization.Formatters.Binary;
...
// Создаем объект BinaryFormatter, используемый при сериализации объектов
// типа OrderEntity

BinaryFormatter formatter = new BinaryFormatter();

// Сериализуем объект OrderEntity в двоичный файл "MyBinaryOrderEntity.dat"
FileStream stream = File.Create("MyBinaryOrderEntity.dat");
formatter.Serialize(stream, order);
stream.Close();

Чтобы сериализовать пользовательский бизнес-объект в двоичный формат, пометьте пользовательский класс бизнес-объекта атрибутом Serializable. Для настройки двоичного формата, применяемого при сериализации пользовательского бизнес-объекта, реализуйте интерфейс ISerializable по тем же принципам, что и при SOAP-сериализации.

Подробнее о двоичной сериализации см. в Binary Serialization.

Благодарности

Выражаем огромную благодарность следующим участникам и рецензентам: Luca Bolognese, Mike Pizzo, Keith Short, Martin Petersen-Frey (PSS), Pablo De Grande, Bernard Chen (Sapient), Dimitris Georgakopoulos (Sapient), Kenny Jones, Chris Brooks, Lance Hendrix, Pradyumna Siddhartha, Franco A. Ceruti (VBNext), Diego Gonzalez (Lagash) и Chris Schoon.

Также благодарим группу, отвечающую за контент (content team): Chris Sfanos, Angela Crocker, Andy Olsen и Sharon Smith.


Может пригодится:


Автор: Angela Crocker
Прочитано: 13540
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру