0Pricing
C# Academy · Урок

Жадная, ленивая и явная загрузка

Управляйте моментом загрузки связанных данных с помощью Include, ThenInclude, прокси ленивой загрузки и явных вызовов Load.

«Жадная, ленивая и явная загрузка» — бесплатный урок C# Academy на CoddyKit. Это урок 1 из 4. Ты можешь прочитать весь урок бесплатно ниже — а потом практиковать его прямо в браузере с встроенным редактором кода и ИИ-репетитором 24/7. Это часть пути обучения C# Academy, и твой прогресс синхронизируется между веб-версией и приложением CoddyKit. Курс C# Academy содержит 4 уроков всего.

Три стратегии загрузки

EF Core предлагает три стратегии загрузки связанных данных: жадная загрузка — загрузка вместе с запросом, ленивая загрузка — загрузка при обращении, и явная загрузка — загрузка по требованию. Правильный выбор помогает избежать проблемы N+1 и ненужных обращений к базе данных.

Жадная загрузка с Include

Жадная загрузка использует Include(), чтобы присоединить связанные данные в том же SQL-запросе. Это наиболее предсказуемая стратегия, позволяющая избежать проблемы N+1.

// Single query with JOIN
var blogs = await _db.Blogs
    .Include(b => b.Posts)
    .Where(b => b.IsActive)
    .ToListAsync();

// SQL: SELECT * FROM Blogs LEFT JOIN Posts ON ...

ThenInclude для вложенных связей

ThenInclude позволяет перейти глубже по графу навигации. Вы можете загрузить несколько уровней одним запросом.

var orders = await _db.Orders
    .Include(o => o.Customer)
    .Include(o => o.Lines)
        .ThenInclude(l => l.Product)
            .ThenInclude(p => p.Category)
    .Where(o => o.Status == OrderStatus.Pending)
    .ToListAsync();

Фильтрация в Include

EF Core 5+ поддерживает фильтрацию связанных данных внутри Include. Это позволяет загрузить только часть дочерних сущностей без отдельного запроса.

// Load only published posts for each blog
var blogs = await _db.Blogs
    .Include(b => b.Posts.Where(p => p.IsPublished)
                         .OrderByDescending(p => p.PublishedAt)
                         .Take(5))
    .ToListAsync();

Настройка ленивой загрузки

Ленивая загрузка автоматически загружает навигационные свойства при первом обращении к ним. Включите её, установив пакет Microsoft.EntityFrameworkCore.Proxies и вызвав UseLazyLoadingProxies().

// 1. Install package: dotnet add package Microsoft.EntityFrameworkCore.Proxies
// 2. Enable in DbContext setup:
builder.Services.AddDbContext<AppDbContext>(opt =>
    opt.UseSqlite(connStr)
       .UseLazyLoadingProxies());

// 3. Mark navigation properties as virtual:
public class Blog
{
    public int Id { get; set; }
    public virtual ICollection<Post> Posts { get; set; } = new List<Post>();
}

Ленивая загрузка на практике (и ловушка N+1)

Ленивая загрузка удобна, но опасна в циклах: каждое обращение запускает новый SQL-запрос, вызывая проблему N+1. В сценариях со списками всегда отдавайте предпочтение жадной загрузке.

var blogs = await _db.Blogs.ToListAsync(); // 1 query

foreach (var blog in blogs)
{
    // BAD: each access fires a new query!
    var count = blog.Posts.Count; // N queries
    Console.WriteLine(count);
}

// FIX: Use eager loading
var blogs2 = await _db.Blogs.Include(b => b.Posts).ToListAsync();

Явная загрузка

Явная загрузка позволяет загрузить навигационное свойство по требованию с помощью Entry().Collection().LoadAsync() или Entry().Reference().LoadAsync(). Это полезно, когда связанные данные нужны только при определённых условиях.

var blog = await _db.Blogs.FindAsync(1); // no posts loaded

// Load posts only when needed
if (needsPosts)
{
    await _db.Entry(blog!)
             .Collection(b => b.Posts)
             .LoadAsync();
}

// Load single reference
var post = await _db.Posts.FindAsync(99);
await _db.Entry(post!)
         .Reference(p => p.Blog)
         .LoadAsync();

Явная загрузка с фильтром запроса

Можно отфильтровать явную загрузку с помощью .Query() перед вызовом LoadAsync(), уменьшив объём возвращаемых данных.

var blog = await _db.Blogs.FindAsync(1);

