.Include () против производительности .Load () в EntityFramework

52

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

В этом примере включенные таблицы содержат всего около 10 записей, а у сотрудников - около 200 записей, и может случиться так, что большинство из них будут загружены в любом случае include, потому что они соответствуют предложению 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();
Теги:
entity-framework

6 ответов

73
Лучший ответ

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

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

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

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

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

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

  • 5
    +1 за учет сетевого подключения ( если это удаленный SQL Server ). Это часто является важным моментом для рассмотрения.
  • 2
    +1 - попробуй оба. в моем случае использование Include() заняло 7 минут, Load() заняло 1,9 секунды и использовало меньше оперативной памяти. Я добавил LazyLoadingEnabled = false;
Показать ещё 1 комментарий
19

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

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

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

Смотрите Загрузка связанных объектов.

  • 2
    Ваше объяснение верно, но Include() не обязательно будет более разумным выбором. В зависимости от конкретных определений таблиц и используемой базы данных, запрос с таким количеством включений может стать настолько сложным, что он выполняет намного хуже, чем отдельные запросы с Load() . (Да, у меня было такое.)
  • 3
    @hvd согласился, что это может случиться, хотя я не нахожу пяти соединений. Реальный ответ будет: «В любом случае, « измерить », так как мы ничего не знаем о базе данных OP.
Показать ещё 1 комментарий
6

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

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

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

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

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

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

4

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

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

  • 0
    это объясняет разницу, которая велика, но вопрос был о производительности
  • 2
    Загрузка НЕ является ручным переопределением EnableLazyLoading. Если вы установите для него значение false, вы должны использовать явную загрузку с клавиатурой Collection или Reference. См. Entityframeworktutorial.net/EntityFramework4.3/…
1

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

1

Всегда сложно решить, следует ли идти с Eager, Explicit или даже "Lazy Loading".
В любом случае, я бы рекомендовал всегда выполнять некоторые профилирования. Это единственный способ убедиться, что ваш запрос будет выполнен или нет.
Есть много инструментов, которые помогут вам. Посмотрите эту статью от Джули Лерман, где она перечисляет несколько разных способов профилирования. Одним простым решением является запуск профилирования в вашей SQL Server Management Studio.
Не стесняйтесь говорить с администратором баз данных (если у вас есть рядом), который поможет вам понять план выполнения.
Вы также можете посмотреть эту презентацию, где я написал раздел о загрузке данных и производительности.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню