Для начала запустите приложение, которое мы создали в предыдущем посте Знакомимся с бесплатной объектной базой 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 – самый простой.