In this article, we will learn about the loading strategies we can employ in EF Core. These strategies, namely Lazy Loading and Eager Loading control how data is fetched from the database.

We will also delve into performance impacts and considerations for choosing one over the other.

To download the source code for this article, you can visit our GitHub repository.

Let’s begin by setting up a sample application.

Support Code Maze on Patreon to get rid of ads and get the best discounts on our products!
Become a patron at Patreon!

Application Setup

We already prepared a simple console application that deals with books and authors. 

We use SQLite as our database, and for that, we need the required packages:

Install-Package Microsoft.EntityFrameworkCore.Sqlite
Install-Package Microsoft.EntityFrameworkCore.Tools

The Microsoft.EntityFrameworkCore.Sqlitepackage enables us to use SQLite as our database and the Microsoft.EntityFrameworkCore.Tools package enables migrations.

We have also prepared Book and Author model classes, the DataContext class, and the Data Seed class to seed the data to the database.

In the Package Manager console, we can use the “Add-Migration” and “Update-Database” commands to add a new migration and apply it to the database respectively:

Add-Migration Init
Update-Database

With our base application setup complete, let’s look at both lazy loading and eager loading strategies.

What Is Lazy Loading?

Lazy Loading or on-demand loading is a data loading strategy where we postpone the retrieval of data until the moment the application explicitly requests it. It helps us enhance the performance and resource efficiency of the application. 

Lazy Loading Implementation in Entity Framework

To enable Lazy Loading in Entity Framework, we must mark the navigation properties, both reference navigations as well as collection navigations, as virtual in the Book class:

public class Book
{
    public int BookId { get; set; }
    public string Title { get; set; }
    public int AuthorId { get; set; }
    public virtual Author Author { get; set; }
}

We need to do the same in the Author class:

public class Author
{
    public int AuthorId { get; set; }
    public string Name { get; set; }
    public virtual ICollection<Book> Books { get; set; }
}

Next, let’s install the Microsoft.EntityFrameworkCore.Proxies package to enable Lazy Loading using proxies in the Nuget package manager console:

Install-Package Microsoft.EntityFrameworkCore.Proxies

Finally, we can enable Lazy Loading with a call to the UseLazyLoadingProxies() method:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseLazyLoadingProxies()
        .UseSqlite($"Data Source={DbPath}")
        .LogTo(Console.WriteLine, LogLevel.Information);
}

With all the steps complete, let’s see it in action.

Let’s retrieve an author and all their corresponding books using Lazy Loading:

var authorLazyLoading = context.Authors.FirstOrDefault(a => a.AuthorId == authorId);
Console.WriteLine($"Author Name: {authorLazyLoading.Name}");

foreach (var book in authorLazyLoading.Books.ToList())
{
    Console.WriteLine($"Book Title: {book.Title}");
}

Here, the Entity Framework executes two separate queries, one for the Author record:

Executed DbCommand (3ms) [Parameters=[@__authorId_0='?' (DbType = Int32)], 
CommandType='Text', CommandTimeout='30']
      SELECT "a"."AuthorId", "a"."Name"
      FROM "Authors" AS "a"
      WHERE "a"."AuthorId" = @__authorId_0
      LIMIT 1
Author Name: George R.R. Martin

And another query for all the corresponding Book records:

 Executed DbCommand (0ms) [Parameters=[@__p_0='?' (DbType = Int32)],
 CommandType='Text', CommandTimeout='30']
      SELECT "b"."BookId", "b"."AuthorId", "b"."Title"
      FROM "Books" AS "b"
      WHERE "b"."AuthorId" = @__p_0
Book Title: A Game of Thrones
Book Title: A Clash of Kings

Thus, we don’t load the Book records into the memory till we need them. 

Advantages 

The primary advantage Lazy Loading offers is resource efficiency, as the data loads only when needed. Thus, conserving memory and reducing initial load times.

We also get improved application responsiveness, allowing the application to present essential content quickly. Lazy Loading is beneficial in scenarios with large datasets where only a subset of data is required.

Disadvantages of Lazy Loading

While it introduces notable improvements in resource efficiency, Lazy Loading also has potential drawbacks. A significant concern is the “N+1 query problem”.

When we try to load a collection of related entities, Lazy Loading ends up making more database queries than expected. We saw this briefly in the example scenario where it made 2 queries to get all the books of a particular author, the first one to get the author and then the second one to get all the books associated with the given author. 

Let’s consider a scenario where we want to get all the authors and their books. In this case, Lazy Loading will make 1 query to retrieve the list of all the Author records. As we iterate over each author, it’ll execute a separate query to retrieve their corresponding books. Thus, N queries to retrieve the books of N authors we retrieve from the first query. This can lead to significant performance overhead.

To address this problem, we can turn to Eager Loading.

What Is Eager Loading?

Eager Loading is a data loading strategy in Entity Framework where we load the related entities from the database along with the main entity in a single query.

