Что такое метод Include в Entity Framework Core?

Ответ

Метод Include в Entity Framework Core реализует стратегию жадной загрузки (Eager Loading) связанных данных. Он указывает EF Core загрузить указанные навигационные свойства сущности в рамках одного SQL-запроса (через JOIN), предотвращая проблему N+1 запроса.

Проблема N+1 (без Include):

var blogs = context.Blogs.ToList(); // 1 запрос: SELECT * FROM Blogs
foreach (var blog in blogs)
{
    // N запросов: SELECT * FROM Posts WHERE BlogId = @id
    var posts = blog.Posts.ToList(); // Ленивая загрузка вызывает отдельный запрос для каждого блога!
}

Решение с Include:

// Все данные загружаются одним запросом с JOIN
var blogsWithPosts = context.Blogs
    .Include(b => b.Posts) // Жадная загрузка постов
    .ToList();
// SQL: SELECT b.*, p.* FROM Blogs b LEFT JOIN Posts p ON b.Id = p.BlogId

Цепочка загрузки с ThenInclude:

// Загрузка вложенных связей (Блог -> Посты -> Авторы -> Контакты)
var detailedBlogs = context.Blogs
    .Include(b => b.Posts)
        .ThenInclude(p => p.Author) // Загрузка автора каждого поста
            .ThenInclude(a => a.ContactInfo) // Загрузка контактов автора
    .Include(b => b.Owner) // Можно добавить несколько независимых ветвей Include
    .ToList();

Критические best practices:

  • Избегайте чрезмерной загрузки: Включайте только те данные, которые действительно нужны для текущего сценария. Загрузка огромных графов объектов (Include(...).Include(...)) убивает производительность.
  • Используйте AsSplitQuery() для сложных случаев: Начиная с EF Core 5, для запросов с множеством Include, которые приводят к декартову произведению (cartesian explosion), используйте AsSplitQuery(). Это разобьет один запрос на несколько, что может быть эффективнее.
  • Альтернативы:
    • Явная загрузка (Load()): Позволяет загрузить связи позже, по требованию.
    • Проекции (Select): Самый эффективный способ — запрашивать сразу только нужные поля в анонимный тип или DTO, минуя загрузку полных сущностей.
      var blogData = context.Blogs
      .Where(b => b.Id == 1)
      .Select(b => new // Проекция
      {
          BlogTitle = b.Title,
          PostTitles = b.Posts.Select(p => p.Title).ToList()
      }).FirstOrDefault();

Ответ 18+ 🔞

А, Entity Framework и его Include... Ну это ж классика, как водка с пивом! Смотри, чтобы не наступить на те же грабли, что и все.

Вот представь: ты достаёшь блоги из базы, а потом для каждого блога лезешь за его постами отдельно. Это как если бы ты пошёл в магазин за хлебом, вернулся домой, вспомнил про молоко, опять пошёл в магазин, вернулся, вспомнил про сыр... И так двадцать раз. База данных тебя возненавидит, это называется проблема N+1 запроса.

var blogs = context.Blogs.ToList(); // Раз — запрос за блогами
foreach (var blog in blogs)
{
    // И вот тут для КАЖДОГО блога — новый запрос. N раз. Пиздец производительности.
    var posts = blog.Posts.ToList();
}

А теперь Include — это как взять тележку в том же магазине и сразу набрать всё, что нужно, за один заход. Жадная загрузка, одним SQL-запросом с JOIN.

var blogsWithPosts = context.Blogs
    .Include(b => b.Posts) // Всё! Блоги и посты — одним махом.
    .ToList();
// Генерится что-то вроде: SELECT b.*, p.* FROM Blogs b LEFT JOIN Posts p ON b.Id = p.BlogId

А если нужно глубже, в дебри? Например, блог -> посты -> автор поста -> его контакты? Тут на помощь приходит ThenInclude. Это как сказать: "И в этих постах мне ещё авторов с их контактными данными, не забудь!"

var detailedBlogs = context.Blogs
    .Include(b => b.Posts)          // Цепляем посты
        .ThenInclude(p => p.Author) // К каждому посту — автора
            .ThenInclude(a => a.ContactInfo) // А к автору — его контакты
    .Include(b => b.Owner) // Можно и параллельную ветку подцепить
    .ToList();

Но вот главное, где все ебутся: с жадностью нужно знать меру, а то будет как в анекдоте про "всё включено". Нагрузишь кучу Include — получишь монструозный запрос с декартовым произведением, который будет возвращать овердохуища дублированных данных и тормозить как черепаха в патруле.

  • Не включай всё подряд. Загружай только то, что реально нужно для работы прямо сейчас.
  • Для сложных графов объектов (куча Include) посмотри в сторону AsSplitQuery() (EF Core 5+). Он разобьёт один толстый запрос на несколько умных. Иногда это спасает от взрыва производительности.
  • Знай альтернативы. Include — не панацея.
    • Явная загрузка (Load()) — "лайт-версия", загрузишь связи потом, когда нужно.
    • Проекции (Select) — вообще красота, топчик по эффективности. Запросишь сразу только нужные поля, без всей этой возни с полными сущностями.
var blogData = context.Blogs
    .Where(b => b.Id == 1)
    .Select(b => new // Берём только название блога и заголовки постов. Ничего лишнего.
    {
        BlogTitle = b.Title,
        PostTitles = b.Posts.Select(p => p.Title).ToList()
    }).FirstOrDefault();

Короче, Include — мощный инструмент, но как молоток: можно гвоздь забить, а можно и по пальцам получить. Думай, что загружаешь, и всё будет пучком.