вторник, 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) и типы запросов (эта статья). В следующий раз (боюсь, это случится не очень скоро) мы подробнее остановимся на процессе выполнения запросов и маппинге.

14 комментариев:

Meowth комментирует...

>>query-syntax и method-base syntax

Есть неплохие распространенные синонимы этих понятий: QBE (query by expression) и QBA (query by API) соответственно. И писать короче -- по 3 буквы )

Idsa комментирует...

Отдает чем-то Хайбернетовским :)
Ни разу не встречал этих понятий в контексте .NET/LINQ.

Meowth комментирует...

Не, никакого Хайбернейт-специфик ) Упоминается, например, тут -- http://www.citforum.ru/database/articles/vietnam/, конечно, не в контексте .net/linq, а в более общем -- ORM

Idsa комментирует...

Спасибо за статью :)
Все-таки она во многом Хайбернет-спификик ;)
Насчет сокращений... QBE там расшифровывается как query by example. Такой возможности, к счастью, в EF нет впринципе.

yuri комментирует...

Не за что

У меня сложилось ощущение, что ADO.NET EF вы воспринимаете как new wave, современный пример архитектуры, вобравший все или многое удачное; а все остальное считаете устаревшим. Это так? )

Idsa комментирует...

Вроде нет :)
Мне приходится иметь дело с EF, Linq To Sql и NHibernate, и я, как мне кажется, могу адекватно оценить их плюсы и минусы. Другое дело, что EF мне больше других нравится...

Raul комментирует...

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

Idsa комментирует...

Raul, ничего не понял :)

Анонимный комментирует...

Привет. Такой тупой вопрос: А как использовать навигационные свойства? Например, я сгенерил модель с БД и у меня появились автоматом Навигационные свойства, но когда я пытаюсь прочитать у меня все время пустые. Может я должен где-то указать чтобы связанные таблицы тоже загружались. А вот запрос мой:
var freeTales = from tale in context.TalesSet
where tale.Free == true
select tale;

Idsa комментирует...

Для подключения навигационных свойств есть метод ObjectContext.Include. Хотя в вашем запросе я не вижу навигационных свойств.

P. S. Подобные вопросы гораздо лучше/правильнее/эффективнее задавать на StackOverflow.

Анонимный комментирует...

Я загружаю данные с помощью хранимой процедуры. В этом случае ObjectContext.Include недоступен. Как в таком случае получить значение столбца в связанной таблице?

Анонимный комментирует...

Опечатка

Однако c появлением LINQ стало очевидно, что при решении большЕнства задач его использование оказывается гораздо более удобным

Idsa комментирует...

Анонимный, fixed

Анонимный комментирует...

"Начнем с Lint To Entities."