Введение в ObjectSpaces

Введение в ObjectSpaces

Тимофей Казаков (TK)
Сохраняемые объекты

В .NET Framework управление данными осуществляется на
уровне объектов. Каждый объект характеризуется своим состоянием (свойства), поведением
(методы), и является экземпляром какого-либо конкретного класса. В рамках
приложения классы могут различаться по целевому назначению – это могут быть
элементы управления, отображающие интерфейс пользователя, или сервисные классы,
отвечающие за связи с базами данных и работу с сетевыми функциями, это могут
быть классы “сообщений”, обеспечивающие обмен информацией между частями
приложения. Все эти сущности объединяет одна общая черта – время их жизни
обычно не превышает времени жизни всего приложения. Но, кроме вышеперечисленных
категорий классов, можно выделить целый ряд сущностей, время жизни которых
превышает срок жизни приложения. Например, в бизнес-задачах роль подобных
сущностей могут играть объекты “Клиент”, “Заказчик”, “Продукт”. Таким объектам
необходимо предоставить возможность сохранения своего состояния во внешнее
хранилище.

В .NET Framework существуют готовые средства для
работы с сохраняемыми объектами, – есть возможность сохранять состояние
объектов в двоичном виде с использованием BinaryFormatter или XML-формате с
использованием XmlSerializer. Все эти средства предоставляют возможности
сохранения “графов” объектов, однако сохраняемая информация не оптимизирована
для выполнения запросов к хранимым данным – так, поиск необходимой информации в
XML-файле, содержащем несколько тысяч записей, может оказаться неприемлемо
медленным. В большинстве подобных случаев в качестве хранилища информации
подойдет реляционная СУБД – данные сохраняются в таблицах, для дополнительного
контроля целостности между ними устанавливаются отношения, поиск информации
осуществляется с использованием языка запросов SQL. Аналогичную
функциональность предоставляют специальные библиотеки Object/Relational Mapping
(O/R Mapping). Такая библиотека перекладывает на себя всю “черную” работу по
сохранению/загрузке информации из объектной модели приложения в реляционную
модель базы данных. В .NET Framework 1.2 для этих целей есть специальный набор
классов из пространства имен System.ObjectSpaces.*.
ObjectSpaces

Если раньше, используя ADO.NET, нужно было
самостоятельно писать SQL-запросы, то теперь это требование становится
необязательным – ObjectSpaces берут на себя всю заботу об отображении классов
приложения на различные источники данных. При этом мы можем создавать новые объекты,
сохранять их, выполнять различные запросы – все необходимые действия по
взаимодействию с источником данных будут выполняться внутри ObjectSpaces (при
этом данные могут находиться как в традиционной БД, так и быть представленными
в XML форме)

ПРЕДУПРЕЖДЕНИЕ

Текущая версия ObjectSpaces
поддерживает в качестве источника данных только SQL Server 7.0 и выше.

Для объектов приложения ObjectSpaces предоставляет
следующие возможности:

Прозрачное отображение экземпляров .NET объектов на
источник данных.

Поддержку иерархий классов для сохраняемых объектов.

Сохранение взаимосвязей между объектами (один к
одному, один ко многим, многие ко многим).

Отложенную загрузку связанных объектов. Построение
запросов с использованием OPath.
Архитектура ObjectSpaces

Какая функциональность требуется от O/R
Mapping-библиотеки? Кроме очевидных задач – загрузки/сохранения состояния
объекта и выполнения операций поиска, есть и менее очевидные задачи –
отслеживание состояния и идентификация объекта. Для чего это нужно?

Отслеживание состояния требуется для принятия решения
о необходимости сохранения объекта. Совершенно очевидно, что если ни одно из
полей объекта не изменялось, то повторно сохранять ту же информацию совершенно
не обязательно. Информация об оригинальных значениях полей может понадобиться и
для достижения “оптимистической параллельности” (optimistic concurrency) в
ситуациях, когда в БД нет колонки с версией записи. Также можно оптимизировать
сохранение полей объекта для ситуаций, когда один объект отображается на
несколько таблиц в базе данных, просто не обновляя не изменившиеся данные.

