Pull to refresh

Comments 26

Какой sql получился из двух запросов?
А нельзя было написать функцию (inline TVF) в sql и дергать её из контекста с материализацией?
О чем статья вообще?
Статья о том, что материализация в EF работает медленно, очевидно производится много действий, нужных для функционала EF. Для поставленной задачи, предполагавшей лишь собрать некоторую статистику по базе и отобразить, этот функционал не был нужен, соотвественно материализация «в обход» EF дала очень хороший прирост. Сам запрос никак не менялся при этом. С функциями, как и с хранимыми процедурами, и вообще с написанием какого либо sql это никак не пересекается.
Вызовите AsNoTracking() на датасете (если Вам не нужно кеширование\трекание изменений) и получите еще прибавку в производительности: по моим подсчетам раза в 2-3 (на 10к сущностей).
Потестировал немного, Сейчас запрос(группа запросов) немного другой, чуть полегче стал, после внесения некоторых изменений, полегче, чем тогда, когда я писал это решение. Время исполнения full EF: 2600ms, AsNoTracking: 570ms, IMaterialize: 430ms
Остается вопрос — как сделать, чтоб Distinct заработал?
А просто Distinct() не работает? На сколько я понимаю, EF реализует Identity Map, так что каждая сущность загрузится только один раз (с одним Primary Key) и стандартный EqualityComparer, который сравнивает референсы, должен сделать свою работу.

Если не работает или Вам нужен Distinct по проекциям, то можно попробовать вот такие методы:
Code
List<Person> distinctPeople = allPeople
  .GroupBy(p => p.PersonId)
  .Select(g => g.FirstOrDefault())
  .ToList();


List<Person> distinctPeople = allPeople
  .GroupBy(p => new {p.PersonId, p.FavoriteColor} )
  .Select(g => g.FirstOrDefault())
  .ToList();


Объекты полученные с использованием AsNoTracking не попадают в Identity Map, cоответственно ReferenceEquals не работает.
Вручную писать такие методы для более 300 сущностей — не вариант, даже если нужны будут меньше половины. Кроме этого, есть сущности не имеющие Identity, то есть нужно перечислить все поля.

Пока что я склоняюсь к тому, чтобы либо генерировать IEqualityComparer, либо partial.

И кстати, не лучше ли было написать вот так?
 allPeople
  .ToDictionary(p => p.PersonId, p => p)
  .Values.ToList();

Это часть запроса. Сгенерируется sql, который вытащит первую из уникальных строчек.
В общем, я бы аггрегировал информацию в базе и вытаскивал уже готовые уникальные значения.
Зачастую производительность достигается варьированием нагрузки. Если запрос слишком сложный, и нагружает базу, то часть вычислений можно перенести в память, на портал, или соответственно наоборот.
Тут есть две стратегии, Union на базе либо Distinct в памяти, обе имеют право на жизнь, и не хотелось бы просто так лишаться одной из них. Собственно partial классы сгенерировать несложно, раз уж нельзя чем нибудь из EF воспользоваться
Зачастую производительность достигается варьированием нагрузки.

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

Далее если у тебя запрос получился сложным и реально требует много вычислений на процессоре, то почти всегда выгоднее его выполнять в базе, ибо прокачать весь объем данных между базой и приложением получится долго.

Только в двух случаях такое «варьирование нагрузки» помогает:
1) Логика невыразима в терминах TSQL, например рекурсии или moving average
2) База очень маленькая (до 1ГБ), тогда выгоднее всосать все в память приложения и использовать навигационный доступ между объектами.
Из примеров — есть случай, когда нужно сделать группировку по двум полям и выбрать максимум по id, т.е. отсеять повторения, заранее известно, что повторений меньше 5%. Опять же, порталов несколько, а база одна.

И, кстати, Union это тоже такой же случай. Два разнородных запроса, достающие одну и ту же сущность, помещенные в Union работают в разы, вплоть до 10 раз медленнее, чем выполненные по отдельности и дистинктнутые в памяти.
Хотя нет, тут опять вмешалась тормозная материализация в EF. Когда запрос не включает в себя Union, имена полей в датасете совпадают с именами полей в таблицах и работают т.н. precompiled views. Что возвращает обратно к IMaterialize.

В Management Studio точность замеров какая то низкая, погрешность плавает, но тем не менее даже так видно, что Union работает где то на треть медленнее.
Покажи схему и скажи что надо получить. Коллективный разум позволит родить запрос.
Есть штука, называется linq2db. По сути тот же Dapper, но с LINQ-провайдером в комплекте. Делает автор Bltoolkit. Существенно шустрее EF работает.
Никаких замеров в инернетах я не нашел. У вас есть? Поделитесь.
Можете считать, что по производительности оно как Bltoolkit (тот же автор, по сути просто очистка bltoolkit от лишнего хлама и груза обратной совместимости), а он в своё время по бенчмаркам лидировал (см. вторую диаграмму, которая Performance).
Так прирост за счёт большей легковесности никуда не делся. Если есть время, можете потестировать на свежем EF, набор тестов тут.
По моим замерам там прирост на десятые доли микросекунд на объект. Этож сколько надо материализовать чтобы тормоза были заметны? Если в секунду материализуешь более миллиона объектов, то скорее всего что-то не так делаешь и проблема вовсе не в EF или другом ORM.
Ну надо прогнать бенчмарки на актуальных версиях и тогда уже смотреть.
Раз уж вы в теме, можно вопрос?
Работал с BLToolkit, всем доволен, всем рекомендую. LINQ никогда не использовал ввиду ненадобности, но я знаю, что к BTL что-то такое прикручивалось. Тогда в чём отличие LINQ2DB от BLT?
linq2db разрабатывается активно, а в BLToolkit только баги иногда чинят. И вообще всем рекомендуют мигрировать со второго на первое.
Почти аргумент :) Ладно, уже скачал — пробую. То есть я так понял, LINQ — наше всё? Может, есть что-то такое на SQL, чего нельзя выразить в LINQ?
Дофига такого, но для этого можно и SQL написать.
Забавно, я работаю с автором PetaPoco на одном проекте, расскажу ему про вашу статью.
Sign up to leave a comment.

Articles