← Back to list

Lazy Loading vs. Eager Loading in EF Core: When to Use What?

In modern .NET projects, many database operations are managed with ORM tools. Entity Framework Core is one of the most powerful libraries…

Ali in DotnetAsync · 2025-12-05 08:24 · 3 claps · 3.6 min read paywalled
#dotnet-core #software-engineering #entity-framework-core #lazy-loading #eager-loading
Open on Medium ↗

Lazy Loading vs. Eager Loading in EF Core: When to Use What?

In modern .NET projects, many database operations are managed with ORM tools. Entity Framework Core is one of the most powerful libraries in the .NET ecosystem. But using EF Core efficiently is not only about knowing CRUD operations. There is a very important topic that directly affects performance: how related data is loaded.

In this article, we will look in detail at the three main loading strategies in EF Core, their architecture effects, performance results and practical examples:

  • Eager Loading
  • Lazy Loading
  • Explicit Loading

In each section, we will explain the idea, how it works, best use cases, common mistakes, and performance analysis.

Why Are Loading Strategies Important?

When an entity (for example Order) is queried, how will the related data (Customer, OrderDetails, Product) be loaded?

The answer affects performance, memory usage, SQL traffic, and even user experience. A wrong loading strategy usually causes:

  • Unnecessary JOIN operations
  • Many extra SQL queries
  • Loading unnecessary data
  • The N+1 problem
  • High memory usage

The correct strategy means faster pages, less memory use, and cleaner SQL.

1. EAGER LOADING

This is the strategy where related data is loaded early. When the query runs, all needed related data is loaded at the same time.

How Does Eager Loading Work?

You use Include and ThenInclude to load related tables with the main query.

var orders = await context.Orders
    .Include(o => o.Customer)
    .Include(o => o.OrderDetails)
        .ThenInclude(od => od.Product)
    .ToListAsync();

EF Core can create a big JOIN or multiple SELECT queries. You can control this with AsSingleQuery() or AsSplitQuery().

var orders = context.Orders
    .Include(o => o.OrderDetails)
    .AsSplitQuery()
    .ToList();

Advantages

  • Related data is loaded in one operation.
  • The N+1 problem is avoided.
  • The code is clean and readable.

Disadvantages

  • Too many Include statements can create large JOINs and high memory usage.
  • You may load data that you do not need.

When To Use It?

  • When a page or API needs all related data at once.
  • When the N+1 problem may happen.

Photo by Mohammad Rahmani on Unsplash

Photo by Mohammad Rahmani on Unsplash

2. LAZY LOADING

In lazy loading, related data is not loaded until you access the navigation property. When you access the property, EF sends a new SQL query.

Lazy loading is not active by default in EF Core.

How To Enable Lazy Loading?

  1. Install the proxy package:
Microsoft.EntityFrameworkCore.Proxies
  1. Configure DbContext:
options.UseLazyLoadingProxies();
  1. Make navigation properties virtual:
public virtual Customer Customer { get; set; }

Lazy Loading Example

var order = await context.Orders.FirstAsync();
var customerName = order.Customer.Name; // New SQL query runs here

How Does Lazy Loading Actually Work? (The Real Mechanism)

EF Core requires two things for Lazy Loading to function:

  • A Dynamic Proxy.
  • The Navigation property must be virtual.

EF generates a proxy class for the entity at runtime (when the program is running).

Example:

A regular class like this:

public class Student { ... }

In the background, is transformed into something like this:

StudentProxy : Student
{
    override Address get Address {
        // This is the added logic (the 'aspect')
        if(not loaded) LoadAddress(); 
        return address;
    }
}

For this reason, Lazy Loading behaves like AOP (Aspect-Oriented Programming), as it inserts extra logic (the loading mechanism) into the method access without changing the original class structure.

Advantages

  • The first query is very light.
  • The code is simple and easy to read.

Disadvantages

  • It can cause the N+1 problem.
  • When looping through a list, it may send many queries without noticing.
  • Harder to test and monitor performance.

When To Use It?

  • Small applications or prototypes.
  • When related data is rarely needed.

When Should Lazy Loading Absolutely Never Be Used?

Lazy Loading is generally considered a poor choice in several modern software development scenarios:

  • API Projects: Lazy loading should be avoided in API projects because it frequently causes a circular reference loop during JSON serialization. This results in runtime errors unless specific serializer configurations are applied.
  • Large Lists: Using lazy loading on large collections or lists causes hundreds of hidden queries to be executed automatically. This phenomenon is known as the N+1 problem and severely impacts application performance.
  • Microservices: In a distributed Microservices environment, the execution of many queries per unit of time puts excessive load on the database. This pattern actively leads to severe scaling problems across the system.
  • CQRS or DDD Architecture: When implementing Command Query Responsibility Segregation (CQRS) or Domain-Driven Design (DDD), uncontrolled data access is an anti-pattern. Lazy loading automatically loads data outside the defined boundaries of the Aggregate Root, compromising architectural integrity.

Serialization Error with Lazy Loading (Common Mistake)

EF proxies conflict with the JSON serializer.

If you write an API like the one below, you will get a circular reference error:

[HttpGet]
public IActionResult GetStudent()
{
    var student = _context.Students.First();
    return Ok(student); // ❌ Circular Reference Error
}

Solution:

  • Use Eager Loading instead of Lazy Loading.
  • Use DTOs (Data Transfer Objects).
  • Add ReferenceHandler.IgnoreCycles to JsonOptions.

3. EXPLICIT LOADING

With explicit loading, related data is loaded only when the developer requests it. It is the most controlled method.

How To Use Explicit Loading?

var order = await context.Orders.FindAsync(id);

await context.Entry(order)
    .Reference(o => o.Customer)
    .LoadAsync();

await context.Entry(order)
    .Collection(o => o.OrderDetails)
    .LoadAsync();

You can also use filtered loading:

await context.Entry(order)
    .Collection(o => o.OrderDetails)
    .Query()
    .Where(od => od.Quantity > 0)
    .LoadAsync();

Advantages

  • Full control over loading.
  • No unnecessary loading.
  • You can filter before loading.

Disadvantages

  • More manual code.
  • Harder to manage with complex graphs.

When To Use It?

  • Performance-critical reports.
  • When you need full control of when queries run.

메타데이터
post_id
04569675fa88
slug
lazy-loading-vs-eager-loading-in-ef-core-when-to-use-what-04569675fa88
url
https://medium.com/dotnetasync/lazy-loading-vs-eager-loading-in-ef-core-when-to-use-what-04569675fa88
canonical_url
https://medium.com/dotnetasync/lazy-loading-vs-eager-loading-in-ef-core-when-to-use-what-04569675fa88
author_url
https://medium.com/@Ali0
status
ok
fetched_at
2026-06-17 08:20:12