вторник, 17 марта 2009 г.

Влюбляемся в Entity Framework: Шаг четвертый: Создание запросов

Cоздание запросов
Entity Framework предоставляет два основных типа запросов: LINQ To Entities и Entity SQL (eSQL). При этом если Object Services поддерживает оба типа запросов, то Entity Client поддерживает лишь eSQL. Да и вообще, Object Services является основным средством создания запросов, а к Entity Client, как правило, прибегают лишь в крайних случаях.

LINQ To Entities представляет собой реализацию LINQ (в статье подразумевается, что читатель знаком с основами LINQ) для работы с Entity Framework, а следовательно поддерживает два типа синтаксиса: query-syntax и method-base syntax (эти понятия буду приводить на английском языке: уж больно нелаконично они звучат на русском).

Entity SQL создавался как основное средство создания запросов. Однако c появлением LINQ стало очевидно, что при решении большинства задач его использование оказывается гораздо более удобным (к слову, разработчики NHibernate после релиза LINQ создали свою реализацию LINQ - LINQ To NHibernate). Синтаксис eSQL схож с синтаксисом SQL (хотя еще больше он похож на синтаксис hSQL ;) ), однако между ними существуют принципиальные различия (помимо того, что eSQL -язык запросов к модели, а SQL - язык запросов к реляционным БД): так, eSQL, в отличие от SQL, поддерживает наследование и навигационные свойства.

Object Services
Начнем с Lint To Entities.
1. Query-syntax.

using (FirstModel ctx = new FirstModel())
{
var persons = from p in ctx.Persons
where p.Address.City == "Tomsk"
select p;

Console.WriteLine("В Томске проживают:");
foreach (Person person in persons)
Console.WriteLine(person.Name + " " + person.Surname);

Console.ReadLine();
}
Результат выполнения запроса:
В Томске проживают:
Evgenii Salomatov
Andrei Lupanov
Vitalii Pogonin
Evgenii Hudoba
Мы написали запрос, который возвращает всех Person, проживающих в городе Томске (этот запрос мы будем использовать для всех примеров).

2. Method-based syntax.
Перепишем запрос:

using (FirstModel ctx = new FirstModel())
{
var persons = ctx.Persons.Where(p => p.Address.City == "Tomsk");

Console.WriteLine("В Томске проживают:");
foreach (Person person in persons)
Console.WriteLine(person.Name + " " + person.Surname);

Console.ReadLine();
}
Перейдем к eSQL-запросам.

1. Чистый eSQL.
В ObjectContext есть метод CreateQuery, который позволяет выполнять eSQL-запросы:

using (FirstModel ctx = new FirstModel())
{
string queryString = "SELECT VALUE p FROM FirstModel.Persons AS p WHERE p.Address.City ='Tomsk'";
var persons = ctx.CreateQuery<Person>(queryString);

Console.WriteLine("В Томске проживают:");
foreach (Person person in persons)
Console.WriteLine(person.Name + " " + person.Surname);

Console.ReadLine();
}
2. Query-builder методы.
Некоторые методы (Where, Select и др.), используемые в method-based LINQ To Entities
(лишь метод Include можно использовать и с query syntax), принимают в качестве аргумента не делегат, а eSQL.

Перепишем наш method-based запрос с применением query-builder метода:

using (FirstModel ctx = new FirstModel())
{
var persons = ctx.Persons.Where("it.Address.City == 'Tomsk'");

Console.WriteLine("В Томске проживают:");
foreach (Person person in persons)
Console.WriteLine(person.Name + " " + person.Surname);

Console.ReadLine();
}
Данный способ приходится очень кстати, когда нужно добавить в запрос немного динамики (например, сортировку по полям, выбранным пользователем), но при этом не хочется терять строгую типизацию, переходя на чистый eSQL-запрос.

В запросе используется алиас по умолчанию - "it". Его можно изменить, воспользовавшись свойством Name класс ObjectQuery:

var persons = ctx.Persons;
persons.Name = "p";
persons = persons.Where("p.Address.City == 'Tomsk'");
Подведем промежуточные итоги.
1. Когда использовать чистые eSQL-запросы? Тогда, когда запрос формируется динамически, и query-builder методов не достаточно.

