45810 (665154), страница 2
Текст из файла (страница 2)
Для обращения к источнику данных в 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”.
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