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.
Let’s begin by setting up a sample application.
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.Sqlite
package 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.
Can I just say, as a resource for reference, your site is invaluable for programmers. Well done and long may it continue.
PS I bought your book and Wasm course and looking forward to a 3rd Edition.
Thank you, David, for the kind words. We will do our best to continue this way.
Also, thank you for purchasing our courses. Yes, we are thinking about the API 3rd edition as .NET 6 (the version we started the 2nd edition with) is no longer maintained. We will introduce a lot of improvements in the course and also we are preparing a new platform to unify all the courses in a single place, with a better reading experience, etc. We are preparing new courses as we speak. Of course, you all will be notified about that, but being a subscriber helps a lot with those notifications 🙂