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.