Pull to refresh

Comments 23

Возможно я чего-то не понимаю, но разве не правильнее получить уже сгруппированные данные из базы данных, чем получать массив данных и потом группировать его в памяти приложения?
Тем более что база данных по идее гораздо эффективнее выполняет такие действия.
Да и концептуально правильнее, насколько я знаю, работу с данными (агрегацию, сортировку сложные выборки) перекладывать как раз на базу данных — для того она и нужна.
А так получается что БД используется исключительно как обычное хранилище.
Так что мне кажется что первый вариант как раз лучше будет. ИМХО.
Я тоже так сначала подумал и реализовал первый вариант. Но обратите внимание, что в приведенном примере ввиду отсутствия функций агрегации использование GROUP BY получается вырожденным: GROUP BY на стороне сервера ровным счетом ничего нам не дает. Так зачем тогда лишний раз напрягать базу?
На стороне сервера GROUP BY в первом случае и не происходит, он делает сортировку по CustomerId, чтобы на клиенте последовательно получить готовые данные, уже _сгруппированные_ по CustomerId.

Во втором случае Вы эту работу перекладываете на плечи managed кода, вместо того, чтобы просто позволить СУБД использовать индексы в ORDER BY `CustomerId`
Я неточно выразился. Да, Group By на стороне сервера не происходит, но вызов LINQ-метода GroupBy на стороне сервера серьезно усложняет запрос. Вопрос в том, насколько целесообразно это усложнение.

Сортировка? А так ли она нужна? Насколько я понимаю реализация GroupBy из LINQ To Objects просто перебирает список подряд. К тому же, если все сводить к сортировке, то можно во вторую реализацию добавить перед AsEnumerable() вызов OrderBy(o => o.CustomerId).

Похоже, без benchmark'а не обойтись. Сейчас попробую прогнать на 10кк записей.
Пока бенчмарк не готов, отмечу, что пост все же не о производительности двух реализаций, а о их концептуальных различиях.
Итак. 1 миллион заказов, где CustomerId заполнен случайным образом в диапазоне от 1 до 1000 (то есть на каждый CustomerId приходится порядка 10к заказов). Сделал 3 замера (для 3, 5 и 10 CustomerId):

3 5 10
1. 7267 11532 19987
2. 5123 8685 17839
3. 5025 8950 17239

Единица измерения — милисекунды. 3-я реализация — вторая с добавленным OrderBy CustomerId (чтобы проверить влияние сортировки).

Насчет влияния сортировки (2 vs. 3). Дефолтная реализация GroupBy из Linq To Objects не получается выигрыша в производительности, если список элементов отсортирован. Так что разница между 2 и 3 реализация — скорее всего, просто погрешность. Другое дело, что можно было бы реализовать своей метод OrderGroupBy… но это уже совсем другая история.
Определённо, если в базе убрать индексы, то второй способ победит =)

Коварно, ничего не скажешь.
Скорее, наоборот, если хорошо настроены индексы, то при определенных условиях первая реализация приблизится по производительности ко второй.
Нука.

А если я сделаю 1 клиента, 1000000 заказов под ним и индекс по CustomerId.
На сколько порядков первая реализация отработает быстрее? =)

В общем, очень сильно зависит от данных. Но в общем случае, первый вариант лучше, потому что не несёт рисков как в примере выше.

В общем случае — да, операции, которые могут выиграть от наличия индексов лучше выносить в базу. Но для текущего примера это не так (см. выше), и для 1кк записей вторая реализация все равно быстрее.
Вот в частности за это мне и нравится NHibernate. За то, что он даёт удобный способ написания старых-добрых SQL-запросов, а не предлагает некую «магию». Таким образом, когда пишешь сложный запрос к БД, задумываешься о том, как он будет выглядеть на SQL.
А в случае простых запросов — ну он такой же, как ЕФ :)

На NH пример выше выглядел бы как:

session.QueryOver<Order>()
      .WhereRestrictionOn(x => x.Customer.Id).IsIn(customersIds)
      .List().GroupBy(x => x.Customer.Id).ToDictionary(x => x.Key, x => x.ToList());

ну и SQL был бы в результате чуть-чуть оптимальнее:

SELECT * FROM orders WHERE order.customerId IN (1, 2, 3)


а засунуть GroupBy-часть в SQL не было бы даже мысли, потому что sql-group-by здесь просто не нужен (он оставил бы из всех Заказов только один).
Интересное разделение синтаксиса. Но в общем случае, думаю, я бы предпочел подход EF: LINQ прекрасен своим единообразием — не хочу изучать отдельный синтаксис LINQ под каждый ORM.

А насчет IN vs. Or — это нельзя рассматривать как преимущество NHibernate перед EF, это лишь преимущество провайдера под MySQL в NHibernate по сравнению с MySQL .NET Connector.
Мне четкие различия: где SQL, а где Linq, наоборот, нравятся. Нравятся четким представлением, что за запрос отправится в базу и угадыванием момента, когда пора оптимизировать.

Люди, хорошо знакомые с Linq, начнут писать запросы на EF быстрее, чем если бы они изучали NH. Тем, кто хорошо знаком с SQL, но мало — c Linq, наоборот будет проще стартовать с NH.

А когда знаешь и то, и другое — это на вкус и цвет :)
Ну и в принципе — обе системы неплохи, с этим не поспоришь.
Ваш вариант выглядел бы так для EF:
ctx.Orders
.Where(o => customersIds.Contains(o.Id))
.ToList()
.GroupBy(o => o.CustomerId).ToDictionary(o => o.Key, o => o.ToList());
И запрос он выдаст абсолютно такой же из-за .ToList()
ну да, он и в посте так написан (за исключением AsEnumerable()).
Я просто хотел подчеркнуть, что при использовании NH не возникает желания вставить GroupBy «не туда», потому что NH заставляет задумываться о том, что идет в SQL, а что — в пост-обработку, не возникает смешения за счет одинаковости синтаксиса (как в EF).

При этом я не говорю, что это минус ЕФа, для многих единство синтаксиса как раз весомый плюс (как для автора, например, судя по комменту выше)
какой у вас Linq To NHibernate провайдер?
боюсь ошибиться в терминологии, но никакой :)
У NH 3.1 такой синтаксис «из коробки».
Ну, у вас в посте и не LINQ вообще :)
ну да :)
но вопрос-то мне, надо ответить :)
4 (та, которая с .NET 4)
Sign up to leave a comment.

Articles