В чем различия между Include и ThenInclude в Entity Framework Core?

Ответ

Include и ThenInclude — это методы в Entity Framework Core для жадной загрузки связанных данных (Eager Loading). Их основное различие — в уровне вложенности навигационных свойств, которые они загружают.

  • Include загружает навигационное свойство первого уровня, непосредственно связанное с основным запрашиваемым объектом.
  • ThenInclude используется для загрузки вложенных (цепочных) свойств — то есть свойств объекта, который был загружен с помощью предыдущего Include или ThenInclude.

Практический пример:

// Загружаем Customers, их заказы (Orders) и детали каждого заказа (OrderDetails)
var customersWithOrders = context.Customers
    .Include(c => c.Orders)           // Первый уровень: заказы клиента
    .ThenInclude(o => o.OrderDetails) // Второй уровень: детали заказа
    .ToList();

Ключевые правила:

  1. Include можно использовать самостоятельно.
  2. ThenInclude обязательно должен следовать после Include или другого ThenInclude.
  3. Порядок вызовов определяет путь загрузки: от корневого объекта к вложенным.
  4. Без ThenInclude в примере выше коллекция OrderDetails в каждом заказе осталась бы null или пустой (если не включена ленивая загрузка).

Ответ 18+ 🔞

Ну, смотри, чтобы не путаться в этих Include и ThenInclude, представь себе обычную ситуацию. Ты заходишь в бар, просишь счёт, а там просто итоговая сумма — нихуя не понятно, за что платить.

Include — это как сказать: «А ну-ка, бармен, разбей мне этот счёт по позициям!». То есть ты загружаешь сразу связанные данные первого уровня. Вместо того чтобы потом бегать десять раз к базе за каждой мелочью, ты всё получаешь одним здоровенным запросом.

// Без Include — получаешь просто клиента, а его заказы — null или пусто.
// С Include — получаешь клиента И ВСЕ его заказы сразу, в одном пакете.
var тупойКлиент = context.Клиенты.Find(1); // Заказов нет, нихуя не ясно.
var умныйКлиент = context.Клиенты
    .Include(k => k.Заказы) // Вот тут всё, заказы уже в комплекте!
    .FirstOrDefault(k => k.Id == 1);

А теперь смотри, что бывает. Ты получил счёт по позициям (Include), но каждая позиция — это, блядь, просто название напитка. А тебе-то ещё и состав коктейля надо посмотреть, кто его готовил! Вот тут на сцену выходит ThenInclude.

ThenInclude — это когда ты, получив разбивку по позициям, говоришь: «А теперь по каждой позиции покажи мне детальную рецептуру и фотку того мудака-бармена, который это смешивал». То есть ты загружаешь данные уже не от основного объекта (клиента), а от того, что ты только что подгрузил (от заказа).

// Хочешь докопаться до сути? Тогда цепляй ThenInclude.
var клиентСРазборомПолёта = context.Клиенты
    .Include(k => k.Заказы)               // Сначала заказы цепляешь
        .ThenInclude(z => z.ДеталиЗаказа) // Потом к КАЖДОМУ заказу — его детали
        .ThenInclude(d => d.Напиток)      // А потом к КАЖДОЙ детали — сам напиток
    .Include(k => k.ЛюбимыйБармен)        // Можно и параллельно другую ветку грузить
    .FirstOrDefault(k => k.Id == 1);

Главные правила, чтобы не обосраться:

  1. Include можно тыкать просто так, с него всё начинается.
  2. ThenInclude — это прицепной вагон. Без Include перед ним он нихуя не работает. Это как требовать рецепт коктейля, не заказав сам коктейль.
  3. Цепочка Include -> ThenInclude -> ThenInclude — это твой путь вглубь, как матрёшка.
  4. Если забудешь ThenInclude, то, получив заказы, обнаружишь, что их детали — пусты. И будешь потом в цикле дергать базу, как лох, убивая производительность.

Короче, Include грузит то, что пристёгнуто прямо к твоей сущности. А ThenInclude — это уже погружение в кроличью нору, когда тебе надо из того, что пристёгнуто, вытащить что-то ещё более глубокое. Всё просто, как три копейки.