dev.net.ua

Українська Спільнота Розробникiв
 
Ласкаво просимо до dev.net.ua Увійти | Приєднатися | Допомога | Увійти Live ID
в Пошук

Андрей

Знакомимся с бесплатной объектной базой db4o. Запросы.

Для начала запустите приложение, которое мы создали в предыдущем посте Знакомимся с бесплатной объектной базой db4o. Введение. и заполните контейнер какими-нибудь данными. Я предлагаю такой вариант:


 

сохраните данные и закройте приложение. В этом посте оно нам в качестве приложения больше не понадобится (но понадобится его сборка для доступа к описанию класса Country).

Для просмотра результатов запросов мы создадим WPF приложение с использованием методики, которая изложена в посте Манипулируем элементами FlowDocument. Описываемый там класс FlowDocumentExtentions здесь.

Начнём как обычно, с подготовительного этапа:

Создаём WpfApplication. В дизайнере переходим на Grid и, кликнув указателем мыши по верхней его полосе, разделим Grid на 2 столбца (В XAML автоматически добавятся 2 ColumnDefinition). Сделайте левую часть побольше, а правую поменьше. В левую часть поместим FlowDocumentScrollViewer, а в правую кнопку "Query". Если FlowDocumentScrollViewer-а нет на вашем Toolbox-e, добавьте его при помощи Choose Items...

Теперь нужен XAML файл с FlowDocument, в котором бы размещался заголовок и таблица с 3-мя столбцами. Это своего рода шаблон для представления данных. Изготовить такой файл вы можете самостоятельно, по методике, изложенной в посте Манипулируем элементами FlowDocument, или используйте уже готовый вариант. Этот файл мы будем загружать в событии flowDocumentScrollViewer1_Loaded:

System.IO.FileStream fs = new System.IO.FileStream(@"d:\CountryTemplate.xaml", System.IO.FileMode.Open);

template = (FlowDocument)System.Windows.Markup.XamlReader.Load(fs);

fs.Close();

flowDocumentScrollViewer1.Document = template;


где:

FlowDocument template;

это поле, которое необходимо объявить в классе Window1.


Запустите приложение и посмотрите, что получилось, Настройте внешний вид так, чтобы было комфортно просматривать данные.

На этом первую часть подготовительного этапа можно считать законченной.

Теперь подключим базу, созданную в прошлом посте. Для этого проведём уже привычные манипуляции:

В Solution Explorer -- References добавим ссылку на: C:\Program Files\Db4objects\db4o-6.1\bin\net-2.0\Db4objects.Db4o.dll

В Window1.xaml.cs допишем:

using Db4objects.Db4o;

using Db4objects.Db4o.Query;

В классе Window1 добавим поле:

IObjectContainer objectContainer;

В событии flowDocumentScrollViewer1_Loaded допишем:

objectContainer = Db4oFactory.OpenFile(@"D:\Test.yap");

В событии Window_Closed напишем:

objectContainer.Close();

Теперь перенесём наше внимание на событие button1_Click кнопки "Query".

Следуя примеру предыдущего поста надо бы написать что-то вроде:

IObjectSet os = objectContainer.Query(typeof(Country));


Но как мы это сделаем, если класс Country в нашем WPF приложении нигде не описан? Идею - продублировать описание - нужно отвергнуть сразу. Совсем другая сборка, другое пространство имён. Правильным решением будет извлечь описание класса из той сборки, которая использовалась приложением, поместившим данные в контейнер. Для этого в Solution Explorer -- References добавим ссылку на сборку предыдущего поста. У меня это:

d:\Документы\Visual_Studio_2008\Projects\WindowsFormsApplicationDB\WindowsFormsApplicationDB\bin\Release\WindowsFormsApplicationDB.exe

В Window1.xaml.cs можно дописать:

using WindowsFormsApplicationDB;

Теперь наше WPF приложение и база данных, заполненная другим приложением, будут одинаково понимать, что такое Country.

Для переноса данных во FlowDocument, используем метод ToFlowDocument, который в свою очередь использует расширения FlowDocumentExtentions (об этом мы уже говорили выше):

void ToFlowDocument(System.Collections.IEnumerable list, string queryName)

