Entity Framework Lazy Loading Not Working Fix
In this tutorial, you'll learn about Entity Framework Lazy Loading Not Working Fix. We cover key concepts, practical examples, and best practices.
Navigation properties return null even though the related data exists in the database:
var order = db.Orders.Find(1);
Console.WriteLine(order.Customer?.Name); // null
Lazy loading only works when your DbContext is not disposed and navigation properties are marked as virtual. EF Core requires explicit opt-in since version 3.0 — it is not enabled by default like in EF6.
Step-by-Step Fix
1. Install proxy package and enable lazy loading
WRONG — assuming lazy loading is automatic:
public class Order
{
public int Id { get; set; }
public Customer Customer { get; set; } // not virtual
}
RIGHT — make navigation properties virtual and enable lazy loading:
public class Order
{
public int Id { get; set; }
public virtual Customer Customer { get; set; }
}
Install the package:
dotnet add package Microsoft.EntityFrameworkCore.Proxies
Configure in OnConfiguring:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies()
.UseSqlServer(connectionString);
}
Or via AddDbContext:
services.AddDbContext<AppDbContext>(options =>
options.UseLazyLoadingProxies()
.UseSqlServer(connectionString));
2. Ensure DbContext is not disposed
WRONG — accessing navigation property after context disposal:
Customer customer;
using (var db = new AppDbContext())
{
var order = db.Orders.Find(1);
customer = order.Customer; // lazy load happens here
}
Console.WriteLine(customer.Name); // ObjectDisposedException
RIGHT — eager load with Include when working outside the context:
Customer customer;
using (var db = new AppDbContext())
{
var order = db.Orders.Include(o => o.Customer).FirstOrDefault(o => o.Id == 1);
customer = order.Customer;
}
Console.WriteLine(customer.Name);
3. Handle N+1 with lazy loading
WRONG — lazy loading causes N+1 queries in loops:
foreach (var order in db.Orders.ToList())
{
Console.WriteLine($"{order.Id}: {order.Customer.Name}");
// Each iteration fires a separate query for Customer!
}
RIGHT — eager load when iterating:
var orders = db.Orders.Include(o => o.Customer).ToList();
foreach (var order in orders)
{
Console.WriteLine($"{order.Id}: {order.Customer.Name}");
}
Expected output: a single query with JOIN, not N+1 queries.
4. Fix serialization circular reference
WRONG — lazy loading triggers infinite serialization:
var orders = db.Orders.ToList();
return JsonSerializer.Serialize(orders); // circular reference error
RIGHT — disable lazy loading for serialization or use DTOs:
var orderDtos = db.Orders.Select(o => new OrderDto
{
Id = o.Id,
CustomerName = o.Customer.Name
}).ToList();
return JsonSerializer.Serialize(orderDtos);
Prevention
- Use eager loading (Include/ThenInclude) by default; opt into lazy loading selectively.
- Disable lazy loading in production APIs to prevent N+1 issues.
- Use DTOs for serialization instead of entity classes.
- Mark navigation properties as virtual only if lazy loading is enabled.
- Never access navigation properties outside the DbContext's scope.
Common Mistakes with framework lazy loading
- Forgetting that lazy evaluation defers computation until the value is forced, causing space leaks with unevaluated thunks
- Using
returnto exit a function early instead of wrapping a pure value in the monad - Mixing let bindings with <- bindings in do notation, producing type errors
These mistakes appear frequently in real-world ENTITY code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.
Practice Exercise
Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.
This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.
FAQ
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro