Что такое Split Query (разделённый запрос) в Entity Framework Core?

«Что такое Split Query (разделённый запрос) в Entity Framework Core?» — вопрос из категории Entity Framework, который задают на 25% собеседований C# Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Split Query (Разделённый запрос) — это стратегия загрузки связанных данных в Entity Framework Core, при которой один сложный SQL-запрос с несколькими JOIN разбивается на несколько отдельных, более простых запросов. Это решение проблемы "раздувания Cartesian product" (Cartesian Explosion).

Проблема, которую решает Split Query: При использовании Include() и ThenInclude() для загрузки нескольких коллекций EF Core генерирует один запрос с LEFT JOIN. Если у родительской сущности много дочерних записей в каждой коллекции, результирующий набор данных дублируется для каждой комбинации, что приводит к передаче избыточного объема данных по сети и повышенной нагрузке на память.

Пример: Загрузка заказов (Order) с их позициями (OrderItems).

Стандартный запрос (Single Query):

var orders = context.Orders
    .Include(o => o.OrderItems)
    .ToList();

Генерирует SQL с LEFT JOIN, что может привести к дублированию данных строк заказа для каждой позиции.

Запрос с использованием Split Query:

var orders = context.Orders
    .Include(o => o.OrderItems)
    .AsSplitQuery() // Ключевой метод
    .ToList();

EF Core сгенерирует два запроса:

  1. SELECT * FROM Orders
  2. SELECT * FROM OrderItems WHERE OrderId IN (...) (для ID заказов, полученных в первом запросе).

Преимущества:

  • Уменьшение объёма передаваемых данных по сети, так как исключается дублирование столбцов родительской сущности.
  • Повышение производительности для запросов, включающих несколько коллекций с большим количеством данных.

Недостатки и ограничения:

  • Дополнительные round-trips к базе данных. Вместо одного запроса выполняется несколько (N+1 для вложенных коллекций, но EF Core оптимизирует это, используя оператор IN).
  • Отсутствие согласованности на уровне моментального снимка (snapshot). Если данные изменяются между выполнением разделённых запросов, итоговое состояние загруженного графа объектов может быть несогласованным (риск при высокой параллельной нагрузке).
  • Не поддерживается в некоторых сложных сценариях, например, при использовании Include с последующей фильтрацией (Where) по коллекции.

Способы настройки:

  1. Локально для запроса: Использовать метод AsSplitQuery().
  2. Глобально для контекста: Настроить в OnConfiguring метода DbContext.
    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
    optionsBuilder.UseSqlServer(connectionString, options => options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
    }

    Выбор между SingleQuery (по умолчанию) и SplitQuery зависит от конкретной структуры данных и требований к производительности.