In Entity Framework Core, lazy loading postpones the retrieval of related entities until those properties are explicitly accessed in your application code.
Example:
| var cars = context.Cars.ToList();
foreach (var car in cars) { Console.WriteLine(car.Manufacturer.Name); // Manufacturer gets loaded lazily here } |
Even though you execute context.Cars.ToList(), EF Core issues additional queries whenever car.Manufacturer.Name is referenced, triggering multiple round-trips to the database.
Problem
Let’s say there are 10,000 cars.
EF will produce the following if lazy loading is enabled and every student has a related car entity:
-
- 1 query for car
- 10,000 separate queries for the manufacturer
That’s 10,001 SQL queries for what could’ve been done with 1 query using .Include().
This issue stems from the classic N+1 query problem, where excessive queries can unintentionally overload your database, mimicking denial-of-service behavior under high load.
The Reasons It Seems Like a DDoS Attack
In large or concurrent systems (e.g., web APIs handling multiple users):
-
- These grow under load, becoming thousands of tiny queries every second.
- Multiple database queries are triggered by each user per request.
- Every query results in locks, connection pool usage, CPU expense, and network I/O.
You are essentially self-DDoSing your database when your connection pool maxes out, your database CPU spikes, and everyone’s latency increases.
Real-World Example
Suppose you have this model:
| public class Car {
public int Id { get; set; } public Manufacturer Manufacturer { get; set; } public List<CarFeature> Features { get; set; } } |
And this code:
| var cars = context.Cars.ToList();
foreach (var car in cars){ Console.WriteLine($”{car.Manufacturer.Name} car has {car.Features.Count} features”); } |
With lazy loading enabled:
-
- 1 query → Get all Cars
- N queries → For each car, fetch its Manufacturer
- N queries → For each car, fetch its Features
Total = 1 + N + N → Can easily become thousands of queries if you have many cars.
Why Lazy Loading is Dangerous
| Problem | Effect |
| Multiple Queries per Request | Causes the connection pool to exhaust rapidly due to numerous small, unplanned queries generated by the N+1 problem. |
| Hidden Behind Innocent Code | Developers often don’t realize the performance impact because the related data loading is implicit and happens automatically, only noticing the issue when performance tanks in production. |
| Works Fine on Small Datasets | The performance degradation is often masked during development and testing, only to break under real-world load when many entities are accessed. |
| No Caching by Default | It results in repeated DB roundtrips to fetch the same related entities, wasting resources and slowing down response times. |
| Difficult to Diagnose | The full scope of the problem is not immediately visible; it can only be seen by profiling or turning on SQL logging, making it a sneaky bug. |
How to Fix Lazy Loading Issues
1. Disable Lazy Loading
The simplest fix is to turn off Lazy Loading by default unless strictly necessary.
-
- Method: Set optionsBuilder.UseLazyLoadingProxies(false) in your context configuration.
- Alternative: Don’t install Microsoft.EntityFrameworkCore.Proxies package at all.
2. Use Eager Loading with .Include()
This is the recommended approach for fetching related data efficiently.
-
- Method: Use the .Include() method in your LINQ queries to explicitly specify related entities you want to load.
- Example: context.cars.Include(s => s.manufacturer.Name).ToList();
- Benefit: Fetches all necessary data in one optimized query, avoiding the “N+1” query problem associated with Lazy Loading.
- Method: Use the .Include() method in your LINQ queries to explicitly specify related entities you want to load.
3. Use Explicit Loading When Needed
Use this when you only occasionally need related data for an already tracked entity.
-
- Method: Use the Entry() and Reference() (or Collection()) methods on the context to manually trigger the loading of a specific related entity.
- Example: context.Entry(cars).Reference(s => s.manufacturer.Name).Load();
- Benefit: You control when the data is loaded, preventing unnecessary database hits.
- Method: Use the Entry() and Reference() (or Collection()) methods on the context to manually trigger the loading of a specific related entity.
4. Profile Your Queries
Use logging to monitor the database activity and verify performance.
-
- Method: Turn on logging using optionsBuilder.LogTo(Console.WriteLine, LogLevel.Information) to see the SQL queries being executed.
- Benefit: Helps you identify the “flood of queries” that occurs when Lazy Loading is active and confirm if your fixes are working.



