EntityFramework: вызов ToList() в IQueryable с ~11.000 записей занимает 10 секунд

Я хочу вернуть сравнительно большое количество записей с сервера SQL Express 2008 R2 через EntityFramework 4 через службу WCF клиенту WCF. Моя тестовая таблица в настоящий момент содержит около 11.000 записей. Запрос LINQ так же прост:

Database DB = new Database(); // create object context
var retValue = DB.Entities.Persons
        .Include("District")
        .Include("District.City")
        .Include("District.City.State")
        .Include("Nationality")

return retValue.ToList();

Это займет около 10 секунд.

Тот же самый запрос SELECT занимает менее 1 секунды при выполнении в SQL Server Managament Studio.

Это должно быть так медленно в EF?

2 ответа

Решение

Ваш запрос не прост, он содержит много объединений (из-за Include s) и что более важно, он может вернуть много дублированных данных, особенно если включенные свойства навигации являются коллекциями: /questions/14708376/skolko-vklyuchit-ya-mogu-ispolzovat-dlya-objectset-v-entityframework-chtobyi-sohranit-proizvoditelnost/14708389#14708389

Часть, отнимающая много времени, - это материализация объекта и присоединение сущностей к контексту, когда результат из базы данных возвращается в контекст Entity Framework.

Это подтверждается вашими измерениями (в комментариях к вашему вопросу), что второй запрос в том же контексте очень быстрый. В этом случае EF выполнит запрос к базе данных, но ему не нужно снова материализовывать объекты, потому что они все еще привязаны к контексту.

Если вы выполняете второй запрос во втором контексте, результирующие объекты должны быть присоединены к новому контексту - и этот шаг снова медленный (также подтвержденный вашими измерениями).

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

Единственный способ улучшить производительность - отключить отслеживание изменений (предполагается, что оно вам не нужно для ваших операций). В EF 4.0 / ObjectContext это было бы:

Database DB = new Database();
DB.Entities.Persons.MergeOption = MergeOption.NoTracking;
// MergeOption is in System.Data.Objects namespace

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

Таким образом, потенциально больше объектов будет загружено в память. Если это контрпродуктивно и фактически ухудшает производительность, или если она все еще работает лучше, это вопрос испытания.

Это очень вероятно, потому что компиляция запроса (LINQ-запрос с большим количеством включений -> SQL для использования) очень медленна в EF по сравнению с выполнением запроса. Вы можете проверить, является ли это проблемой, профилируя ваш код процессором. Попробуйте использовать меньше включений + несколько меньших запросов, использовать скомпилированные запросы или выполнить обновление до последней бета-версии EF5.

Другие вопросы по тегам