.NET Tools How-To's

Eager, Lazy and Explicit Loading with Entity Framework Core

Entity Framework Core (EF Core) supports a number of ways to load related data. There’s eager loading, lazy loading, and explicit loading. Each of these approaches have their own advantages and drawbacks. In this post, let’s have a quick look at each of these ways to load data for navigational properties!

Eager loading

Eager loading is when you query one type of entity and immediately load related entities as part of it. For example, loading an Invoice and immediately bringing in a collection of InvoiceLine:

var invoices = db.Invoices
    .Include(invoice => invoice.InvoiceLines)
    .ToList();

// All invoices are already loaded...
foreach (var invoice in invoices)
{
    // ...including all their Invoice lines
    foreach (var invoiceLine in invoice.InvoiceLines)
    {
        // ...
    }
}

It’s even possible to include navigational properties of InvoiceLine, for example including the referenced Product:

var invoices = db.Invoices
    .Include(invoice => invoice.InvoiceLines)
    .ThenInclude(invoiceLine => invoiceLine.Product)
    .ToList();

// All invoices are already loaded...
foreach (var invoice in invoices)
{
    // ...including all their invoice lines...
    foreach (var invoiceLine in invoice.InvoiceLines)
    {
        // ...and related product!
        var product = invoiceLine.Product;

        // ...
    }
}

With eager loading, you can look at the .Include() (and .ThenInclude()) calls to see which navigational properties will be loaded from the database as part of the query. As a developer, I like how clearly this communicates what’s going on in code.

Eager loading does come with one issue: you have to remember to call .Include() to query related entities. If you forget to do this, your code will keep working, except for the fact that in our example no invoice lines may be loaded at all, or only a partial set is returned if some invoice lines were loaded before. As a result, your code will not be reliable.

ReSharper and JetBrains Rider both have inspections to help you out in such cases. When accessing any navigational property that has not been loaded, the IDE will tell you that the Usage of navigational property can return incomplete data.

As usual, there’s a quick-fix available through the Alt+Enter menu where the IDE can update the query for you.

ReSharper and JetBrains Rider then automatically add the necessary .Include() calls for navigational properties, including those for navigational properties of navigational properties.

Lazy loading

With lazy loading, navigational properties are queried automatically when needed. This way, you don’t have to remember to add all of those .Include() calls like with eager loading! Additionally, only the data you are actually working with is queried from the database.

var invoices = db.Invoices
    .ToList();

// All invoices are already loaded...
foreach (var invoice in invoices)
{
    // ...invoice lines are queried when accessed...
    foreach (var invoiceLine in invoice.InvoiceLines)
    {
        // ...the related product is also queried when accessed
        var product = invoiceLine.Product;

        // ...
    }
}

Lazy loading does come at a cost: there’s a big risk that for each Invoice entity you process (N), another query is run on the database to fetch the collection of InvoiceLine for that entity (+1). As a result, the database may have to process N+1 queries instead of just one query in the eager loading example we saw earlier.

The N+1 problem becomes very visible when using a (database) profiler. Tools like Dynamic Program Analysis can highlight the number of queries executed. With lazy loading, you’ll see many more queries being executed than you may have expected.

If you do decide to use lazy loading in your application, a similar inspection is available in ReSharper and JetBrains Rider: Possible multiple queries to the database (N+1 problem).

Alt+Enter provides a quick-fix that switches to eager loading for this particular query, again adding .Include() for navigational properties that are accessed.

Explicit loading

Explicit loading is somewhat similar to lazy loading, in that data from navigational properties is only queried from the database when it’s needed. The difference with lazy loading is that you’ll have to query the related data explicitly. If you don’t, only a partial or empty collection of invoice lines will be available, which would make your code unreliable.

Therefore, you’ll have to load the navigational data explicitly by asking the database context:

var invoices = db.Invoices
    .ToList();

// All invoices are already loaded...
foreach (var invoice in invoices)
{
    // ...but you'll have to explicitly load invoice lines when they are needed
    db.Entry(invoice).Collection(p => p.InvoiceLines).Load();

    foreach (var invoiceLine in invoice.InvoiceLines)
    {
        // ...
    }
}

With explicit loading, it becomes very clear where navigational data is queried in code. However, the N+1 issue is also present here, as each time an invoice is processed its invoice lines are queried separately.

Summary

In this post, we have seen different mechanisms for loading related data with Entity Framework Core:

  • Eager loading queries navigational data together with the main entity type, but you have to remember to call `.Include()` for each of them to do so.
  • Lazy loading makes querying navigational data implicit, fetching data from the database when it’s accessed. The downside is that this may result in running too many queries due to the N+1 problem.
  • Explicit loading is similar, in that related data is only loaded when you need it. The downside is that you have to remember to load the data explicitly, and the N+1 problem is also very likely with this approach.

Over the past years, I’ve seen many projects where the N+1 problem was causing performance issues, I’m happy the EF Core team did a great job steering developers towards eager loading, while making lazy loading inconvenient to enable (also see Entity Framework Core – Pitfalls To Avoid). It’s definitely an option that may make sense for your scenario.

Especially combined with tools like ReSharper and JetBrains Rider suggesting to add the necessary .Include() calls, eager loading is a great default approach for most codebases using EF Core.

Image credit: Jacalyn Beales on Unsplash

image description

Discover more