Linq To Entities Против Linq To Objects На Примере Группировки

LINQ — удобная, красивая, но в то же время довольно коварная абстракция.

Самые неожиданные вещи обычно происходят на пересечении какой-либо реализации LINQ и LINQ To Objects. Сегодня на одном примере я рассмотрю, как LINQ To Entities (Entity Framework) и LINQ To Objects работают вместе.

Возьмем за основу метод репозитория, который принимает на вход список идентификаторов клиентов и возвращает набор заказов, сгруппированных по этим идентификаторам (таблица Orders содержит поля OrderId, OrderDate и CustomerId):

  
  
  
  
   

public IDictionary<long, List<Order>> GetOrdersByCustomersIds(IList<long> customersIds) { using (var ctx = new RepositoryContext()) { return ctx.Orders. Where(o => customersIds.Contains(o.Id)).

GroupBy(o => o.CustomerId).

ToDictionary(o => o.Key, o => o.ToList()); } }

Подождите минуту! Как это работает? Ведь при выполнении запроса GROUP BY мы можем выбирать только поля, по которым происходит группировка, а также агрегированные значения.

Стандартное решение этой проблемы — СОЕДИНЕНИЕ данных таблицы и группировка результатов.

Как это:

SELECT o1.*, MinTotal FROM Orders as o1 INNER JOIN (SELECT o2.CustomerId, Min(o2.Total) as MinTotal FROM Orders o2 GROUP BY o2.CustomerId) as o3 ON o1.CustomerId = o3.CustomerId Where o1.CustomerId in (1, 2, 3, 4, 5)

Поставщик EF должен сгенерировать что-то вроде этого.

Давайте в этом убедимся.

У меня был под рукой MySQL .

NET Connector (официальный поставщик ADO.NET для MySQL), поэтому я использовал его и получил следующий сгенерированный запрос (передавая в качестве входных данных список идентификаторов от 1 до 5):

SELECT `Project2`.

`C1`, `Project2`.

`CustomerId`, `Project2`.

`C2`, `Project2`.

`CustomerId1`, `Project2`.

`Id`, `Project2`.

`OrderDate` FROM (SELECT `Distinct1`.

`CustomerId`, 1 AS `C1`, `Extent2`.

`CustomerId` AS `CustomerId1`, `Extent2`.

`Id`, `Extent2`.

`OrderDate`, CASE WHEN (`Extent2`.

`CustomerId` IS NULL) THEN (NULL) ELSE (1) END AS `C2` FROM (SELECT DISTINCT `Extent1`.

`CustomerId` FROM `orders` AS `Extent1` WHERE ((1 = `Extent1`.

`Id`) OR (2 = `Extent1`.

`Id`)) OR (((3 = `Extent1`.

`Id`) OR (4 = `Extent1`.

`Id`)) OR (5 = `Extent1`.

`Id`))) AS `Distinct1` LEFT OUTER JOIN `orders` AS `Extent2` ON (((1 = `Extent2`.

`Id`) OR (2 = `Extent2`.

`Id`)) OR (((3 = `Extent2`.

`Id`) OR (4 = `Extent2`.

`Id`)) OR (5 = `Extent2`.

`Id`))) AND (`Distinct1`.

`CustomerId` = `Extent2`.

`CustomerId`)) AS `Project2` ORDER BY `CustomerId` ASC, `C2` ASC

Немного хуже, чем ручная реализация, но в целом озвученная выше идея прослеживается.

Останавливаться! Почему мы используем группировку на уровне базы данных? Группировка оправдана при использовании функций агрегирования (как в реализации ручного запроса выше).

В нашем случае группировка — это лишь удобное представление полученных данных.

Давайте немного изменим метод репозитория и перенесем процесс группировки на уровень LINQ To Objects:

public IDictionary<long, List<Order>> GetOrdersByCustomersIds(IList<long> customersIds) { using (var ctx = new RepositoryContext()) { return ctx.Orders. Where(o => customersIds.Contains(o.Id)).

AsEnumerable().

GroupBy(o => o.CustomerId).

ToDictionary(o => o.Key, o => o.ToList()); } }

Для полноты картины посмотрим, какой запрос сгенерирует EF-провайдер:

SELECT `Extent1`.

`CustomerId`, `Extent1`.

`Id`, `Extent1`.

`OrderDate` FROM `orders` AS `Extent1` WHERE ((1 = `Extent1`.

`Id`) OR (2 = `Extent1`.

`Id`)) OR (((3 = `Extent1`.

`Id`) OR (4 = `Extent1`.

`Id`)) OR (5 = `Extent1`.

`Id`))

Этот запрос определенно эффективнее предыдущего.

Вот и все, собственно.

Ничего особенного — я просто хотел обратить ваше внимание на коварство перехода от LINQ To X к LINQ To Objects после того, как сам попал в эту ловушку.

Будь осторожен! P.S. Несмотря на то, что я использовал MySQL .

NET Connector, я категорически Я не рекомендую это используйте этого провайдера в продакшене: это не провайдер, а концентрированная куча ошибок, которые не исправляются годами.

Теги: #.

net с #LINQ #.

NET

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.