LINQ — удобная, красивая, но в то же время довольно коварная абстракция.
Самые неожиданные вещи обычно происходят на пересечении какой-либо реализации LINQ и LINQ To Objects. Сегодня на одном примере я рассмотрю, как LINQ To Entities (Entity Framework) и LINQ To Objects работают вместе.
Возьмем за основу метод репозитория, который принимает на вход список идентификаторов клиентов и возвращает набор заказов, сгруппированных по этим идентификаторам (таблица Orders содержит поля OrderId, OrderDate и CustomerId):
Подождите минуту! Как это работает? Ведь при выполнении запроса GROUP BY мы можем выбирать только поля, по которым происходит группировка, а также агрегированные значения.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()); } }
Стандартное решение этой проблемы — СОЕДИНЕНИЕ данных таблицы и группировка результатов.
Как это: 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
-
Черный Рынок Ip-Адресов
19 Oct, 24 -
Суд И Ems
19 Oct, 24 -
Сегодня Indextank Отключает Все Свои Серверы
19 Oct, 24 -
Хабр Не Выдает Пароль
19 Oct, 24 -
Отчет О Презентации Galaxy S Ii И Galaxy Tab
19 Oct, 24 -
У Сообществ Жж Появятся Спонсоры
19 Oct, 24