2. Что выбрать: query или method-based LINQ To Entities? Здесь многое зависит от личных предпочтений (для кого-то выразительность query-синтаксиса может оказаться дороже всех преимущества method-based синтаксиса)... но я рекомендую использовать method-based синтаксис: во-первых, при помощи query-синтаксиса можно выразить далеко не весь функционал LINQ (и LINQ To Entities в частности), а, во-вторых, из 13 query-builder методов query-синтаксис поддерживает лишь один - Include.

EntityClient
Если, читая статью, Вы успели соскучиться по старому-доброму ADO.NET, у меня для Вас приятный сюрприз. EntityClient, являясь более низкоуровневым компонентом, чем Object Services (как мы выяснили во второй статье, Object Service считывает данных при помощи EntityClient, а затем материализует их), оперирует умными наследниками таких базовых классов ADO.NET, как DbConnection, DbCommand, DbDataReader и т. д. Почему умными? Потому что в них учитывается специфика Entity Framewok: так, например, EntityDataReader поддерживает не только скалярные значения, но и DbDataReader, DbDataRecord и EntityKey.

Отмечу, что EntityClient не является самым низкоуровневым средством создания запросов, так как EDM поддерживает запросы на нативном SQL (при помощи Defining Query).

Перепишем наш запрос с использованием EntityClient:

using (EntityConnection connection = new EntityConnection("name=FirstModel"))
{
string queryString = "SELECT VALUE p FROM FirstModel.Persons AS p WHERE p.Address.City ='Tomsk'";
EntityCommand command = connection.CreateCommand();
command.CommandText = queryString;

connection.Open();

Console.WriteLine("В Томске проживают:");

using (EntityDataReader dataReader = command.ExecuteReader(CommandBehavior.SequentialAccess))
{
while (dataReader.Read())
{
string firstName = dataReader.GetString(1);
string secondName = dataReader.GetString(2);

Console.WriteLine(firstName + " " + secondName);
}
}

Console.ReadLine();
}
Вот мы и рассмотрели все типы запросов в EF (кроме DefiningQuery... но это тема для отдельного разговора).

Напоследок я раскрою третий аргумент в противостоянии method-based vs. query syntax.

ObjectQuery vs. IQueryable
ObjectQuery - это класс, который содержит все необходимую информацию о EF-запросе. Он реализует ряд интерфейсов, в том числе и IQueryable. Некоторые Linq To Entities методы возвращают ObjectQuery, а некоторые - IQueryable:
Самая серьезная потеря в IQueryable по сравнению с ObjectQuery - метод Execute (который мы рассмотрим в начале следующей статьи). Однако потерю можно восполнить: на самом деле эти методы возвращают не IQueryable, а ObjectQuery, поэтому нам нужно лишь привести тип:

using (FirstModel ctx = new FirstModel())
{
var iQueryablePersons = from p in ctx.Persons
where p.Address.City == "Tomsk"
select p;
var objectQueryPersons = (ObjectQuery)iQueryablePersons;
objectQueryPersons.Execute(MergeOption.AppendOnly);

Console.WriteLine("В Томске проживают:");
foreach (Person person in objectQueryPersons)
Console.WriteLine(person.Name + " " + person.Surname);

Console.ReadLine();
}
Заключение
К этому моменту мы изучили подноготную запросов (шаг 3) и типы запросов (эта статья). В следующий раз (боюсь, это случится не очень скоро) мы подробнее остановимся на процессе выполнения запросов и маппинге.

понедельник, 16 марта 2009 г.

Влюбляемся в Entity Framework: Шаг третий: Введение в запросы

Непринужденный флирт (первые две статьи) позади - пора познать внутренний мир Entity Framework. Первым шагом на этом пути станет изучение запросов в EF. Однако сначала разберемся, откуда у сгенерированных дизайнером классов ноги растут.

