Отложенная загрузка данных – это очень полезная возможность, реализованная в 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 лежит в основе ObjectSpaces и реализует механизмы взаимодействия с источником данных. В большинстве случаев ObjectEngine напрямую не используется, но в ситуациях, когда необходимо выполнить OPath-запрос или сохранить объект в БД в обход основной функциональности ObjectSpaces и с минимальными издержками – использование ObjectEngine может пригодиться.
// Небольшой пример использования функциональности ObjectEnginepublic 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);}} |
Использование нескольких 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-операторами для удаления объектов без предварительного их извлечения.