{

 var fd = flowDocumentScrollViewer1.Document;

 fd.Blocks.Paragraphs()[0].Inlines.RunsTree()[1].Text = queryName;

 var tables = fd.Blocks.Tables();

 Table t = tables[0].Clone();

 foreach (var obj in list)

 {

  if (!(obj is Country)) continue;

  TableRow r = t.RowGroups[0].Rows[1].Clone();

  r.Cells[0].Blocks.Paragraphs()[0].Inlines.Add(((Country)obj).Name + string.Empty);

  r.Cells[1].Blocks.Paragraphs()[0].Inlines.Add(((Country)obj).Code + string.Empty);

  r.Cells[2].Blocks.Paragraphs()[0].Inlines.Add(((Country)obj).Description + string.Empty);

  t.RowGroups[0].Rows.Add(r);

 }

 t.RowGroups[0].Rows.RemoveAt(1);

 fd.Blocks.InsertAfter(tables[0], t);

 fd.Blocks.Remove(tables[0]);

}


Подготовительный этап закончен, приступаем к работе с Db4o.

Теперь наше внимание будет постоянно приковано к событию button1_Click кнопки "Query":

IObjectSet os = objectContainer.Query(typeof(Country));

FlowDocument fd = new FlowDocument();

fd.Blocks.AddRange(template.Blocks.CloneDeep());

flowDocumentScrollViewer1.Document = fd;

ToFlowDocument(os, "");

Запустите приложение и нажмите на Query. С этого момента мы можем отвлечься от всего, о чём говорили раньше, и сосредоточиться только на запросах.

Как вы уже поняли, запросом здесь является:

IObjectSet os = objectContainer.Query(typeof(Country));

Давайте расмотрим его более внимательно. IObjectSet os – это список каких-то объектов, получаемых из контейнера, а typeof(Country), говорит методу objectContainer.Query – какие именно объекты нужно получить. В данном случае, мы хотели получить список всех Country (в отличие от предыдущего поста, когда мы хотели один List<Country> для bindingSource1.DataSource).

Теперь встаёт законный вопрос - как извлечь из контейнера только те объекты Country, которые соответствуют определённому условию? Если бы Country была таблицей реляционной базы данных, мы могли бы написать SQL запрос:

Select * from Country where Description = "пост СССР"

Критерий поиска Description = "пост СССР", впрочем, как и вся команда Select, передаётся в базу данных обычной строкой.

В Db4o нет SQL потому, что там нет понятия "таблица". Класс Country в общем случае может иметь довольно сложную структуру. Он может содержать списки других классов, реализовывать какие-то интерфейсы, иметь атрибуты. Например, наш Country мог бы быть и таким:

public class Country

{

public string Name;

public string Code;

public string Description;

public object Selector;

public List<Region> RegionList;

}

Как терминами SQL описать критерий поиска экземпляров Country, у которых селектор реализует интерфейс MyInterface и список регионов содержит более 10 элементов?

Для NQ запосов Db4o это вполне решаемая задача:

IList<Country> list = objectContainer.Query<Country>( (Country country) => country.Selector is MyInterface && RegionList.Count > 10);

Как видите, даже в области запросов, использование объектной базы данных отрывает нас от привычного реляционного мышления и сталкивает с новой (кому-то может показаться – враждебной) идеологией хранения, обработки, и извлечения данных. Хорошо это или плохо – решать вам. Мы ещё пофилософствуем на эту тему в последующих постах, а сейчас я расскажу, что же предлагает Db4o взамен SQL на примере простого критерия поиска: Description == "пост СССР".

В Db4o существует 3 разных способа поиска объектов в контейнере:

1. Query by Example (QBE).

Пользователь создаёт экземпляр класса Country и передает его в базу данных в качестве образца для поиска. Критерий поиска должен находится в этом образце в виде инициализированных свойств или полей:

Country country = new Country();

country.Description = "пост СССР";

IObjectSet os = objectContainer.Get(country);

FlowDocument fd = new FlowDocument();

fd.Blocks.AddRange(template.Blocks.CloneDeep());

flowDocumentScrollViewer1.Document = fd;

ToFlowDocument(os, "QBE");