// Only load posts published this year
await _db.Entry(blog!)
         .Collection(b => b.Posts)
         .Query()
         .Where(p => p.PublishedAt.Year == DateTime.UtcNow.Year)
         .LoadAsync();

Console.WriteLine(blog!.Posts.Count);

Раздельные запросы для больших Include

Загрузка нескольких коллекций в одном запросе приводит к декартову взрыву (количество строк умножается). Используйте AsSplitQuery(), чтобы выполнять отдельные запросы для каждой коллекции, объединяя результаты в памяти.

var orders = await _db.Orders
    .Include(o => o.Lines)
    .Include(o => o.Shipments)
    .AsSplitQuery()  // 3 separate SELECTs instead of one giant JOIN
    .ToListAsync();

// Or configure globally:
opt.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery);

Проекция для предотвращения загрузки лишних данных

Вместо загрузки полных сущностей со всеми связанными данными создайте проекцию в DTO с помощью Select. Так Вы получите ровно нужные данные одним оптимизированным запросом.

var summaries = await _db.Blogs
    .Select(b => new BlogSummaryDto
    {
        Name      = b.Name,
        PostCount = b.Posts.Count(p => p.IsPublished),
        LatestPost = b.Posts
            .Where(p => p.IsPublished)
            .OrderByDescending(p => p.PublishedAt)
            .Select(p => p.Title)
            .FirstOrDefault()
    })
    .ToListAsync();
// No Include needed - translated to subqueries

Реальный пример: выбор правильной стратегии

На практике используйте следующую схему выбора:

  • Жадная загрузка + Include: связанные данные нужны всегда, например на страницах со списками
  • Проекция: представления только для чтения, ответы API — самый эффективный вариант
  • Явная загрузка: условная загрузка в зависимости от логики приложения
  • Ленивая загрузка: избегайте в веб-приложениях; используйте только в настольных приложениях или сценариях с небольшими наборами данных

Быстрая проверка

Что представляет собой проблема N+1 в контексте ленивой загрузки?

Итоги: стратегии загрузки

Главное:

  • Жадная загрузка (Include): загружает данные одним запросом — лучший вариант при предсказуемых сценариях доступа
  • Фильтруемый Include: загружает только часть дочерних объектов — начиная с EF Core 5+
  • Ленивая загрузка: удобна, но рискованна — избегайте её в циклах (проблема N+1)
  • Явная загрузка: загружает данные по требованию и обеспечивает детальный контроль
  • Проекция с помощью Select: самый эффективный вариант для сценариев только для чтения
  • Разделение запросов: предотвращает декартов взрыв при включении нескольких коллекций

Часто задаваемые вопросы

Урок «Жадная, ленивая и явная загрузка» бесплатный?

Да — полный текст урока «Жадная, ленивая и явная загрузка» бесплатно доступен здесь в веб-версии. Чтобы практиковать его интерактивно (встроенный редактор кода и ИИ-репетитор 24/7) и разблокировать остальной курс C# Academy, подпишись на CoddyKit PRO. Курс C# Academy содержит 4 уроков всего.

Чему я научусь в уроке «Жадная, ленивая и явная загрузка»?

Управляйте моментом загрузки связанных данных с помощью Include, ThenInclude, прокси ленивой загрузки и явных вызовов Load. Ты практикуешь C# Academy с помощью реального кода, который запускаешь прямо в браузере, и ИИ-репетитор 24/7 отвечает на твои вопросы во время урока.

Нужен ли мне опыт, чтобы начать C# Academy?

Предыдущий опыт не требуется. C# Academy на CoddyKit структурирован для всех уровней — от новичков до продвинутых, поэтому ты можешь начать отсюда или с самого начала и учиться в своем темпе. Это урок 1 из 4.

Сколько времени занимает урок «Жадная, ленивая и явная загрузка»?

Большинство уроков CoddyKit занимают около 5–10 минут. Каждый из них компактный и интерактивный, поэтому ты постоянно делаешь прогресс и продолжаешь с того же места в веб-версии и приложении.

Можно ли писать и запускать код в этом уроке C# Academy?

Да. Каждый урок C# Academy включает встроенный редактор кода, поэтому ты пишешь и запускаешь реальный код прямо в браузере и получаешь моментальную обратную связь от AI — локальная установка не требуется.

Все уроки этого курса

  1. Жадная, ленивая и явная загрузка
  2. Сырой SQL, хранимые процедуры и интерполяция
  3. Компилируемые запросы и производительность
  4. Глобальные фильтры запросов и принадлежащие сущности
← Назад к C# Academy