Loading Strategies Overview
When you query an entity in EF Core, its navigation properties are not loaded by default. This is a deliberate design decision that gives you control over how much data is retrieved from the database.
Why Related Data Isn't Loaded Automatically
Consider a Blog entity with thousands of Posts, each with hundreds of Comments. If EF Core automatically loaded all related data every time you queried a blog, a simple lookup could pull the entire database into memory.
csharp1// This returns a Blog with Posts = null (not loaded)2var blog = await context.Blogs.FirstAsync(b => b.Id == 1);34Console.WriteLine(blog.Title); // Works fine5Console.WriteLine(blog.Posts.Count); // NullReferenceException!
EF Core requires you to explicitly declare when and how related data should be loaded. This keeps queries efficient and predictable.
The Three Loading Strategies
EF Core provides three strategies for loading related data:
| Strategy | When Data Is Loaded | Method |
|---|---|---|
| Eager Loading | At query time, in the same query | Include() / ThenInclude() |
| Explicit Loading | On demand, after the parent is loaded | Entry().Collection().LoadAsync() |
| Lazy Loading | Automatically when navigation is accessed | Virtual properties + proxies |
Eager Loading
Eager loading retrieves related data as part of the initial query using Include(). The related data is loaded in one round trip to the database.
csharp1var blog = await context.Blogs2 .Include(b => b.Posts)3 .FirstAsync(b => b.Id == 1);45// blog.Posts is populated - no additional queries needed6foreach (var post in blog.Posts)7{8 Console.WriteLine(post.Title);9}
Best for: When you know upfront that you need the related data. Most common strategy.
Explicit Loading
Explicit loading lets you load related data after the parent entity is already in memory. You control exactly when the additional query runs.
csharp1var blog = await context.Blogs.FirstAsync(b => b.Id == 1);23// Load posts on demand4await context.Entry(blog)5 .Collection(b => b.Posts)6 .LoadAsync();
Best for: When you conditionally need related data based on some logic.
Lazy Loading
Lazy loading automatically loads related data when you first access a navigation property. It requires configuration with proxies or injection.
csharp1// With lazy loading configured:2var blog = await context.Blogs.FirstAsync(b => b.Id == 1);34// Accessing Posts triggers a database query automatically5foreach (var post in blog.Posts) // Query happens here6{7 Console.WriteLine(post.Title);8}
Best for: Prototyping or when access patterns are unpredictable. Use with caution in production.
Choosing the Right Strategy
1Do you always need the related data?2├── Yes → Eager Loading (Include)3└── No4 ├── Do you need it conditionally? → Explicit Loading5 └── Is it unpredictable? → Lazy Loading (with caution)
General guidance:
- Start with eager loading - it is the most explicit and predictable approach
- Use explicit loading when the decision to load depends on runtime conditions
- Avoid lazy loading in production web applications - it causes the N+1 query problem, where accessing a list of parents with their children generates one query per parent instead of a single batch query
Key Takeaways
- EF Core does not load navigation properties by default
- Eager loading (
Include) loads related data in the initial query - Explicit loading (
Entry().LoadAsync()) loads data on demand after the parent is loaded - Lazy loading loads data automatically on property access but risks N+1 queries
- Eager loading is the recommended default for most scenarios