ПРИМЕЧАНИЕ

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

В каких случаях нужно уметь идентифицировать объект? В
случае с O/R Mapping-библиотекой мы работаем не с “сырыми” данными, а с
реальными объектами. Это значит, что одному значению первичного ключа в базе
данных должен соответствовать один объект в приложении. В самом деле, разумно
рассчитывать, что все возможные способы получения одного и того же объекта из
базы данных каждый раз должны возвращать одну и ту же ссылку. Это означает, что
O/R Mapping-библиотека должна отслеживать все загружаемые объекты, и в случае
повторной попытки восстановить объект с тем же значением первичного ключа
возвращать ссылку на уже загруженный.

Какие есть пути для реализации подобной
функциональности? Реализация функциональности идентификации объектов
пересекается с реализацией отслеживания состояния объекта и не представляет
особой сложности. Поэтому сосредоточимся на том, какими способами можно
обеспечить отслеживание состояния.

Есть два варианта отслеживания состояния объекта. В
первом варианте инициатором сохранения состояния выступает сам объект. По
второму варианту объект играет пассивную роль, а вся необходимая
функциональность реализуется в библиотеке O/R Mapper. Разберем каждую из двух
реализаций более подробно.

Инициатором сохранения выступает объект. В данной
ситуации O/R Mapper предоставляет механизм хранения оригинальных и текущих
значений, а “сохраняемый” объект выступает лишь интерфейсом для работы c этим
хранилищем. Здесь можно выделить два возможных направления:

“Сохраняемый” класс описывается на некотором метаязыке.
Что это за метаязык, и какие средства работы с ним используются, в общем случае
не столь важно. Отличительной особенностью данной реализации является то, что
весь необходимый код отслеживания состояния генерируется на этапе разработки
(компиляции). В качестве примера подобного подхода можно взять реализацию
Borland Enterprise Core Objects (ECO).

