Поможем написать учебную работу
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Если у вас возникли сложности с курсовой, контрольной, дипломной, рефератом, отчетом по практике, научно-исследовательской и любой другой работой - мы готовы помочь.
Введение в 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-схеме:
<rsd:Database Name="Northwind" Owner="sa" xmlns:rsd="http://schemas.microsoft.com/data/2002/09/28/rsd"> <rsd:Schema Name="dbo"> <rsd:Tables> <rsd:Table Name="Orders"> <rsd:Columns> <rsd:Column AutoIncrement="true" Name="OrderID" SqlType="int" IncrementStep="1" IncrementSeed="1"/> <rsd:Column AllowDbNull="true" Name="CustomerID" SqlType="nchar" Precision="5"/> <rsd:Column AllowDbNull="true" Name="EmployeeID" SqlType="int"/> <rsd:Column AllowDbNull="true" Name="OrderDate" SqlType="datetime"/> <rsd:Column AllowDbNull="true" Name="RequiredDate" SqlType="datetime"/> <rsd:Column AllowDbNull="true" Name="ShippedDate" SqlType="datetime"/> <rsd:Column AllowDbNull="true" Name="Freight" SqlType="money"> <rsd:Default>0</rsd:Default> </rsd:Column> </rsd:Columns> <rsd:Constraints> <rsd:PrimaryKey Name="PK_Orders"> <rsd:ColumnRef Name="OrderID"/> </rsd:PrimaryKey> <rsd:ForeignKey ForeignTable="Customers" Name="FK_Orders_Customers"> <rsd:ColumnMatch ForeignName="CustomerID" Name="CustomerID"/> </rsd:ForeignKey> </rsd:Constraints> </rsd:Table> <rsd:Table Name="Customers"> <rsd:Columns> <rsd:Column Name="CustomerID" SqlType="nchar" Precision="5"/> <rsd:Column Name="CompanyName" SqlType="nvarchar" Precision="40"/> <rsd:Column AllowDbNull="true" Name="ContactName" SqlType="nvarchar" Precision="30"/> <rsd:Column AllowDbNull="true" Name="Phone" SqlType="nvarchar" Precision="24"/> </rsd:Columns> <rsd:Constraints> <rsd:PrimaryKey Name="PK_Customers"> <rsd:ColumnRef Name="CustomerID"/> </rsd:PrimaryKey> </rsd:Constraints> </rsd:Table> </rsd:Tables> </rsd:Schema> </rsd:Database> |
Эта схема описывает две таблицы из базы данных Northwind (рисунок 2). Для таблиц Customers и Orders описываются исходные поля в БД, первичные ключи, а также связи между таблицами.
Рисунок 2. ER-диаграмма
Кроме этого, понадобится описать OSD-схему, которая будет описывать объекты C#-кода.
<osd:ExtendedObjectSchema Name="DataTypesOSD"
xmlns:osd="http://schemas.microsoft.com/data/2002/09/20/persistenceschema">
<osd:Classes>
<osd:Class Name="Rsdn.Samples.Northwind.Customer">
<osd:Member Name="CustomerID" Key="true" />
<osd:Member Name="Company" />
<osd:Member Name="Name" />
<osd:Member Name="Phone" />
<osd:Member Name="Orders" />
</osd:Class>
<osd:Class Name="Rsdn.Samples.Northwind.Order">
<osd:Member Name="_orderID" KeyType="AutoIncrement"
Hidden="false" Key="true" Alias="OrderID" />
<osd:Member Name="OrderDate" />
<osd:Member Name="RequiredDate" />
<osd:Member Name="ShippedDate" />
<osd:Member Name="EmployeeID" />
<osd:Member Name="Freight" />
<osd:Member Name="Customer" />
</osd:Class>
</osd:Classes>
<osd:ObjectRelationships>
<osd:ObjectRelationship Name="Customers_Orders"
Type="OneToMany" ParentClass="Rsdn.Samples.Northwind.Customer"
ParentMember="Orders" ChildClass="Rsdn.Samples.Northwind.Order"
ChildMember="Customer" />
</osd:ObjectRelationships>
</osd:ExtendedObjectSchema>
Рисунок 3. Объектная модель.
После объявления RSD- и OSD-схем (рисунок 3), нужно создать Mapping-схему, которая определит отображение одной схемы на другую:
<m:MappingSchema xmlns:m="http://schemas.microsoft.com/data/2002/09/28/mapping">
<m:DataSources>
<m:DataSource Name="NorthwindRSD" Type="SQL Server" Direction="Source">
<m:Schema Location="RSD.XML" />
<m:Variable Name="Customers" Select="Customers" />
<m:Variable Name="Orders" Select="Orders" />
<m:Relationship Name="Customers_Orders"
FromVariable="Customers" ToVariable="Orders">
<m:FieldJoin From="CustomerID" To="CustomerID" />
</m:Relationship>
</m:DataSource>
<m:DataSource Name="DataTypesOSD" Type="Object" Direction="Target">
<m:Schema Location="OSD.XML" />
</m:DataSource>
</m:DataSources>
<m:Mappings>
<m:Map SourceVariable="Customers"
TargetSelect="Rsdn.Samples.Northwind.Customer">
<m:FieldMap SourceField="CustomerID" TargetField="CustomerID" />
<m:FieldMap SourceField="CompanyName" TargetField="Company" />
<m:FieldMap SourceField="ContactName" TargetField="Name" />
<m:FieldMap SourceField="Phone" TargetField="Phone" />
</m:Map>
<m:Map SourceVariable="Orders"
TargetSelect="Rsdn.Samples.Northwind.Order">
<m:FieldMap SourceField="OrderID" TargetField="_orderID" />
<m:FieldMap SourceField="OrderDate" TargetField="OrderDate" />
<m:FieldMap SourceField="RequiredDate" TargetField="RequiredDate" />
<m:FieldMap SourceField="ShippedDate" TargetField="ShippedDate" />
<m:FieldMap SourceField="EmployeeID"
TargetField="EmployeeID" NullValue="-1" />
<m:FieldMap SourceField="Freight" TargetField="Freight" />
</m:Map>
<m:RelationshipMap Source="Customers_Orders" Target="Customers_Orders" />
</m:Mappings>
</m:MappingSchema>
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 < #1998.10.10#).
// Для каждого экземпляра класса Customer загружается свойство “Orders”.
Customer cust = (Customer)os.GetObject(typeof(Customer),
"City='Berlin' && Orders.OrderDate < #1998.10.10#", “Orders”);
Во что выливается вызов приведенного выше метода 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”.
<!-- Фрагмент OSD схемы -->
<osd:Class Name="Rsdn.Samples.Northwind.Customer">
<osd:Member Name="CustomerID" Key="true" />
<osd:Member Name="Company" />
<osd:Member Name="Name" />
<osd:Member Name="Phone" />
<osd:Member Name="Orders" LazyLoad=”true” />
</osd:Class>
<osd:Class Name="Rsdn.Samples.Northwind.Order">
<osd:Member Name="_orderID" KeyType="AutoIncrement"
Hidden="false" Key="true" Alias="OrderID" />
<osd:Member Name="OrderDate" />
<osd:Member Name="RequiredDate" />
<osd:Member Name="ShippedDate" />
<osd:Member Name="EmployeeID" />
<osd:Member Name="Freight" />
<osd:Member Name="_customer" Alias=”Customer” LazyLoad=”true” />
</osd:Class>
После этого можно работать с восстановленным объектом как обычно:
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/