Entity Framework И Производительность

Работая над проектом веб-портала, я исследовал способы повышения производительности и наткнулся на короткую статью о микро-ORM Dapper, написанную авторами проекта StackOverflow.com. Изначально их проект был написан на Linq2Sql, а теперь все критичные к производительности области переписаны с использованием обозначенного решения.

Минус этого, как и других подобных решений, которые мне удалось рассмотреть, заключается в том, что они очень мало помогают облегчить процесс разработки, обеспечивая по большому счету только материализацию, скрывая работу с самим ADO.Net. SQL-запросы необходимо писать вручную.

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

.

Но статья не о том, насколько EF ускоряет разработку, и не о том, что не очень хорошо некоторые запросы писать на linq, а некоторые напрямую на sql. Здесь я представлю решение, которое позволяет объединить сущности EF и запросы Linq2Entities, с одной стороны, и «чистую производительность» ADO.Net — с другой.

Но сначала немного предыстории.

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

И многие, вероятно, пытались оптимизировать это, написав огромный запрос и втиснув в него все, что можно.

Это работает, но выглядит очень устрашающе — код метода огромен, его сложно поддерживать и невозможно протестировать.

Первым этапом решения, которое я попробовал, была материализация всех необходимых сущностей, каждая отдельным запросом.

И их соединение/преобразование в доменную структуру происходит отдельно от материализации.

Позвольте мне объяснить на примере.

Вам необходимо отобразить список страховых полисов; первоначальный запрос выглядит примерно так:

  
  
  
  
   

int clientId = 42; var policies = context.Set<policy>().

Where(x => x.active_indicator).

Where(x => x.client_id == clientId);

Далее для отображения необходимой информации нам нужны зависимые, или как их еще можно назвать, «дочерние» сущности.



var coverages = policies.SelectMany(x => x.coverages); var premiums = coverages.Select(x => x.premium).

Where(x => x.premium_type == SomeIntConstant);

Сущности, подключенные через NavProps, также можно загрузить через Include, но это создает свои трудности; оказалось проще (и производительнее, об этом позже) сделать, как в приведенном выше примере.

Сама эта переработка не дала такого прироста производительности относительно исходного комплексного запроса, но упростила код, позволила разбить его на более мелкие методы и сделала код более приятным и привычным для глаза.

Производительность пошла дальше, когда, запустив профилировщик SQL-сервера, я обнаружил, что два запроса из 30 выполняются в 10-15 раз дольше, чем остальные.

Первый из этих запросов был таким

var tasks = workflows.SelectMany(x => x.task) .

Where(x => types.Contains(x.task_type)) .

GroupBy(x => new { x.workflow_id, x.task_type}) .

Select(x => x.OrderByDescending(y => y.task_id).

FirstOrDefault());

Как оказалось, EF генерирует очень неудачный запрос, и просто переместив GroupBy с последнего на первый, я приблизил скорость выполнения этих запросов к остальным, получив примерно 30-35% сокращение конечного времени выполнения.



var tasks = context.Set<task> .

GroupBy(x => new { x.workflow_id, x.task_type}) .

Select(x => x.OrderByDescending(y => y.task_id).

FirstOrDefault()) .

Join(workflows, task => task.workflow_id, workflow => workflow.workflow_id, (task, workflow) => task) .

Where(x => types.Contains(x.task_type));

На всякий случай скажу, что Join в этом запросе эквивалентен SelectMany в предыдущем.

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

И через Include это реализовать тоже нельзя.

Возвращаясь к началу статьи, к микро-ОРМ, хочу сразу сказать, что такой подход может быть оправдан не во всех сценариях.

В нашем нужно было загрузить часть данных из базы, сделать некоторые преобразования и вычисления и отправить клиенту в браузере через JSON. В качестве прототипа решения я попробовал реализовать материализацию через PetaPoco, и результат теста меня очень впечатлил, разница во времени материализации целевой группы запросов составила 4,6х (756мс против 3493мс).

Хотя точнее было бы сказать, что я разочарован выступлением EF. Из-за жестких настроек в StyleCop не было возможности использовать PetaPoco в проекте, и чтобы адаптировать его под задачу пришлось лезть в него и вносить изменения, поэтому возникла идея написать собственное решение.

Решение основано на том, что при создании запросов EF будет указывать в запросе имена полей для набора данных, которые соответствуют именам свойств объектов, которые он сгенерировал для контекста.

Альтернативно вы можете положиться на порядок этих полей, который тоже работает. Для извлечения запроса и параметров из запроса используется метод ToObjectQuery, а для результирующего объекта — метод ToTraceString и свойство «Параметры».

Далее следует простой цикл чтения, взятый из MSDN. Изюминка решения — материализаторы.

PetaPoco генерирует код материализатора во время выполнения, но я решил сгенерировать для них код, используя шаблоны T4. За основу я взял файл, генерирующий сущности для контекста, при чтении .

edmx использовал все вспомогательные классы из него, а непосредственно генерирующий код заменил.

Пример сгенерированного класса:

public class currencyMaterialize : IMaterialize<currency>, IEqualityComparer<currency> { public currency Materialize(IDataRecord input) { var output = new currency(); output.currency_id = (int)input["currency_id"]; output.currency_code = input["currency_code"] as string; output.currency_name = input["currency_name"] as string; return output; }

Теги: #ef #производительность #.

NET

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

Автор Статьи


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

Dima Manisha

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