0Pricing
C# Academy · Lesson

Eager, Lazy & Explicit Loading

Control when related data is loaded using Include, ThenInclude, lazy loading proxies, and explicit Load calls.

Eager, Lazy & Explicit Loading is a free C# Academy lesson on CoddyKit — lesson 1 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the C# Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Three Loading Strategies

EF Core offers three strategies for loading related data: Eager Loading (load together with query), Lazy Loading (load on access), and Explicit Loading (load on demand). Choosing the right one avoids the N+1 problem and unnecessary database roundtrips.

Eager Loading with Include

Eager loading uses Include() to JOIN related data in the same SQL query. This is the most predictable strategy and avoids N+1 issues.

// 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 for Nested Relations

ThenInclude chains deeper into the navigation graph. You can load multiple levels in a single query.

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();

Filtered Include

EF Core 5+ supports filtering related data inside Include. This lets you load a subset of children without a separate query.

// 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();

Lazy Loading Setup

Lazy loading automatically loads navigation properties the first time they are accessed. Enable it by installing the Microsoft.EntityFrameworkCore.Proxies package and calling 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>();
}

Lazy Loading in Action (and the N+1 Trap)

Lazy loading is convenient but dangerous in loops — each access fires a new SQL query, causing the N+1 problem. Always prefer eager loading in list scenarios.

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();

Explicit Loading

Explicit loading lets you load a navigation property on demand using Entry().Collection().LoadAsync() or Entry().Reference().LoadAsync(). Useful when you conditionally need related data.

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();

Explicit Loading with Query Filter

You can filter an explicit load using .Query() before calling LoadAsync(), reducing the amount of data returned.

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);

Split Queries for Large Includes

Loading multiple collections in one query causes a Cartesian explosion (rows multiply). Use AsSplitQuery() to issue separate queries per collection, joined in memory.

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);

Projection to Avoid Over-Fetching

Instead of loading full entities with all their relations, project to a DTO with Select. This gives you exactly the data you need in a single optimized query.

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

Real-World: Choosing the Right Strategy

Use this decision guide in practice:

  • Eager + Include: you always need the related data, list pages
  • Projection: read-only views, API responses — most efficient
  • Explicit: conditional loading based on app logic
  • Lazy: avoid in web apps; only for desktop/scripting with small data sets

Quick Check

What is the N+1 problem in the context of lazy loading?

Recap: Loading Strategies

Key takeaways:

  • Eager loading (Include): loads data in one query — best for predictable access patterns
  • Filtered Include: load only a subset of children — EF Core 5+
  • Lazy loading: convenient but risky — avoid in loops (N+1 problem)
  • Explicit loading: load on demand with fine-grained control
  • Projection with Select: most efficient for read-only scenarios
  • Split queries: avoid Cartesian explosion with multiple collection includes

Frequently asked questions

Is the “Eager, Lazy & Explicit Loading” lesson free?

Yes — the full text of “Eager, Lazy & Explicit Loading” is free to read here on the web, and the C# Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the C# Academy course, upgrade to CoddyKit PRO.

What will I learn in “Eager, Lazy & Explicit Loading”?

Control when related data is loaded using Include, ThenInclude, lazy loading proxies, and explicit Load calls. You practise C# Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start C# Academy?

No prior experience is required. C# Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 1 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Eager, Lazy & Explicit Loading” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this C# Academy lesson?

Yes. Every C# Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Eager, Lazy & Explicit Loading
  2. Raw SQL, Stored Procedures & Interpolation
  3. Compiled Queries & Performance
  4. Global Query Filters & Owned Entities
← Back to C# Academy