Lazy Loading is the "magic" capability where EF Core automatically pauses your C# code and reaches out to the database to fetch relational data the exact millisecond you access a navigation property. While it seems incredibly convenient, it is widely considered the most dangerous anti-pattern in modern API development.
Unlike older versions of Entity Framework, EF Core completely disables Lazy Loading by default to protect you. If you want it, you must explicitly install the Proxies package.
dotnet add package Microsoft.EntityFrameworkCore.Proxies
// 1. Activate it in Program.cs
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer("...")
.UseLazyLoadingProxies()); // Danger zone enabled
// 2. You MUST mark your Navigation Properties as "virtual"
public class Blog
{
public int Id { get; set; }
// The 'virtual' keyword allows EF Core to create a dynamic "Proxy" wrapper class
// that overrides this property with database-fetching logic
public virtual ICollection<Post> Posts { get; set; }
}
If you loop through data using Lazy Loading, you will accidentally execute hundreds of physical SQL queries without realizing it.
// 1 Query fired: SELECT * FROM Blogs
var blogs = _context.Blogs.ToList();
// Suppose there are 500 blogs.
foreach(var blog in blogs)
{
// EVERY TIME this loop iterates, the 'virtual' Posts property triggers an invisible SQL query!
// Result: 500 physical database queries fired in extreme rapid succession.
Console.WriteLine(blog.Posts.Count);
}
Total Queries: N + 1 (500 + 1). This will cripple your database server and cause your Web API endpoint to take 15 seconds to load.
To fix the N+1 problem, you explicitly disable Lazy Loading and revert to .Include().
// Firing exactly ONE query with a SQL JOIN!
var blogs = _context.Blogs.Include(b => b.Posts).ToList();
foreach(var blog in blogs)
{
// No database hits occur here, the data is already in RAM!
Console.WriteLine(blog.Posts.Count);
}
Q: "We enabled Lazy Loading. In our Web API Controller, we `return Ok(blog);` to send the entity to the frontend. The API immediately crashes with an immense `System.Text.Json.JsonException: A possible object cycle was detected`. Why did enabling Lazy Loading destroy our JSON serialization?"
Architect Answer: "When `System.Text.Json` converts your `Blog` object into a JSON string, it walks through every property on the C# class. When it hits the `virtual Posts` property, the Lazy Loading Proxy instantly fires a DB query to fetch the Posts. Then, the JSON serializer walks into the `Post` object, hits the `virtual Blog` property, and fires ANOTHER DB query to get the parent Blog. It loops infinitely between the Parent and Child until the server throws a StackOverflow or an Object Cycle exception. This is why you should never return physical EF Core entities directly from an API; you must project them into clean `DTOs` to sever the connection to the Lazy Loading proxies, or fundamentally disable Lazy Loading entirely."