2013-10-11 2 views
48

При запросе большой таблицы, в которой вам нужно получить доступ к свойствам навигации в дальнейшем в коде (я явно не хочу использовать ленивую загрузку), что будет работать лучше .Include() или .Load()? Или зачем использовать один над другим?.Include() vs .Load() производительность в EntityFramework

В этом примере включенные таблицы содержат всего около 10 записей, а сотрудники имеют около 200 записей, и может случиться так, что большинство из них будут загружены в любом случае с включением, потому что они соответствуют предложению where.

Context.Measurements.Include(m => m.Product) 
        .Include(m => m.ProductVersion) 
        .Include(m => m.Line) 
        .Include(m => m.MeasureEmployee) 
        .Include(m => m.MeasurementType) 
        .Where(m => m.MeasurementTime >= DateTime.Now.AddDays(-1)) 
        .ToList(); 

или

Context.Products.Load(); 
Context.ProductVersions.Load(); 
Context.Lines.Load(); 
Context.Employees.Load(); 
Context.MeasurementType.Load(); 

Context.Measurements.Where(m => m.MeasurementTime >= DateTime.Now.AddDays(-1)) 
        .ToList(); 

ответ

65

Ответ: «все зависит, попробуйте оба».

При использовании Include() вы получаете возможность загрузить все свои данные за один раз в базовый хранилище данных. Например, если это удаленный SQL Server, это может стать большим повышением производительности.

Недостатком является то, что Include() запросы, как правило получить действительно сложно, особенно если у вас есть какие-либо фильтры (Where() вызовы, например) или попытаться сделать любую группировку. EF будет генерировать очень сильно вложенные запросы, используя подзаголовки SELECT и APPLY, чтобы получить нужные данные. Это также намного менее эффективно - вы возвращаете одну строку данных со всеми возможными столбцами дочернего объекта, поэтому данные для объектов верхнего уровня будут повторяться много раз.(Например, один родительский объект с 10 детьми будет обрабатывать 10 строк, каждый из которых имеет те же данные для столбцов родительского объекта.) У меня были отдельные запросы EF настолько сложными, что они вызывали взаимоблокировки при работе в то же время, что и EF логики обновления.

Метод Load() намного проще. Каждый запрос представляет собой простой, простой, простой оператор SELECT против одной таблицы. Это намного проще всячески, за исключением, вы должны сделать многие из них (возможно, много раз). Если у вас есть вложенные коллекции коллекций, вам даже может понадобиться выполнить цикл над объектами верхнего уровня и Load их под-объекты. Он может выйти из-под контроля.

Как быстрое правило, я стараюсь избегать иметь не более трех вызовов Include в одном запросе. Я нахожу, что запросы EF становятся уродливыми, чтобы признать это выше; он также соответствует моему правилу большого пальца для запросов SQL Server, что до четырех операторов JOIN в одном запросе работает очень хорошо, но после этого пришло время рассмотреть рефакторинг.

Однако все это только отправная точка. Это зависит от вашей схемы, вашей среды, ваших данных и многих других факторов. В конце концов, вам просто нужно попробовать все по-своему. Выберите разумный шаблон «по умолчанию», чтобы узнать, достаточно ли он, и если нет, оптимизируйте вкус.

+3

+1 для учета сетевого подключения (_If это удаленный SQL Server_). Часто это важно. – ken2k

+0

+1 - попробуйте оба. в моем случае использования 'Include()' заняло 7 минут, 'Load()' заняло 1,9 секунды и использовало меньше оперативной памяти. Я добавил 'LazyLoadingEnabled = false;' –

17

Include() будет записан в SQL, как JOIN: один туда и обратно к базе данных.

Каждый из Load() -строек является «явно ленивой загрузкой» запрошенных объектов, поэтому одна обратная транзакция по одному вызову.

Таким образом, Include(), скорее всего, будет более разумным выбором в этом случае, но это зависит от расположения базы данных, как часто этот код вызывается и как долго живет ваш DbContext. Почему бы вам не попробовать обеими способами, не профилировать запросы и сравнивать тайминги?

См. Loading Related Entities.

+2

Ваши объяснения правильно, но 'Include()' не обязательно будет более разумным выбором. В зависимости от конкретных определений таблиц и используемой базы данных запрос с таким количеством включений может стать настолько сложным, что он выполняет намного хуже, чем отдельные запросы с помощью функции «Load()». (Да, у меня это было.) – hvd

+2

@hvd согласился, что может случиться, хотя я не нахожу пять объединений. Реальный ответ был бы: «Измерьте это» _ в любом случае, поскольку мы ничего не знаем о базе данных OP. – CodeCaster

4

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

Load является ручной заменой EnableLazyLoading. Если для этого параметра установлено значение false. Вы все еще можете лениво загружать сущность, которую вы просили, с .Load()

+0

объясняет отличную разницу, но вопрос был задан о производительности –

1

Всегда сложно решить, следует ли идти с нетерпением, явным или даже ленивым загрузкой.
То, что я хотел бы рекомендовать в любом случае, всегда выполнять некоторые профилирования. Это единственный способ убедиться, что ваш запрос будет выполнен или нет.
Есть много инструментов, которые помогут вам. Посмотрите на this article from Julie Lerman, где она перечисляет несколько различных способов профилирования. Одним простым решением является запуск profiling in your SQL Server Management Studio.
Не стесняйтесь говорить с администратором баз данных (если у вас есть рядом с вами), который поможет вам понять план выполнения.
Вы также можете посмотреть this presentation, где я написал раздел о загрузке данных и выполнении.

6

Я согласен с @MichaelEdenfield в его answer, но мне захотелось прокомментировать сценарий вложенных коллекций. Вы можете обойтись, чтобы делать вложенные циклы (и многочисленные результирующие вызовы в базу данных), выворачивая запрос наизнанку.

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

context.OrderItems.Where(x => x.Order.CustomerId == customerId); 

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

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

Я не уверен, каким был бы эффект, если бы в большинстве случаев существовал ни один ребенок - много нулей? Редкие дети в отношениях один к одному лучше подходят для метода прямого запроса, описанного выше.

1

Еще одна вещь, чтобы добавить в эту тему. Это зависит от того, какой сервер вы используете. Если вы работаете на сервере sql, нормально использовать загружаемую загрузку, но для sqlite вам придется использовать .Load(), чтобы избежать перекрестной пересылки исключений. Sqlite не может иметь дело с некоторыми заявлениями включения, которые идут глубже, чем один уровень зависимости.

Смежные вопросы