TargetField = "EmployeeID" NullValue = "-1" /> p>
p>
m: Map> p>
p>
m: Mappings> p>
m: MappingSchema> p>
OPath
p>
Одне з основних завдань при роботі з інформацією - це
створення запитів для вибірки необхідних даних. Так, у випадку РСУБД можна
використовувати мову запитів SQL, для вибірки інформації з XML-джерел у нас
є XPath. Але як SQL, так і XPath - це мови запитів, які занадто
сильно прив'язані до моделі зберігання даних і, як результат, для O/R Mapper
доводиться застосовувати спеціальна мова запитів, який дозволить створювати
запити до даних в термінах об'єктної моделі програми, але легко транслювати
їх у мову, що розуміється сховищем даних (для ObjectSpaces і MS SQL Server це
SQL). P>
Для звернення до джерела даних в ObjectSpaces
використовується спеціальна мова запитів - OPath. Синтаксис цієї мови
(віддалено він нагадує XPath) дозволяє виконувати запити до джерела даних,
грунтуючись на ієрархії класів, що використовуються в додатку. В даний час
OPath підтримує наступний набір операторів (для операторів може
використовуватися синтаксис як C #, так і VB.NET): p>
Оператор в C # стилі p>
Оператор в VB стилі p>
Опис p>
. p>
[] p>
. p>
() p>
Оператори угруповання
використовуються для позначення властивостей і угруповання виразів.
Наприклад: Customer [CustomerID = 'alfki']. Orders.ShipDate> # 11/12/2003 # p>
! p>
not p>
Логічне заперечення. not (Customer [CustomerID = 'alfki']) p>
* p>
/ p>
% p>
* p>
/ p>
MOD p>
Множення, ділення,
отримання модуля p>
+ p>
- p>
+ p>
- p>
Додавання, віднімання p>
< p>
> p>
<= p>
> = p>
< p>
> p>
<= p>
> = p>
Порівняння двох
значенійCustomer.CreateDate> # 12/09/2002 # p>
= p>
! = p>
== p>
= p>
<> p>
== p>
Рівність двох значень p>
& & p>
| | p>
and p>
or p>
Customer.Region = 'ru' | | Customer.Region = 'en' p>
^ p>
^ p>
Символ ^ використовується для
позначень батьків/нащадок. У випадку використання оператора ^ наступні
два вирази еквівалентні: Orders.OrderDetail [^. OrderDate>
# 1/1/2003 #] Orders. [OrderDate> # 1/1/2003 #] p>
ObjectSpace
p>
При роботі з зберігаються об'єктами нам потрібні
наступні можливості - завантаження збережених об'єктів, відстеження стану і
повернення змін назад, до бази даних. Клас ObjectSpace об'єднує в собі
всі ці можливості. Розглянемо окремі моменти роботи з цим класом. P>
Створення екземпляра ObjectSpace p>
Для створення екземпляра ObjectSpace потрібно мати три
схеми - RSD, OSD і MSD (при бажанні їх можна скомбінуйтеовать в одному XML-файлі),
а також примірник SqlConnection для взаємодії з джерелом даних. p>
// Створення екземпляра класу ObjectSpaces p>
using (SqlConnection conn = new SqlConnection ( p>
"Data Source = tim;
Integrated Security = SSPI; Database = northwind ")) p>
( p>
ObjectSpace os = new
ObjectSpace ( "map.xml", conn); p>
// Працюємо з os. Явно відкривати підключення SqlConnection НЕ
обов'язково. p>
// Це відбувається автоматично. p>
) p>
Запит до джерела даних p>
Після ініціалізації екземпляра ObjectSpace можна
звернутися до джерела даних. Для цього у класу ObjectSpace є три методи
GetObject, GetObjectReader, GetObjectSet які дозволяють одержувати дані в
вигляді трьох різних форм - одиночний об'єкт, курсор або список. p>
// Визначимо "зберігаються" об'єкти, які
будемо використовувати надалі p>
public class Customer p>
( p>
public string CustomerID; p>
public string Name; p>
public string Company; p>
public string Phone; p>
public string Fax; p>
public ArrayList Orders = new
ArrayList (); p>
) p>
public class Order p>
( p>
private int _orderID = 0; p>
public int OrderID p>
( p>
get (return _orderID;) p>
) p>
public DateTime OrderDate; p>
public DateTime RequiredDate; p>
public DateTime ShippedDate; p>
public Decimal Freight; p>
public int EmployeeID; p>
public Customer Customer; p>
) p>
// Витягуємо об'єкт
Customer (включаючи підпорядковане властивість Orders) p>
// на основі OPath-запиту (City = 'Berlin'
& & Orders.OrderDate <# 1998.10.10 #). p>
// Для кожного примірника
класу Customer завантажується властивість "Orders". p>
Customer cust = (Customer) os.GetObject (typeof (Customer), p>
"City = 'Berlin'
& & Orders.OrderDate <# 1998.10.10 # "," Orders "); p>
У що виливається виклик наведеного вище методу
os.GetObject? Використовуючи Profiler з MS SQL Server, можна побачити, що в БД
буде виконаний наступний SQL-запит (відформатовано для приведення в більш
"Читається" вид): p>
exec sp_executesql p>
N'select Customers. [CustomerID], p>
Customers. [CompanyName], p>
Customers. [ContactName], p>
Customers. [City], p>
Customers. [Phone] p>
from
[Northwind]. [Dbo]. [Customers] as Customers p>
where ((Customers. [City]) =
(@ p0)) p>
AND (EXISTS ( p>
select Orders. [OrderID],
Orders. [CustomerID] p>
from
[Northwind]. [Dbo]. [Orders] as Orders p>
where ((Customers. [CustomerID])
= (Orders. [CustomerID ])) p>
AND
((Orders. [OrderDate])> (@ p1)))) p>
order by 1; p>
select Customers. [CustomerID], p>
Orders. [OrderID], p>
Orders. [CustomerID], p>
Orders. [RequiredDate], p>
Orders. [ShippedDate], p>
Orders. [OrderDate] p>
from
[Northwind]. [Dbo]. [Customers] as Customers, p>
[Northwind]. [dbo]. [Orders]
as Orders p>
where (((Customers. [City]) =
(@ p0)) p>
AND (EXISTS ( p>
select Orders. [OrderID],
Orders. [CustomerID] p>
from
[Northwind]. [Dbo]. [Orders] as Orders p>
where
((Customers. [CustomerID]) = (Orders. [CustomerID ])) p>
AND
((Orders. [OrderDate ])>(p1)) ))) p>
AND
((Customers. [CustomerID]) = (Orders. [CustomerID])) p>
order by 1, 2, 3; ', p>
N '@ p0 nvarchar (6), @ p1 datetime', @ p0 = N'Berlin ', p>
@ p1 = 'Oct 10 1998 12:00:00:000 AM' p>
Створення записів в базі даних p>
Одне з великих переваг у використанні
ObjectSpaces полягає в тому, що для додавання об'єкту властивостей "зберігання"
його не треба спеціальним чином модифіковані (успадковувати від спеціального базового
класу, спеціальним чином розмічати властивості або поля). Подібна прозорість
реалізації ObjectSpaces дає переваги у використанні. p>
// Робота з об'єктами
Customer і Orders не залежить p>
// від того, використовується
ObjectSpaces чи ні p>
Customer cust = new Customer (); p>
Order ord = new Order (); p>
cust.Id = "ALFQI"; p>
cust.Name = "MyName"; p>
cust.Company = "MyCompany"; p>
cust.Phone = "MyPhone"; p>
cust.Fax = "MyFax"; p>
ord.Customer = cust; p>
ord.OrderDate = DateTime.Now; p>
ord.ShippedDate = DateTime.Now; p>
ord.RequiredDate = DateTime.Now; p>
cust.Orders.Add (ord); p>
// Перед збереженням
об'єктів необхідно помістити їх в контекст p>
// ObjectSpaces. Прапор
InitialState.Inserted показує, що ми додаємо нову p>
// запис в базу даних p>
os.StartTracking (ord, InitialState.Inserted); p>
os.StartTracking (cust, InitialState.Inserted); p>
// Зберігаємо екземпляр класу Customer. p>
// Параметр
PersistenceOptions (Depth.ObjectGraph) повідомляє, p>
// що буде збережений весь
граф об'єктів. p>
os.PersistChanges (cust, new PersistenceOptions (Depth.ObjectGraph )); p>
Видалення записів з використанням ObjectSpaces p>
Існуюча версія ObjectSpaces підтримує видалення
об'єктів тільки в тому випадку, якщо вони раніше були додані в контекст
ObjectSpaces. p>
ПРИМІТКА p>
Щоб видалити об'єкт з бази даних його
необхідно попередньо додати в контекст ObjectSpaces. Це можна
зробити, використовуючи методи GetObject, GetObjectReader, GetObjectSet класу
ObjectSpace, або додати об'єкт в контекст самостійно за допомогою методу
StartTracking p>
Customer
cust = new Customer (); p>
cust.Id
= "ALFQI"; p>
// Перед операцією над об'єктом
необхідно помістити його в контекст p>
// ObjectSpaces. Прапор
InitialState.Unchanged показує, що об'єкт раніше p>
// був збережений у базі даних p>
os.StartTracking (cust,
InitialState.Unchanged); p>
// Позначає екземпляр класу Customer
як видаляється. p>
os.MarkForDeletion (cust); p>
// Зберігаємо зміни в базі даних p>
os.PersistChanges (cust); p>
Відкладена завантаження даних p>
Відкладена завантаження даних - це дуже корисна
можливість, реалізована в ObjectSpaces. Правда, використання цієї
функціональності затьмарюється її недостатньою "прозорістю". Це означає, що в
випадку, коли необхідно довантажувати залежні класи на вимогу, доведеться
модифіковані вихідний код. На щастя, модифікації незначні. p>
public class Customer p>
( p>
public string CustomerID; p>
public string Name; p>
public string Company; p>
public string Phone; p>
public string Fax; p>
// Для відкладеної завантаження списку замовлень необхідно перейти p>
// від використання ArrayList до
використання спеціального класу з p>
// ObjectSpaces - ObjectList. p>
public ObjectList Orders = new
ObjectList (); p>
) p>
public class Order p>
( p>
private int _orderID = 0; p>
public int OrderID p>
( p>
get (return _orderID;) p>
) p>
public DateTime OrderDate; p>
public DateTime RequiredDate; p>
public DateTime ShippedDate; p>
public Decimal Freight; p>
public int EmployeeID; p>
// Для відкладеної завантаження класу Customer, ми змінюємо тип поля з Customer p>
// на ObjectHolder. Саме ObjectHolder буде відповідати за підвантаження
потрібних p>
// даних. p>
private ObjectHolder _customer
= New ObjectHolder (); p>
public Customer Customer p>
( p>
get (return (Customer)
_customer.InnerObject;) p>
set (_customer.InnerObject = value;) p>
) p>
) p>
Крім зміни коду програми, відкладену завантаження
властивостей слід оголосити в OSD-схемі. Для цього потрібно додати опис полів
спеціальний атрибут LazyLoad = "true". p>
p>
p>
p>
p>
p>
p>
p>
osd: Class> p>
p>
Hidden = "false"
Key = "true" Alias = "OrderID" /> p>
p>
p>
p>
p>
p>
p>
osd: Class> p>
Після цього можна працювати з відновленим об'єктом
як завжди: p>
using (SqlConnection conn = new SqlConnection ( p>
"Data Source = tim;
Integrated Security = SSPI; Database = northwind ")) p>
( p>
ObjectSpace os = new ObjectSpace ( "map.xml",
conn); p>
Customer cust =
(Customer) os.GetObject (typeof (Customer), p>
"CustomerID = 'alfki'"); p>
// Список замовлень завантажиться при першому
зверненні p>
foreach (Order order in cust.Orders) p>
( p>
Console.WriteLine ( "Customer:
(0), OrderDate: (1) ", p>
order.Customer.Name,
order.OrderDate); p>
) p>
) p>
Метод p>
Опис p>
BeginTransaction, Commit,
Rollback p>
Управління транзакціями.
Варто звернути увагу, що метод Rollback НЕ відкочується зміни в
зберігаються об'єктах, тому можуть виникати ситуації, коли інформація в БД і
інформація, що зберігаються в об'єктах виявляться неузгодженості. Тому, по
уникнути конфліктів, рекомендується після Rollback створювати новий екземпляр
ObjectSpaces. P>
GetObject p>
Отримати одиночний об'єкт
заданого типу з бази даних. У параметрах методу можна передати як
OPath-запит, так і список дочірніх об'єктів, які повинні бути завантажені
одночасно з запитуваним об'єктом. p>
GetObjectReader p>
Отримати з бази даних
об'єкти через курсор, використовуючи семантику, аналогічну що використовується при роботі
з IDataReader. p>
GetObjectSet p>
Отримати об'єкти з БД в
вигляді єдиного масиву. На відміну від ArrayList, клас ObjectSet надає
додаткові можливості відстеження оригінальних значень, передачі
змін через Remoting і деякі інші. p>
PersistChanges p>
Зберегти змінений об'єкт
в БД. p>
MarkForDeletion p>
Позначити об'єкт для
видалення. Реальне видалення відбувається при виклику PersistChanges. P>
Resync p>
Синхронізувати состочніе
об'єкта з інформацією з БД. p>
StartTracking p>
"Позначити" об'єкт як зберігається.
Крім поточних значень, у контексті зберігається і стан об'єкта
(новий/змінений/вилучений/без змін) p>
Додаткові можливості ObjectSpaces
p>
Читання даних з використанням DbObjectReader
p>
В окремих випадках використання класу ObjectSpace
може виявитися надмірним або незручним. Наприклад, якщо для доступу до бази
даних необхідно використовувати процедури, що зберігаються, більша частина
функціональності ObjectSpaces виявиться непотрібною. Але і для подібних ситуацій у
ObjectSpaces є своє рішення. Якщо потрібно витягувати з довільного
джерела даних інформацію у вигляді об'єктів додатка, можна використовувати
клас DbObjectReader. Виступаючи як тонкий прошарок між ADO.NET-курсором
(IDataReader) і класами програми, DbObjectReader дозволяє завантажувати
зберігаються об'єкти з джерел даних, які не підтримуються
ObjectSpaces напряму. P>
public static void Main () p>
( p>
DataTable table = new
DataTable (); p>
table.Columns.Add ( "CustomerID",
typeof (int )); p>
table.Columns.Add ( "CompanyName",
typeof (string )); p>
table.Columns.Add ( "ContactName",
typeof (string )); p>
table.Columns.Add ( "Phone",
typeof (string )); p>
table.Rows.Add (new object [] (
1, "MyCompany", "MyCustomer", "222 33 22" }); p>
using (IDataReader reader =
table.GetDataReader ()) p>
( p>
DbObjectReader objectReader = new
DbObjectReader (reader, p>
typeof (Customer), new
MappingSchema ( "map.xml ")); p>
while (objectReader.Read ()) p>
( p>
Customer cust =
(Customer) objectReader.Current; p>
Console.WriteLine (cust.Name); p>
) p>
) p>
) p>
ObjectEngine
p>
Клас ObjectEngine лежить в основі ObjectSpaces і
реалізує механізми взаємодії з джерелом даних. У більшості випадків
ObjectEngine прямо не використовується, але в ситуаціях, коли необхідно
виконати OPath-запит або зберегти об'єкт в БД в обхід основної
функціональності ObjectSpaces і з мінімальними витратами - використання
ObjectEngine може стати в нагоді. P>
// Невеликий приклад
використання функціональності ObjectEngine p>
public static void Main () p>
( p>
using (SqlConnection conn = new
SqlConnection ( p>
"Data Source = tim;
Integrated Security = SSPI; Database = northwind ")) p>
( p>
conn.Open (); p>
// Враховуючи, що ObjectEngine - це
"Низькорівневий" клас, деяку частину p>
// підготовчої роботи доводиться
виконувати самостійно. p>
ObjectContext context = p>
new CommonObjectContext (new
ObjectSchema ( "osd.xml ")); p>
MappingSchema msd = new
MappingSchema ( "map.xml "); p>
ObjectSchema osd = new
ObjectSchema ( "osd.xml "); p>
ObjectSources sources = new
ObjectSources (); p>
sources.Add ( "NorthwindRSD", conn); p>
// Створюємо OPath запит і читаємо дані з БД p>
ObjectExpression expr = OPath.Parse ( p>
new
ObjectQuery (typeof (Customer), "", ""), osd); p>
// Ще один побічний ефект ObjectEngine - перед використанням OPath p>
// запит треба "компілювати". p>
CompiledQuery query = expr.Compile (msd); p>
Customer cust = null; p>
// Виконуємо OPath-запит, використовуючи
"Об'єктовий" курсор. P>
using (ObjectReader reader = p>
ObjectEngine.GetObjectReader (sources, context, query, new object [] (
})) p>
( p>
while (reader.Read ()) p>
( p>
cust =
(Customer) reader.Current; p>
Console.WriteLine (cust.Name); p>
) p>
) p>
// Створити об'єкт і зберігаємо його в
джерелі даних p>
cust = new Customer (); p>
cust.CustomerID =
"alfq"; p>
cust.Name =
"MyName"; p>
cust.Phone =
"MyPhone"; p>
cust.Company = "MyComp"; p>
context.Add (cust,
ObjectState.Inserted); p>
ObjectEngine.PersistChanges (msd, sources, context, p>
new object [] (cust),
PersistenceOptions.Default); p>
) p>
) p>
Розширення ObjectSpaces
p>
Використання декількох XML-схем для опису
структури класів програми, реляційної структури БД, а крім того ще й
Mapping-схеми, що не може не пригнічувати. Звичайно, у фінальній версії. NET Framework
1.2 можливості візуального проектування цих схем повинні обов'язково
з'явитися, але поки їх немає, можна скористатися сторонніми засобами. Одне з
таких засобів входить в приклад ObjectSpacesPDCSamples.zip (фото можна знайти на http://www.gotdotnet.com).
p>
До складу цього прикладу входить спеціальна утиліта для
створення всіх необхідних XML-схем (малюнок 4). p>
p>
p>
p>
Малюнок 4. Microsoft ObjectSpaces Mapper Utility. P>
Крім цього, в даний приклад входить реалізація класу
ObjectPersistence. Цей клас володіє однією характерною особливістю - він
приховує в собі не тільки створення XML-описів, але і створення необхідної бази
даних. Розглянемо найпростіший приклад використання ObjectPersistence. P>
using System; p>
using Microsoft.ObjectSpaces.ObjectPersistence; p>
class ObjectPersistenceDemo p>
( p>
// Вихідний код класу ObjectPersistence також доступний у рамках прикладу p>
static ObjectPersistence op =
new p>
ObjectPersistence ( "Data
Source = local; Integrated Security = true; ", p>
"Persistence "); p>
static void Main (string [] args) p>
( p>
Customer c = new Customer (); p>
// Шукаємо замовника в базі даних p>
c = (Customer) op.LoadObject (typeof (Customer),
"CustomerID = 'alfki'"); p>
if (c == null) p>
( p>
c = new
Customer ( "alfki "); p>
c.Comments = "New
Customer "; p>
) p>
else p>
( p>
c.Comments = "Old
Customer "; p>
) p>
p>
// Зберігаємо зміни. p>
// Якщо база даних/таблиця ще не створені, то це відбудеться зараз p>
op.Persist (c); p>
) p>
) p>
Клас ObjectPersistence спроектований таким чином,
що для його використання не обов'язково попередньо створювати базу даних,
настроювати XML-схеми даних - все це робиться всередині реалізації
ObjectPersistence. Так, у наведеному вище прикладі на SQL Server буде створена
база даних Persistence, і в неї буде додана таблиця з ім'ям Customer.
Звичайно, не в кожному проекті можна допустити подібні вольності з боку
бібліотеки доступу до даних, але для простих реалізацій - це чудова
можливість приховати непотрібні деталі. p>
Підсумок
p>
Технології доступу до даних у. NET Framework 1.2
містять безліч корисних нововведень, але якщо для ADO.NET це швидше
еволюційні зміни, пов'язані з простим розширенням бібліотеки, то
ObjectSpaces є абсолютно новим продуктом, який може кардинальним
чином змінити підхід до роботи з даними. Звичайно, в даний момент робота
над бібліотекою ще далека від завершення. До моменту виходу VisualStudio
«Whidbey» ми зможемо побачити в ній багато змін, починаючи з використання
generics і розширення можливостей OPath, і закінчуючи DML-операторами для
видалення об'єктів без попереднього їх вилучення. p>
Список літератури h2>
Для підготовки даної роботи були використані
матеріали з сайту http://www.rsdn.ru/
p>