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 subqueriesReal-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
- Eager, Lazy & Explicit Loading
- Raw SQL, Stored Procedures & Interpolation
- Compiled Queries & Performance
- Global Query Filters & Owned Entities