This is in contrast to Lazy Loading, where we load the related entities only when explicitly accessed. It is particularly useful when we know upfront that we’ll need the related data to avoid the N+1 query problem.

Eager Loading Implementation in Entity Framework

To enable Eager Loading, we can use the Include() method. This method allows us to specify related entities to load along with the main entity in a query. We no longer need to mark the navigation properties virtual to use the Eager Loading strategy.

Let’s retrieve an author and all their corresponding books using Eager Loading:

var authorEagerLoading = context.Authors
    .Include(a => a.Books)
    .FirstOrDefault(a => a.AuthorId == authorId);

foreach (var book in authorEagerLoading.Books)
{
    Console.WriteLine($"Book Title: {book.Title}");
}

Here, Entity Framework executes a single query to retrieve both the Author as well as the Book records:

Executed DbCommand (0ms) [Parameters=[@__authorId_0='?' (DbType = Int32)],
 CommandType='Text', CommandTimeout='30']
      SELECT "t"."AuthorId", "t"."Name", "b"."BookId", "b"."AuthorId", "b"."Title"
      FROM (
          SELECT "a"."AuthorId", "a"."Name"
          FROM "Authors" AS "a"
          WHERE "a"."AuthorId" = @__authorId_0
          LIMIT 1
      ) AS "t"
      LEFT JOIN "Books" AS "b" ON "t"."AuthorId" = "b"."AuthorId"
      ORDER BY "t"."AuthorId"
Author Name: J.K. Rowling
Book Title: Harry Potter and the Philosopher's Stone
Book Title: Harry Potter and the Chamber of Secrets

Thus, we no longer face the “N+1 query problem”. In addition, Eager Loading also allows us to use ThenInclude() to load additional levels of related entities.

Let’s see it in action by creating a Review class:

public class Review
{
    public int ReviewId { get; set; }
    public string ReviewText { get; set; }
    public int BookId { get; set; }
    public Book Book { get; set; }
}

We can chain the calls to these methods to specify a complex graph of related entities to be eagerly loaded in a single query:

var authorsWithBooksAndReviews = context.Authors
    .Include(a => a.Books)                     
        .ThenInclude(b => b.Reviews)           
    .ToList();

Advantages of Eager Loading

One notable benefit of Eager Loading is the reduction of database round-trips. By loading related entities alongside the main entity in a single query, we minimize the need for subsequent database calls. This is beneficial in scenarios where fetching related data is inevitable, as it mitigates the N+1 query problem associated with Lazy Loading.

In addition, Eager Loading can help in scenarios where we need to pass data to the presentation layer. By pre-loading all necessary data, we can present a more responsive user experience to users without resorting to additional queries.

Disadvantages of the Same

While Eager Loading has its advantages, it is not without its drawbacks. One significant drawback is the potential for over-fetching data. Since with Eager Loading, we retrieve all related entities, it might bring more data into memory than the application immediately needs. This can lead to increased memory consumption and impact overall performance where the related data is not consistently utilized.

There may be scenarios where the application workflow requires selective loading of related entities based on specific user actions. In such cases, the up-front loading of all related data may be counterproductive and result in wasted resources. However, we can work around this scenario by implementing a paging mechanism in our application.

When to Use Lazy Loading

In terms of architecture, we can simplify the development process using Lazy Loading as it allows us to focus on immediate needs. It promotes a more modular and lightweight design, as we load the related data on-demand. Thus, it reduces the need for extensive upfront data retrieval and processing.

In applications dealing with extensive datasets like an e-commerce platform, loading all the related data at once can be impractical. Lazy Loading helps us in fetching additional information like reviews, or specifications only when required. This helps us minimize resource usage and thus improve performance.

In mobile applications where we are limited in terms of bandwidth and resources, Lazy Loading can help us deliver a more responsive user experience. Not all data needs to be present or visible all the time. Thus, we can get away with only having necessary data present initially and retrieving additional data based on a user action. For example, loading additional details of an item only when a user clicks on a specific element.

When to Use Eager Loading

When we have an application architecture where the relationships between entities are more predictable we can use eager loading as a more proactive approach to data retrieval. It makes sure all our data is readily available by loading it upfront. Hence, we can process the data without the need for any additional database hits. 

Eager Loading is effective in scenarios where our application performance depends on minimizing database round-trips. By fetching related data upfront, we reduce the need for subsequent requests to the database. Thus, resulting in a more responsive application.

In applications where a holistic view of data is critical, such as reports, or dashboards, we can use eager loading to present the complete set of data. In addition, Eager Loading is also useful in applications where we might want to rely on offline modes by employing caching. As we retrieve all the data initially and have it available to us, there is reduced dependency on real-time database queries.

Conclusion

In this article, we delved into Lazy Loading and Eager Loading. We learned how each strategy works and saw the scenarios in which each of them is useful. We also went over the best practices to consider when choosing one over another. Finally, the choice between Eager Loading and Lazy Loading depends on the specific needs of our application.

Liked it? Take a second to support Code Maze on Patreon and get the ad free reading experience!
Become a patron at Patreon!