Изучаем EDM и сгенерированные дизайнером классы
Откроем нашу модель дизайнером и произведем небольшие изменения:
  1. Выделим сущность Persons и в окне Properties выставим: Name в Person, Entity Set Name в Persons
  2. В сущности Person в разделе Navigation Properties выделим Addresses и в окне Properties зададим свойству Name значение Address
  3. Выделим сущность Addresses и в окне Properties выставим: Name в Address, Entity Set Name в Addresses
  4. Сохраним модель и закроем ее.
Откроем FirstModel.edmx при помощи XML-редактора и рассмотрим концептуальную модель:

<edmx:ConceptualModels>
<Schema Namespace="FirstModelModel" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
<EntityContainer Name="FistModel">
<EntitySet Name="Addresses" EntityType="FirstModelModel.Address" />
<EntitySet Name="Persons" EntityType="FirstModelModel.Person" />
<AssociationSet Name="FK_Persons_Addresses" Association="FirstModelModel.FK_Persons_Addresses">
<End Role="Addresses" EntitySet="Addresses" />
<End Role="Persons" EntitySet="Persons" />
</AssociationSet>
</EntityContainer>
<EntityType Name="Address">
<Key>
<PropertyRef Name="AddressId" />
</Key>
<Property Name="AddressId" Type="Int32" Nullable="false" />
<Property Name="City" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="Street" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="PostalCode" Type="String" Nullable="false" MaxLength="6" Unicode="true" FixedLength="false" />
<Property Name="Apartment" Type="String" MaxLength="6" Unicode="true" FixedLength="false" />
<NavigationProperty Name="Persons" Relationship="FirstModelModel.FK_Persons_Addresses" FromRole="Addresses" ToRole="Persons" />
</EntityType>
<EntityType Name="Person">
<Key>
<PropertyRef Name="PersonId" />
</Key>
<Property Name="PersonId" Type="Int32" Nullable="false" />
<Property Name="Name" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="false" />
<Property Name="Surname" Type="String" Nullable="false" MaxLength="50" Unicode="true" FixedLength="false" />
<NavigationProperty Name="Address" Relationship="FirstModelModel.FK_Persons_Addresses" FromRole="Persons" ToRole="Addresses" />
</EntityType>
<Association Name="FK_Persons_Addresses">
<End Role="Addresses" Type="FirstModelModel.Address" Multiplicity="0..1" />
<End Role="Persons" Type="FirstModelModel.Person" Multiplicity="*" />
</Association>
</Schema>
</edmx:ConceptualModels>
Полный разбор концептуальной модели в мои планы на эту статью не входит: сделаю акцент на том, что имеет отношение к сегодняшней теме.

Концептуальная модель состоит из EntityContainer, множества определений EntityType и определений Association.

EntityContainer хранит опиcания EntitySet'ов и AssociationSet'ов.

Каждый элемент EntityType определяет сущность концептуальной модели. Элемент Key определяет совокупность свойств, являющихся первичным ключом. Элементы Property задают скалярные свойства сущности. А элементы NavigationProperty служат для связи сущностей.

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

Классы-сущности
Каждый EntityType преобразуется в класс-сущность (приведен упрощенный вариант кода):