Все свойства, для которых необходимо предоставить
возможности “сохранения” объявляются как абстрактные, сам такой класс также
становится абстрактным (MustInherit в VisualBasic). На O/R Mapping-библиотеку в
такой ситуации ложится ответственность за создание наследника “сохраняемого”
(например, через Reflection.Emit) класса с неизбежной реализацией всех
сохраняемых свойств, и предоставление фабрики класса для его создания. Найти
реализацию подобной функциональности можно в ранней preview-версии ObjectSpaces
и в EntityBroker (http://www.thona-consulting.com).

У каждого из этих направлений есть свои достоинства и
недостатки. Плюсом первого направления является более быстрый запуск, т.к. весь
необходимый код сгенерирован еще на этапе компиляции. Между тем, этот плюс
может стать и минусом – если по какой-то причине реализация “отслеживания”
состояния изменится, придется повторить операцию генерации кода сохраняемого
класса и перекомпилировать его. В случае использования второго направления
будет достаточно заменить реализацию O/R Mapper.

Объект играет пассивную роль, вся реализация – в O/R
Mapper. В этой ситуации к “сохраняемому” классу не предъявляется никаких
дополнительных требований. Основной плюс данного подхода состоит в том, что для
добавления “сохраняемости” к существующему классу его не надо никак
модифицировать (максимальная прозрачность в использовании O/R Mapper), а минус
состоит в том, что для сохранения закрытых полей класса приходится использовать
механизмы рефлексии, что может отрицательно сказаться на производительности.

В ObjectSpaces выбран второй вариант. В основе
реализации ObjectSpaces (Рисунок 1) лежат три основных класса: ObjectEngine,
ObjectContext и ObjectSpace.

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

ObjectContext – это реализация механизмов
идентификации и отслеживания состояния “сохраняемых” классов.

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

Рисунок 1 Архитектура ObjectSpaces.

Схемы данных

Для большинства приложений описать однозначное (“один
к одному”) отображение объектной модели данных на реляционную модель нельзя,
иногда нужно специально описывать то, как объекты должны отображаться на
источник данных. В ObjectSpaces эту задачу выполняет класс MappingSchema
(пространство имен System.Data.Mapping). Данный класс предназначен для
описания:

RSD (Relational Schema Definition) – схемы, которая
описывает таблицы, поля и отношения между ними;

OSD (Object Schema
Definition) – схемы, описывающей объекты;

MSD (Mapping Schema
Definition) – схемы отображения.

ObjectSpaces дает возможность самостоятельно
формировать состояние класса MappingSchema или загружать его состояние из
XML-файла. Рассмотрим использование MappingSchema на основе базы данных
Northwind из состава SQL Server. На первом этапе нужно описать структуру этой
базы данных в RSD-схеме:

 xmlns:rsd=”http://schemas.microsoft.com/data/2002/09/28/rsd”>

 

 

  

   

    

          
IncrementStep=”1″ IncrementSeed=”1″/>

    

          
Precision=”5″/>

    

    

    

    

    

     
0

    

   

   

    

     

    

    

     

    

   

  

  

   

    

    

    

          
SqlType=”nvarchar” Precision=”30″/>

    

           Precision=”24″/>

   

   

    

     

    

   

  

 

 

Эта схема описывает две таблицы из базы данных
Northwind (рисунок 2). Для таблиц Customers и Orders описываются исходные поля
в БД, первичные ключи, а также связи между таблицами.

Рисунок 2. ER-диаграмма

Кроме этого, понадобится описать OSD-схему, которая
будет описывать объекты C#-кода.

 xmlns:osd=”http://schemas.microsoft.com/data/2002/09/20/persistenceschema”>

 

 

  

  

  

  

  

 

 

  

        
Hidden=”false” Key=”true”
Alias=”OrderID” />

  

  

  

  

  

  

 

 

 

 

     Type=”OneToMany”
ParentClass=”Rsdn.Samples.Northwind.Customer”

    
ParentMember=”Orders”
ChildClass=”Rsdn.Samples.Northwind.Order”

    
ChildMember=”Customer” />

 

Рисунок 3. Объектная модель.

После объявления RSD- и OSD-схем (рисунок 3), нужно
создать Mapping-схему, которая определит отображение одной схемы на другую:

 

 

  

  

  

  

          
FromVariable=”Customers” ToVariable=”Orders”>

   

  

 

 

  

 

 

 

 

      TargetSelect=”Rsdn.Samples.Northwind.Customer”>

  

  

  

  

 

 

     
TargetSelect=”Rsdn.Samples.Northwind.Order”>

  

  

   

  

  

        
TargetField=”EmployeeID” NullValue=”-1″ />

  

 

 

 

OPath

Одна из основных задач при работе с информацией – это
создание запросов для выборки необходимых данных. Так, в случае РСУБД можно
использовать язык запросов SQL, для выборки информации из XML-источников у нас
есть XPath. Но как SQL, так и XPath – это языки запросов, которые слишком
сильно привязаны к модели хранения данных и, как результат, для O/R Mapper
приходится применять специальный язык запросов, который позволит создавать
запросы к данным в терминах объектной модели приложения и легко транслировать
их в язык, понимаемый хранилищем данных (для ObjectSpaces и MS SQL Server это
SQL).

Для обращения к источнику данных в ObjectSpaces
используется специальный язык запросов – OPath. Синтаксис этого языка
(отдаленно он напоминает XPath) позволяет выполнять запросы к источнику данных,
основываясь на иерархии классов, используемых в приложении. В настоящее время
OPath поддерживает следующий набор операторов (для операторов может
использоваться синтаксис как C#, так и VB.NET):

Оператор в C# стиле

Оператор в VB стиле

Описание

.

[]

.

()

Операторы группировки
используются для разделения свойств и группировки выражений.
Например:Customer[CustomerID=’alfki’].Orders.ShipDate>#11/12/2003#

!

not

Логическое отрицание. not (Customer[CustomerID=’alfki’])

*

/

%

*

/

MOD

Умножение, деление,
получение модуля

+

+

Сложение, вычитание

>

>=

>

>=

Сравнение двух
значенийCustomer.CreateDate > #12/09/2002#

=

!=

==

=

==

Равенство двух значений

&&

||

and

or

Customer.Region = ‘ru’ || Customer.Region = ‘en’

^

^

Символ ^ используется для
обозначений родитель/потомок. В случае использования оператора ^ следующие
два выражения эквивалентны:Orders.OrderDetail[^.OrderDate >
#1/1/2003#]Orders.[OrderDate > #1/1/2003#]

ObjectSpace

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

Создание экземпляра ObjectSpace

Для создания экземпляра ObjectSpace нужно иметь три
схемы – RSD, OSD и MSD (при желании их можно скомбинировать в одном XML-файле),
а также экземпляр SqlConnection для взаимодействия с источником данных.

// Создание экземпляра класса ObjectSpaces

using (SqlConnection conn = new SqlConnection(

 “Data Source=tim;
Integrated Security=SSPI; Database=northwind”))

{

 ObjectSpace os = new
ObjectSpace(“map.xml”, conn);

 // Работаем с os. Явно открывать подключение SqlConnection не
обязательно.

 // Это происходит автоматически.

}

Запрос к источнику данных

После инициализации экземпляра ObjectSpace можно
обратиться к источнику данных. Для этого у класса ObjectSpace есть три метода
GetObject, GetObjectReader, GetObjectSet которые позволяют получать данные в
виде трех различных форм – одиночный объект, курсор или список.

 // Определим “сохраняемые” объекты, которые
будем использовать в дальнейшем

 public class Customer

 {

  public string CustomerID;

  public string Name;

  public string Company;

  public string Phone;

  public string Fax;

  public ArrayList Orders = new
ArrayList();

 }

 public class Order

 {

  private int _orderID = 0;

  public int OrderID

  {

   get { return _orderID; }

  }

  public DateTime OrderDate;

  public DateTime RequiredDate;

  public DateTime ShippedDate;

  public Decimal Freight;

  public int EmployeeID;

  public Customer Customer;

}

// Извлекаем объект
Customer (включая подчиненное свойство Orders)

// на основе OPath-запроса (City=’Berlin’
&& Orders.OrderDate

// Для каждого экземпляра
класса Customer загружается свойство “Orders”.

Customer cust = (Customer)os.GetObject(typeof(Customer),

   “City=’Berlin’
&& Orders.OrderDate

Во что выливается вызов приведенного выше метода
os.GetObject? Используя Profiler из MS SQL Server, можно увидеть, что в БД
будет выполнен следующий SQL-запрос (отформатирован для приведения в более
“читаемый” вид):

exec sp_executesql

N’select Customers.[CustomerID],

   Customers.[CompanyName],

   Customers.[ContactName],

   Customers.[City],

   Customers.[Phone]

  from
[Northwind].[dbo].[Customers] as Customers

  where ((Customers.[City]) =
(@p0))

    AND (EXISTS(

       select Orders.[OrderID],
Orders.[CustomerID]

        from
[Northwind].[dbo].[Orders] as Orders

        where ((Customers.[CustomerID])
= (Orders.[CustomerID]))

         AND
((Orders.[OrderDate]) > (@p1))))

 order by 1;

select Customers.[CustomerID],

  Orders.[OrderID],

  Orders.[CustomerID],

  Orders.[RequiredDate],

  Orders.[ShippedDate],

  Orders.[OrderDate]

 from
[Northwind].[dbo].[Customers] as Customers,

    [Northwind].[dbo].[Orders]
as Orders

 where (((Customers.[City]) =
(@p0))

  AND (EXISTS(

   select Orders.[OrderID],
Orders.[CustomerID]

    from
[Northwind].[dbo].[Orders] as Orders

    where
((Customers.[CustomerID]) = (Orders.[CustomerID]))

     AND
((Orders.[OrderDate])>(@p1)) )))

     AND
((Customers.[CustomerID])=(Orders.[CustomerID]))

 order by 1, 2, 3 ;’,

N’@p0 nvarchar(6),@p1 datetime’, @p0 = N’Berlin’,

 @p1 = ‘Oct 10 1998 12:00:00:000AM’

Создание записей в базе данных

Одно из больших преимуществ в использовании
ObjectSpaces состоит в том, что для добавления объекту свойств “сохраняемости”
его не надо специальным образом модифицировать (наследовать от специального базового
класса, специальным образом размечать свойства или поля). Подобная прозрачность
реализации ObjectSpaces дает преимущества в использовании.

// Работа с объектами
Customer и Orders не зависит

// от того, используется
ObjectSpaces или нет

Customer cust = new Customer();

Order ord = new Order();

cust.Id = “ALFQI”;

cust.Name = “MyName”;

cust.Company = “MyCompany”;

cust.Phone = “MyPhone”;

cust.Fax = “MyFax”;

ord.Customer = cust;

ord.OrderDate = DateTime.Now;

ord.ShippedDate = DateTime.Now;

ord.RequiredDate = DateTime.Now;

cust.Orders.Add(ord);

// Перед сохранением
объектов необходимо поместить их в контекст

// ObjectSpaces. Флаг
InitialState.Inserted показывает, что мы добавляем новую

// запись в базу данных

os.StartTracking(ord, InitialState.Inserted);

os.StartTracking(cust, InitialState.Inserted);

// Сохраняем экземпляр класса Customer.

// Параметр
PersistenceOptions(Depth.ObjectGraph) сообщает,

// что будет сохранен весь
граф объектов.

os.PersistChanges(cust, new PersistenceOptions(Depth.ObjectGraph));

Удаление записей с использованием ObjectSpaces

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

ПРИМЕЧАНИЕ

Для удаления объекта из базы данных его
необходимо предварительно добавить в контекст ObjectSpaces. Это можно
сделать, используя методы GetObject, GetObjectReader, GetObjectSet класса
ObjectSpace, или добавить объект в контекст самостоятельно с помощью метода
StartTracking

Customer
cust = new Customer();

cust.Id
= “ALFQI”;

// Перед операцией над объектом
необходимо поместить его в контекст

// ObjectSpaces. Флаг
InitialState.Unchanged показывает, что объект ранее

// был сохранен в базе данных

os.StartTracking(cust,
InitialState.Unchanged);

// Помечаем экземпляр класса Customer
как удаляемый.

os.MarkForDeletion(cust);

// Сохраняем изменения в базе данных

os.PersistChanges(cust);

Отложенная загрузка данных

Отложенная загрузка данных – это очень полезная
возможность, реализованная в ObjectSpaces. Правда, использование этой
функциональности омрачается ее недостаточной “прозрачностью”. Это значит, что в
случае, когда необходимо подгружать зависимые классы по требованию, придется
модифицировать исходный код. К счастью, модификации незначительны.

public class Customer

{

 public string CustomerID;

 public string Name;

 public string Company;

 public string Phone;

 public string Fax;

 // Для отложенной загрузки списка заказов необходимо перейти

 // от использования ArrayList к
использованию специального класса из

 // ObjectSpaces – ObjectList.

 public ObjectList Orders = new
ObjectList();

}

public class Order

{

 private int _orderID = 0;

 public int OrderID

 {

  get {return _orderID;}

 }

 public DateTime OrderDate;

 public DateTime RequiredDate;

 public DateTime ShippedDate;

 public Decimal Freight;

 public int EmployeeID;

 // Для отложенной загрузки класса Customer, мы меняем тип поля с Customer

 // на ObjectHolder. Именно ObjectHolder будет отвечать за подгрузку
нужных

 // данных.

 private ObjectHolder _customer
= new ObjectHolder();

 public Customer Customer

 {

  get {return (Customer)
_customer.InnerObject;}

  set {_customer.InnerObject = value;}

 }

}

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

 

 

 

 

 

 

       Hidden=”false”
Key=”true” Alias=”OrderID” />

 

 

 

 

 

 

После этого можно работать с восстановленным объектом
как обычно:

using (SqlConnection conn = new SqlConnection(

 “Data Source=tim;
Integrated Security=SSPI; Database=northwind”))

{

 ObjectSpace os = new ObjectSpace(“map.xml”,
conn);

 Customer cust =
(Customer)os.GetObject(typeof(Customer),

  “CustomerID=’alfki’”);

 // Список заказов загрузится при первом
обращении

 foreach (Order order in cust.Orders)

 {

  Console.WriteLine(“Customer:
{0}, OrderDate: {1}”,

   order.Customer.Name,
order.OrderDate);

 }

}

Метод

Описание

BeginTransaction, Commit,
Rollback

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

GetObject

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

GetObjectReader

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

GetObjectSet

Получить объекты из БД в
виде единого массива. В отличии от ArrayList, класс ObjectSet предоставляет
дополнительные возможности отслеживания оригинальных значений, передачи
изменений через Remoting и некоторые другие.

PersistChanges

Сохранить измененный объект
в БД.

MarkForDeletion

Пометить объект для
удаления. Реальное удаление происходит при вызове PersistChanges.

Resync

Синхронизировать состочние
объекта с информацией из БД.

StartTracking

“Пометить” объект как сохраняемый.
Кроме текущих значений, в контексте сохраняется и состояние объекта
(новый/измененный/удаленный/без изменений)

Дополнительные возможности ObjectSpaces

Чтение данных с использованием DbObjectReader

В отдельных случаях использование класса ObjectSpace
может оказаться избыточным или неудобным. Например, если для доступа к базе
данных необходимо использовать хранимые процедуры, большая часть
функциональности ObjectSpaces окажется ненужной. Но и для подобных ситуаций в
ObjectSpaces есть свое решение. Если требуется извлекать из произвольного
источника данных информацию в виде объектов приложения, можно использовать
класс DbObjectReader. Выступая как тонкая прослойка между ADO.NET-курсором
(IDataReader) и классами приложения, DbObjectReader позволяет загружать
сохраняемые объекты из источников данных, которые не поддерживаются
ObjectSpaces напрямую.

public static void Main()

{

 DataTable table = new
DataTable();

 table.Columns.Add(“CustomerID”,
typeof(int));

 table.Columns.Add(“CompanyName”,
typeof(string));

 table.Columns.Add(“ContactName”,
typeof(string));

 table.Columns.Add(“Phone”,
typeof(string));

 table.Rows.Add(new object[] {
1, “MyCompany”, “MyCustomer”, “222 33 22” });

 using (IDataReader reader =
table.GetDataReader())

 {

  DbObjectReader objectReader = new
DbObjectReader(reader,

   typeof(Customer), new
MappingSchema(“map.xml”));

  while (objectReader.Read())

  {

   Customer cust =
(Customer)objectReader.Current;

   Console.WriteLine(cust.Name);

  }

 }

}

ObjectEngine

Класс ObjectEngine лежит в основе ObjectSpaces и
реализует механизмы взаимодействия с источником данных. В большинстве случаев
ObjectEngine напрямую не используется, но в ситуациях, когда необходимо
выполнить OPath-запрос или сохранить объект в БД в обход основной
функциональности ObjectSpaces и с минимальными издержками – использование
ObjectEngine может пригодиться.

// Небольшой пример
использования функциональности ObjectEngine

public static void Main()

{

 using (SqlConnection conn = new
SqlConnection(

  “Data Source=tim;
Integrated Security=SSPI; Database=northwind”))

 {

  conn.Open();

  // Учитывая, что ObjectEngine – это
“низкоуровневый” класс, некоторую часть

  // подготовительной работы приходится
выполнять самостоятельно.

  ObjectContext context =

   new CommonObjectContext(new
ObjectSchema(“osd.xml”));

  MappingSchema msd = new
MappingSchema(“map.xml”);

  ObjectSchema osd = new
ObjectSchema(“osd.xml”);

  ObjectSources sources = new
ObjectSources();

 
sources.Add(“NorthwindRSD”, conn);

  // Создаем OPath запрос и читаем данные из БД

  ObjectExpression expr = OPath.Parse(

    new
ObjectQuery(typeof(Customer), “”, “”), osd);

  // Еще одна издержка ObjectEngine – перед использованием OPath

  // запрос надо “компилировать”.

  CompiledQuery query = expr.Compile(msd);

  Customer cust = null;

  // Выполняем OPath-запрос, используя
“объектный” курсор.

  using (ObjectReader reader =

  
ObjectEngine.GetObjectReader(sources, context, query, new object[] {
}))

  {

   while (reader.Read())

   {

    cust =
(Customer)reader.Current;

    Console.WriteLine(cust.Name);

   }

  }

  // Cоздаем объект и сохраняем его в
источнике данных

  cust = new Customer();

  cust.CustomerID =
“alfq”;

  cust.Name =
“MyName”;

  cust.Phone =
“MyPhone”;

  cust.Company = “MyComp”;

  context.Add(cust,
ObjectState.Inserted);

 
ObjectEngine.PersistChanges(msd, sources, context,

   new object[] { cust },
PersistenceOptions.Default);

 }

}

Расширения ObjectSpaces

Использование нескольких XML-схем для описания
структуры классов приложения, реляционной структуры БД, а кроме того еще и
Mapping-схемы, не может не удручать. Конечно, в финальной версии .NET Framework
1.2 возможности визуального проектирования этих схем должны обязательно
появиться, но пока их нет, можно воспользоваться сторонними средствами. Одно из
таких средств входит в пример ObjectSpacesPDCSamples.zip (файл можно найти на http://www.gotdotnet.com).

В состав этого примера входит специальная утилита для
создания всех необходимых XML-схем (рисунок 4).

Рисунок 4. Microsoft ObjectSpaces Mapper Utility.

Кроме этого, в данный пример входит реализация класса
ObjectPersistence. Этот класс обладает одной характерной особенностью – он
скрывает в себе не только создание XML-описаний, но и создание необходимой базы
данных. Рассмотрим простейший пример использования ObjectPersistence.

using System;

using Microsoft.ObjectSpaces.ObjectPersistence;

class ObjectPersistenceDemo

{

 // Исходный код класса ObjectPersistence также доступен в рамках примера

 static ObjectPersistence op =
new

  ObjectPersistence(“Data
Source=local; Integrated Security=true;”,

  “Persistence”);

 static void Main(string[] args)

 {

  Customer c = new Customer();

  // Ищем заказчика в базе данных

  c = (Customer)op.LoadObject(typeof(Customer),
“CustomerID = ‘alfki'”);

  if (c == null)

  {

   c = new
Customer(“alfki”);

   c.Comments = “New
Customer”;

  }

  else

  {

   c.Comments = “Old
Customer”;

  }

 

  // Сохраняем изменения.

  // Если база данных/таблица еще не созданы, то это произойдет сейчас

  op.Persist(c);

 }

}

Класс ObjectPersistence спроектирован таким образом,
что для его использования не обязательно предварительно создавать базу данных,
настраивать XML-схемы данных – все это делается внутри реализации
ObjectPersistence. Так, в приведенном выше примере на SQL Server будет создана
база данных Persistence, и в нее будет добавлена таблица с именем Customer.
Конечно, не в каждом проекте можно допустить подобные вольности со стороны
библиотеки доступа к данным, но для простейших реализаций – это замечательная
возможность скрыть ненужные детали.

Итог

Технологии доступа к данным в .NET Framework 1.2
содержат множество полезных нововведений, но если для ADO.NET это скорее
эволюционные изменения, связанные с простым расширением библиотеки, то
ObjectSpaces является совершенно новым продуктом, который может кардинальным
образом изменить подход к работе с данными. Конечно, в настоящий момент работа
над библиотекой еще далека от завершения. К моменту выхода VisualStudio
«Whidbey» мы сможем увидеть в ней массу изменений, начиная с использования
generics и расширения возможностей OPath, и заканчивая DML-операторами для
удаления объектов без предварительного их извлечения.
Список литературы

Для подготовки данной работы были использованы
материалы с сайта http://www.rsdn.ru/