Те поля или свойства объекта, которые не инициализированы, не участвуют в поиске.

Разработчики Db4o предлагают этот способ извлечения данных в ознакомительных целях. Но я вижу практическое применение, например, при поиске дубликатов. Характерным признаком того, что вы используете способ QBE, является наличие метода Get в вашем коде.


2. Native Queries (NQ).

Пользователь создаёт специальный метод (предикат) и передаёт его в базу данных в качестве критерия поиска:

IList<Country> list = objectContainer.Query<Country>( (Country country) => country.Description=="пост СССР");

FlowDocument fd = new FlowDocument();

fd.Blocks.AddRange(template.Blocks.CloneDeep());

flowDocumentScrollViewer1.Document = fd;

ToFlowDocument(list, "Native");

В общем случае, параметром для Query<T> должен быть делегат типа:

public delegate bool Predicate<T>(T obj);

этот делегат описан в пространстве имён System сборки mscorlib. Метод, на который ссылается этот делегат часто называют предикатом. Мы в качестве предиката использовали анонимный метод. Более подробно об анонимных методах в C# 3.0 можно узнать здесь.

Фактически, при Native Queries, база ничего не делает, кроме как анализирует, какое из двух значений (true/false) возвращает предикат для каждого объекта типа T. Если true – объект попадает в список IList<T>, возвращаемый методом Query<T>.

Очевидно, что такой подход обладает большой гибкостью, попробуйте:

(Country country) => country.Description == "Западная Европа" || country.Name.Contains("стан")

Можно увлечься созданием сложных предикатов и сильно проиграть в производительности. Время выполнения запроса в основном зависит от времени отработки предиката и количества объектов заданного типа в контейнере. Характерным признаком того, что вы используете способ NQ, является наличие в вашем коде метода Query<T>(Predicate<T> obj);


3. SODA Query API.

Пользователь вводит критерии поиска в аргументы методов Query API:

IQuery q = objectContainer.Query();

q.Constrain(typeof(Country));

q.Descend("<Description>k__BackingField").Constrain("пост СССР");

IObjectSet os = q.Execute();

FlowDocument fd = new FlowDocument();

fd.Blocks.AddRange(template.Blocks.CloneDeep());

flowDocumentScrollViewer1.Document = fd;

ToFlowDocument(os, "SODA");

Как видите, это не очень удобно. Если нужно выбрать страны Западной Европы и пост СССР, придется городить что-то вроде:

q.Descend("<Description>k__BackingField").Constrain("пост СССР").Or(q.Descend("<Description>k__BackingField").Constrain("Западная Европа"));

Кроме того, SODA работает только с полями (из-за соображений производительности) и мне пришлось расплачиваться за свою лень, подглядывая имена полей в рефлекторе. В прошлом посте я опустил описания полей в классе Country и компилятор присвоил им имена:

<Name>k__BackingField

<Code>k__BackingField

<Description>k__BackingField

Спрашивается – для чего столько мучений с SODA, если есть QBE и Native? Дело в том, что при обработке запросов SODA, Db4o максимально задействует встроенные механизмы оптимизации запросов. При работе с большим количеством однотипных объектов в контейнере, SODA запрос может заметно сократить время выборки. Характерным признаком того, что вы используете SODA, является наличие в вашем коде объекта, реализующего интерфейс IQuery и его метода Execute().

Вот, пожалуй и всё, что я хотел сообщить в этом посте о запросах Db4o. Подводя краткий итог, можно сказать, что в Db4o существует 3 типа запросов: SODA – самый быстрый, Native – самый удобный с точки зрения .NET-идеологии, QBE – самый простой.

Опубліковані Sunday, February 17, 2008 1:56 PM від ServiceDeveloper
Помічено як:

Коментарі

 

Андрей сказав:

Давайте попробуем "копнуть глубже в недра" Db4o и посмотреть, что же представляет собой контейнер объектов.

February 27, 2008 6:37 AM
 

Андрей сказав:

Для демонстрации принципов активации объектов в Db4o, мы будем использовать WpfApplication из поста Знакомимся

March 26, 2008 2:22 AM
Анонімні коментарі деактивовані. Увійдіть або Зареєструйтесь щоб мати доступ до ресурсів Спільноти.