public partial class Person : EntityObject
{
private int _PersonId;
partial void OnPersonIdChanging(int value);
partial void OnPersonIdChanged();
public int PersonId
{
get
{
return this._PersonId;
}
set
{
this.OnPersonIdChanging(value);
this.ReportPropertyChanging("PersonId");
this._PersonId = StructuralObject.SetValidValue(value);
this.ReportPropertyChanged("PersonId");
this.OnPersonIdChanged();
}
}

//...
//Остальные скалярные свойства

public Address Address
{
get
{
return ((IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference("FirstModelModel.FK_Persons_Addresses", "Addresses").Value;
}
set
{
((IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference("FirstModelModel.FK_Persons_Addresses", "Addresses").Value = value;
}
}

public EntityReference AddressReference
{
get
{
return ((IEntityWithRelationships)(this)).RelationshipManager.GetRelatedReference("FirstModelModel.FK_Persons_Addresses", "Addresses");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedReference("FirstModelModel.FK_Persons_Addresses", "Addresses", value);
}
}
}
}

public partial class Address : EntityObject
{
private int _AddressId;
partial void OnAddressIdChanging(int value);
partial void OnAddressIdChanged();
public int AddressId
{
get
{
return this._AddressId;
}
set
{
this.OnAddressIdChanging(value);
this.ReportPropertyChanging("AddressId");
this._AddressId = StructuralObject.SetValidValue(value);
this.ReportPropertyChanged("AddressId");
this.OnAddressIdChanged();
}
}

//...
//Остальные скалярные свойства

public EntityCollection Persons
{
get
{
return ((IEntityWithRelationships)(this)).RelationshipManager.GetRelatedCollection("FirstModelModel.FK_Persons_Addresses", "Persons");
}
set
{
if ((value != null))
{
((IEntityWithRelationships)(this)).RelationshipManager.InitializeRelatedCollection("FirstModelModel.FK_Persons_Addresses", "Persons", value);
}
}
}
}
Если элементы Property просто конвертируются в C#-свойства (с добавлением partial-методов для валидации), то с NavigationProperty все несколько сложнее. Возможны два варианта:
  1. Если навигационному свойству соответствует 0..1 объект (как, например, в случае Person.Address), для него дизайнер сгенерирует два свойства: первое - с типом данных связанной сущности (Address) и с именем соответствующего NavigationProperty (Address), а второе - с типом EntityReference и с добавлением к имени постфикса "Reference" (AddressReference). Первое свойство - это непосредственно связанный с сущностью объект (адрес данной личности), второе - вспомогательное свойство, содержащее функционал, который пригодится при более изощренном использовании EF.
  2. Если навигационному свойству соответствует множество объектов (как, например, в случае Address.Persons), для него будет сгенерировано одно свойство типа EntityCollection с именем соответствующего NavigationProperty (Persons). В этом случае вспомогательного свойства не требуется: весь необходимый функционал встроен в EntityCollection.
Возникает вопрос, как определить сколько объектов (0..1 или множество) соответствует навигационному свойству? Конечно, данная информация есть в EDM, но нагляднее это изображено в дизайнере:
Подписи к ассоциации говорят, что "одному Address соответствует множество Person, а одному Person соответствует 0 либо 1 Address".

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

Наследник ObjectContext
Заканчивая разговор о классах, сгенерированных дизайнером, рассмотрим класс FirstModel, который является наследником ObjectContext.

Как было отмечено в прошлой статье, ObjectContext является центральным классом Object Services. Однако на практике используется не сам ObjectContext, а его наследник, генерируемый дизайнером, т. к. в нем есть ряд свойств и методов, упрощающих работу:

public partial class FistModel : ObjectContext
{
private ObjectQuery _Addresses;
public ObjectQuery Addresses
{
get
{
if ((this._Addresses == null))
{
this._Addresses = base.CreateQuery("[Addresses]");
}
return this._Addresses;
}
}

private ObjectQuery _Persons;
public ObjectQuery Persons
{
get
{
if ((this._Persons == null))
{
this._Persons = base.CreateQuery("[Persons]");
}
return this._Persons;
}
}

public void AddToAddresses(Address address)
{
base.AddObject("Addresses", address);
}

public void AddToPersons(Person person)
{
base.AddObject("Persons", person);
}
}
Как мы выяснили в начале статьи, в EntityContainer хранятся определения EntitySet (в том числе). Так вот для каждого EntitySet в FirstModel генерируется:
  1. Свойство типа ObjectQuery для обращения к этому набору (Addresses, Persons)
  2. Метод для добавления элементов в этот набор (AddToAddresses, AddToPersons)
Может показаться, что для каждого EntityType есть свой EntitySet (как у нас в примере), однако это всего лишь частный случай (например, при реализации наследования EntitySet определяется только для базовой сущности).

Заключение
Неожиданно введение затянулось. Ну что ж... к самим запросам перейдем в следующей статье, которая выйдет совсем скоро.

вторник, 10 марта 2009 г.

Влюбляемся в Entity Framework: Шаг второй: Архитектура EF

Создание первой модели
Дабы дальнейший разговор был более предметным, создадим нашу первую модель. Предварительно необходимо в Sql Server 2005 заресторить базу данных из бэкапа, расположенного в прикрепленном к этому посту файле. Далее:
  1. Создадим в Visual Studio 2008 новый проект Console Application и назовем его FirstModel.
  2. В Solution Explorer правой кнопкой кликаем по имени проекта -> Add -> New Item -> выбираем ADO.NET Entity Data Model и вводим имя FirstModel -> Add
  3. Выбираем Generate From Database (по умолчанию) -> Next
  4. Заходим в New Connection, где создаем соединение к базе данных First Model. Если Вы выбрали Sql Server Authentication, то после создания строки нужно будет подтвердить хранение логина и пароля в строке соединения, выбрав "Yes, include sensitive data in the connection string". В поле ввода имени строки соединения вводим FirstModel (фактически это не имя строки соединения, а Entity Container Name, но об этом мы подробнее поговорим как-нибудь в следующий раз) -> Next
  5. В появившемся окне необходимо выбрать элементы, которые будут добавлены в модель. Выбираем таблицы Addresses и Persons -> Finish.
Модель сформирована - можем приступать к изучению ее "внутренностей".

Изучаем модель изнутри
После создания модели в проект было добавлено 2 файла: FirstModel.edmx и FirstModel.Designer.cs. Кроме того, Visual Studio открыла ForstModel.edmx EF-дизайнером.

FirstModel.edmx - XML файл, описывающий Entity Data Model (EDM), а также содержащий некоторую вспомогательную информацию для EF-дизайнера.

Я не буду подробно останавливаться на рассмотрении EDM: об этом подробно писал Сергей Розовик. Вкратце, EDM является одной из основных концепций EF и представляет собой совокупность трех элементов: схемы хранилища (Store Schema Definition Language (SSDL) = Storage Schema), концептуальной схемы (Conceptual Schema Definition Language (CSDL) = Conceptual Schema) и спецификации отображения (Mapping Specification Language (MSL) = С-S mapping).

Чтобы ознакомиться с содержимым EDM, закроем вкладку с FirstModel.edmx, щелкнем правой кнопкой по FirstModel.edmx в Solution Explorer -> Open With... -> XML Editor -> OK.
На этапе компиляции EDM поэлементно разбивается на три файла, которые встраиваются в ресурсы сборки. Это поведение можно изменить: например, если Вам необходимо во время выполнения динамически вносить изменения в EDM, файлы можно хранить рядом со сборкой.

Перейдем к рассмотрению файла FirstModel.Designer.cs. Как несложно догадаться из названия, этот файл был сгенерирован EF-дизайнером. Заглянем во внутрь.
Дизайнер любезно сгенерировал для нас три класса: класс FirstModel, являющийся наследником ObjectContext, и две сущности Addresses и Persons, являющиеся наследниками EntityObject.

Кстати, namespace, который мы вводили при создании модели, - это внутренний namespace модели, который используется для полного именования сущностей. Namespace же сгенерированных классов определяется их местонахождением в проекте.

Теперь настало время абстрагироваться от нашего примера и посмотреть, как же он вписывается в общую архитектуру Entity Framework.

Архитектура Entity Framework
Рассмотрим схему, изображающую основные компоненты (и способы взаимодействия с ними) Entity Framework:
Работа EF выглядит примерно следующим образом:
  1. Формируется запрос к EntityClient (при помощи Object Services либо напрямую)
  2. EntityClient при помощи метаданных преобразует Entity SQL или Linq To Entities-запрос в SQL-запрос
  3. SQL-запрос отдается на выполнение ADO.NET провайдеру, указанному в строке соединения (в нашем случае - SqlClient)
  4. После обращения провайдера к СУБД данные в обратном порядке возвращаются.
Детали выполнения запросов мы рассмотрим в следующей статье, сейчас же главная задача - изучить три основных (на мой взгляд) компонента архитектуры EF: Metadata Files (EDM), Entity Client и Object Services.
EDM мы уже рассмотрели в предыдущей главе, так что смело переходим к изучению Entity Client.

EntityClient
EntityClient - самый низкоуровневый способ создания запросов к EDM. EntityClient отличается от Object Services тем, что он не материализует данные в объекты, а просто возвращает данные в виде строк и столбцов посредством EntityDataReader.

При работе с EntityClient невольно вспоминаешь старые-добрые(?) времена, когда основными действующими лицами были SqlClient, OracleClient и т. д. : приходится оперировать аналогичными классами: EntityConnection, EntityCommand, EntityParameter и т. д.

Зачем в Entity Framework нужен столь низкоуровневый подход? На практике самым популярным применением EntityClient является реализация того, что не умеет (или умеет, но плохо) Object Services (например, работа с некоторыми типа хранимых процедур). Об этом мы обязательно подробно поговорим в будущих статьях.

Object Services
Object Services является верхушкой API Entity Framework и располагается в пространстве имен System.Data.Objects. Object Services реализует весь необходимый функционал для удобного создания и взаимодействия с сущностями концептуальной модели. Основным компонентом Object Services является класс ObjectContext, наследником которого в нашей модели является сгенерированный дизайнером класс FirstModel.

Функционал ObjectServices можно разбить на четыре основные группы:
  1. Обработка запросов (Query Processing). Как я уже отмечал, обработку запросов мы отложим до следующей статьи. Однако по рисунку несложно догадаться, что Object Services позволяет делать запросы как при помощи LINQ To Entities, так и при помощи Entity SQL.
  2. Материализация объектов (Object materialization). После получения результатов от ADO.NET-провайдера EntityClient возвращает в Object Services EntityDataReader, после чего Object Services материализует результаты в экземпляры сущностей.
  3. Управление состоянием объектов (Object state management). ObjectContext хранит экземпляр ObjectStateEntry для каждого экземпляра сущности (entity) и взаимосвязи (relationship). В частности, в ObjectStateEntry хранятся оригинальные и текущие значения свойств сущности, что позволяет реализовать отслеживание изменений (change tracking).
  4. Управлением взаимосвязями объектов (Object relationship management). Хотя экземпляры сущностей и "знают", как обратиться к связанным объектам, именно ObjectContext обеспечивает эти взаимосвязи.
Заключение
Надеюсь к этому моменту мне удалось передать в общих чертах суть архитектуры Entity Framework. Понимание того, как и какие компоненты взаимодействуют друг с другом очень важно при решении более сложных задач, до которых мы со временем обязательно доберемся.

Прикрепленный файл

понедельник, 9 марта 2009 г.

Влюбляемся в Entity Framework: Шаг первый: Введение

Пролог
Этот пост является началом цикла статей по Entity Framework, идея написания которого преследует меня уже достаточно продолжительное время.

Что побудило меня на это начинание?

Во-первых, мне нравятся ORM-системы (со всеми своими достоиствами и недостатками).
Во-вторых, несмотря на то, что мне приходится иметь дело с разными ORM, c Entity Framework меня связывают особенное теплые отношения :)
В-третьих, уже прошло более полугода с момента релиза EF v. 1 (а с моменты появления стабильных бета-версий - и того больше), а материалов в русскоговорящем сообществе до сих пор довольно мало. Здесь я хотел бы отметить статьи Сергея Розовика в блоге stump-workshop.blogspot.com (справа в блоке "Темы" выберите "Entity Framework"). Вероятно, я еще не раз буду ссылаться на эти статьи, дабы не повторяться.

Я сомневался, как писать этот цикл статей: последовательно от простого к сложному или как набор постов, описывающих определенные подходы и решающих конкретные задачи. С одной стороны, хочется, как можно скорее, поделиться интересными мыслями и наработками (это аргументы в пользу второго варианта), с другой, - ввиду отсутствия обширной информации по EF на русском языке, гораздо более уместным мне видится первый вариант. На нем и остановился.

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

Введение
Итак, что же такое Entity Framework? EF - это реализация фирмы Microsoft технологии Object-relational Mapping (ORM) под .NET.

Так сложилось, что в области обработки данных (data management) мэйнстримом являются объектно-ориентированные языки программирования, а в области хранения данных (data storing) - реляционные базы данных (РБД).

ORM - это подход, позволяющий конвертировать данные между реляционными базами данных и объектно-ориентированными языками программирования, тем самым нивелируя так называемый object-relational impedance mismatch (по-русски это звучит не очень лаконично, вроде "несоответствие принципов, лежащих в основе объектного и реляционного подходов", поэтому позволю себе использовать английский вариант).

Рассмотрим ряд различий между объектной и реляционной концепциями:
  1. Различия в базовых принципах. ООП базируется на принципах создания программного обеспечения (инкапсуляция, наследование, полиморфизм), в то время как реляционная парадигма имеет в основе математические правила (операции с множествами). Если инкапсулацию и полиморфизм на уровне РБД представить невозможно (да, наверное, и не нужно), то с наследованием ORM справляются на отлично.
  2. Различия в оперируемых сущностях. В ООП мы оперируем объектами и ссылками на объекты, а в РБД - таблицами, атрибутами и записями. ORM с блеском нивелирует это различие, позволяя программисту обращаться к связанным объектам традиционными для ООП способами, лишая программиста "удовольствия" создания разнообразных join-запросов.
  3. Различия в оперируемых типах (эта проблема не является уникальной для взаимодействия объектной и реляционной систем: с ней приходится сталкиваться даже при взаимодействии двух объектно-ориентированных платформ, например, через Web-сервисы). В качестве примера обычно приводят тип данных "строка". Реляционные базы данных позволяют ограничивать размер строк, тогда как в большинстве объектно-ориентированных языков размер строки ограничивается лишь объемом доступной виртуальной памяти. В любом случае конвертация типов - рутинная работа, которую лучше автоматизировать за счет использования ORM.
Безусловно, на этом отличия не заканчиваются. Но, думаю, этого достаточно для того, чтобы понять, что ORM-системы появились не на ровном месте, и имеют право на существование.

Кроме того, ORM-системы направлены не только на борьбу с object-relational impedance mismatch, они предлагают и ряд других полезных возможностей (набор таких возможностей индивидуален для каждой конкретной реализации). Так, например, ORM позволяют генерировать SQL-запросы налету: Вы выполняете определенные операции с сущностями, отдаете команду ORM-системе, а она генерирует SQL-запрос и отдает его на выполнение. Это ли не чудо?! Больше не нужно писать рутинный код для работы с базой данных! Больше не нужно судорожно делать бесконечные Find&Replace при малейших изменениях схемы базы! Больше не нужно...

Стоит отметить, что до появления ORM (Hibernate 1.0 датирован лишь 2002 годом) программисты зачастую создавали свои собственные утилиты, которые по возможностям очень напоминали сегодняшние ORM. Таким образом, появление серьезных ORM стало не революцией, а, скорее, эволюцией, качественным улучшением средств реализации слоя доступа к данным.

Противники скрещивания ужа (объектной парадигмы) с ежом (реляционной парадигмой) совершенно справедливо указывают на то, что более логичным и лежащим на поверхности решением object-relational impedance mismatch был бы переход на объектно-ориентированные базы данных (ООБД). Однако, несмотря на то, что идея ООБД не нова (начало 70-х годов 20-го века), позиции объектно-ориентированных СУБД очень шатки. Причин на то много, но самая существенная, на мой взгляд, - как раз прочность позиций РБД.

Дабы обратить внимание на другие парадигмы, решил привести ссылку на пост двухгодичной давности с Enfranchised Mind, посвященный functional-relational impedance mismatch. Если я все правильно понял, описываемое там решение уже реализовано в .NET, и имя ему - Linq (в случае EF - Linq To Entities). Однако этот пост подтолкнул меня к дальнейшим размышлениям, в ходе которых я пришел к тому, что ORM - верный путь, ибо не напасешься СУБД, заточенных под каждую конкретную парадигму (все-таки ORM-система и СУБД - не сопоставимые по сложности задачи). А затем я вспомнил то, как ужасно ведут себя объемные RDF-хранилища, реализуемые на базе реляционных СУБД (лирическое отступление на тему Semantic Web). А все потому, что реляционая парадигма совершенно не отражает принципы, заложенные в онтологии (так, например, такая мощная возможность СУБД, как индексы, вынуждена простаивать без дела).

Так что мнение "лучше использовать ORM, чем внедрять объектно-ориентированную СУБД" актуально лишь для мэйнстримовых (на данный момент) задач. При решении же более узкоспециализированных задач возможны и диаметрально-противоположные мнения.

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

четверг, 5 марта 2009 г.

SEMANTIC WEB for the WORKING ONTOLOGIST. Часть 2

Сегодня мы опять поговорим о замечательной книге "Semantic Web for the Working Ontologist", и о ее нелегкой судьбе на земле русской.
Как же развивались события с момента публикации предыдущего поста?

В комментариях к кросспосту на Хабре Дмитрий Уланов пригласил меня в группу webofdata. В дальнейшем основное общение проходило в этой ветке.
Примерно через неделю, списавшись с Дином Алемангом, я узнал, что запросов на перевод пришло очень мало. Совместно с подписчиками webofdata решили, что ждать милости от Morgan-Kaufmann не стоит - нужно самим связываться с российскими издательствами. После уведомления избранных издательств о существовании такой замечательной книги, оставалось только ждать...
Пока ждали, рассматривали альтернативные варианты: создание закрытого перевода на translated.by, публикация обзоров глав книги на Хабре и т. д. Однако 1-го марта я с удивлением обнаружил на мыле очень радостную весеннюю новость: Дин Алеманг писал о том, что издательство Morgan-Kaufmann начало переговоры с российским издательством. Цитирую Дина:
I have received a number of emails showing interest in a Russian
edition of Working Ontologist. Thanks to everyone who has sent me
letters of support - Morgan-Kaufmann is exploring possibilities with a
Russian publisher to produce a translation. We'll keep our fingers
crossed! Of course, anyone who knows the publishing industry knows
that such a project typically takes a year to a year and a half before
the book is actually available. So do keep your fingers crossed, but
don't hold your breath!
Это было очень неожиданно. Время шло, новостей не было... и я уже почти перестал надеяться. К слову, удивлен был не только я :)

В письме Дин предложил выступить в качестве моего протеже при выборе переводчика. Связано это было с тем, что в одном из первых писем, я писал Дину о том, что участвую в любительском переводе его книги для внутренних целей одной организации. Меня смутило это предложение (ведь я программист, а не переводчик)... и в итоге я отказался от этого предложения (с подробностями можно ознакомиться на webofdata). К счастью, Полина Медведкова, одна из подписчиц webofdata, изъявила желание заняться переводом. Это просто замечательно: ведь для перевода столь узкоспециализированной книги недостаточно хорошего знания английского, нужно разбираться в технологиях Semantic Web. Поэтому я с удовольствием порекомендовал Дину Алемангу Полину в качестве переводчика.

Будем надеяться, что издательства таки договорятся, а переводчиком выберут Полину. Удачи тебе, Полина! :)

