Введение в 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/