Ответ
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 сгенерирует два запроса:
SELECT * FROM OrdersSELECT * FROM OrderItems WHERE OrderId IN (...)(для ID заказов, полученных в первом запросе).
Преимущества:
- Уменьшение объёма передаваемых данных по сети, так как исключается дублирование столбцов родительской сущности.
- Повышение производительности для запросов, включающих несколько коллекций с большим количеством данных.
Недостатки и ограничения:
- Дополнительные round-trips к базе данных. Вместо одного запроса выполняется несколько (N+1 для вложенных коллекций, но EF Core оптимизирует это, используя оператор
IN). - Отсутствие согласованности на уровне моментального снимка (snapshot). Если данные изменяются между выполнением разделённых запросов, итоговое состояние загруженного графа объектов может быть несогласованным (риск при высокой параллельной нагрузке).
- Не поддерживается в некоторых сложных сценариях, например, при использовании
Includeс последующей фильтрацией (Where) по коллекции.
Способы настройки:
- Локально для запроса: Использовать метод
AsSplitQuery(). - Глобально для контекста: Настроить в
OnConfiguringметодаDbContext.protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(connectionString, options => options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)); }Выбор между
SingleQuery(по умолчанию) иSplitQueryзависит от конкретной структуры данных и требований к производительности.
Ответ 18+ 🔞
Слушай, вот тебе реальная история про эту вашу Split Query в Entity Framework. Сидишь ты такой, пишешь код, всё вроде красиво — Include, ThenInclude, коллекции в коллекциях... А потом бац — запрос тормозит как черепаха в патруле, а в логах SQL такой, что глаза на лоб лезут.
В чём прикол, а?
Представь, что ты грузишь заказы с их позициями. Стандартный подход — это как поехать в магазин за хлебом на КамАЗе. EF делает один здоровенный запрос с кучей JOIN, и если в каждом заказе по 10 позиций, то данные по самому заказу повторяются в каждой строке результата. Это и есть тот самый "Cartesian Explosion" — когда из-за дублей объём данных раздувается до неприличных размеров. Сервер пашет, сеть стонет, память плачет.
И что, выход есть? А то! Split Query — это как раз та штука, которая говорит: "А давайте не будем городить один мега-запрос, а разобьём его на несколько адекватных".
Вот смотри, был у тебя запрос:
var orders = context.Orders
.Include(o => o.OrderItems)
.ToList();
Он генерирует SQL с LEFT JOIN. Скучно, предсказуемо, неэффективно.
А теперь добавляешь волшебное заклинание:
var orders = context.Orders
.Include(o => o.OrderItems)
.AsSplitQuery() // Вот этот красавец!
.ToList();
И EF просыпается и делает уже не один, а два запроса:
- Сначала:
SELECT * FROM Orders— берём все заказы. - Потом:
SELECT * FROM OrderItems WHERE OrderId IN (1, 2, 3...)— берём все позиции для этих заказов одним махом.
Чем хорошо? Данных по сети летит в разы меньше, потому что заказ не дублируется для каждой позиции. Особенно кайфуешь, когда коллекций много и они не пустые. Производительность иногда взлетает просто на глазах.
Но не всё так радужно, конечно.
- Больше походов к базе. Вместо одного удара — несколько. Хотя EF не тупой, он не делает N+1, а использует
IN, так что не так страшно. - Согласованность может поехать. Если между первым и вторым запросом кто-то успеет изменить данные в базе (ну, мало ли, админ-потеряшка какой), то в твоих объектах окажется "солянка" из состояний в разные моменты времени. Риск, конечно, но часто им можно пренебречь.
- Не везде работает. Попробуй сделать
Includeс последующимWhereпо коллекции — и всё, приехали, Split Query может и не взлететь.
Как включить?
- Точечно, прямо в запросе: Как показал выше —
.AsSplitQuery(). - Глобально, для всего контекста: Настроил один раз и забыл.
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer(connectionString, options => options.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery)); }
Итог?
Split Query — это не серебряная пуля, а инструмент. Когда у тебя запрос с несколькими "жирными" коллекциями и он начинает тупить — это твой кандидат. Когда запрос простой или данные мелкие — не заморачивайся, стандартный JOIN справится. Просто знай, что такая фича есть, и ты уже не будешь, как тот чувак, который пытается вывезти диван на велосипеде.