Во второй части поста я хотел бы поговорить о будущем книги.
Читая отзывы о книге на Amazon, увидел лишь один относительно негативный комментарий:
There is a great need for a book like this, and the authors are well-credentialed, but unfortunately, this book is more like an introduction than a craftsman's resource. It reads like a first draft with typos, belabored repetitive text, and some odd examples. There is almost no discussion of internationalization, security, performance, or tricky basic types such as dates, times, or currency. More discussion of SPARQL would have been useful, as well as discussion of what should go in the model vs. what should be queried out. The chapter "Good and Bad Modeling Practices" was a particular let-down. A sharp, thoughtful, deeper book on this topic would really help the field.
К сожалению, после прочтения книги я был готов подписаться под каждым словом этого комментария. Безусловно, книга отличная (особенно на фоне других в этой области), но она могла быть еще лучше. Возникает логичный вопрос: "А не будет ли второго издания?". Пока ответа не знает и сам Дин Алеманг:
A second edition (with new material) is a lot of work both for us and the publisher.
Our current plan is to write some new chapters an pre-publish them on the web, for eventual inclusion in a second edition. I am working on the SPARQL chapter now. I am not yet convinced of the industrial relevance of RuleML or even OWL 2.0, but we will treat those in the same manner.
Of course, if I find the time to do all this . . . .
Но что-то мне подсказывает, что второму изданию быть ;)