Using EF Core, references can be eager loaded, explicitly loaded, and lazy loaded. With eager loading you load references by specifying what references should be included when defining the query. Specifying the query, you use the Include method to define what references should be included. This is best when knowing in advance the needed references. With explicit loading you explicitly invoke the Load method when references to single items or collections should be loaded. With lazy loading, you do not need to explicitly invoke the Load method. Instead, just accessing the property dynamically invokes a query to the database to retrieve the data for the needed references.
With EF Core, lazy loading is available since version 2.1. This article gives information on lazy loading which is very different to lazy loading with Entity Framework 6.
I usually prefer eager loading or explicit loading to lazy loading. With lazy loading, reading the code you can easily miss where queries to the database are done. More queries than needed could be done to retrieve data from the database. Using eager loading you can reduce the number of queries, and access relations with a single SQL query. Using explicit loading, usually the same number of queries like with lazy loading are done – it’s just shown explicitly where the database access is used.
Using lazy loading, every time a property is accessed and the data is not yet retrieved, a query to the database is done.
Because of the disadvantages of lazy loading, comparing the implementations of lazy loading between Entity Framework and EF Core, it’s now implemented in a different way. You need to explicitly turn it on.
If the issues don’t apply to your scenario, go ahead and use lazy loading. There’s just a little more work needed defining the model types, and doing some configuration.
To use lazy loading, an additional package needs to be added. Lazy loading is not available if you just use the package Microsoft.EntityFrameworkCore. For using lazy loading, the package Microsoft.EntityFrameworkCore.Proxies needs to be added to the project references. As mentioned previously, because of the lazy loading disadvantages, it’s not included by default with your EF Core projects.
Checking the dependencies, Microsoft.EntityFrameworkCore.Proxies is dependent on the package Castle.Core. Castle.Core is part of the Castle Project. The Castle Project consists of several interesting projects. One of these is DynamicProxy. EF Core makes use of this package for dynamic-loading proxies.
With the sample code I’m using Visual Studio 2019 and C# 8. In case you don’t have this versions yet, you can change the code easily to use C# 7 – just remove some features on nullable reference types.
Proxies are used to make lazy loading possible. After adding the reference to the NuGet package, proxies can be configured using the method
UseLazyLoadingProxies. This is an extension method to extend the
DbContextOptionsBuilder. This method turns on proxy creation and uses proxy services implemented in the referenced NuGet package.
With the sample code, the lazy loading proxies are configured using the DI container. In case you are not using the DI Container with your EF Core application, you can use the method
OnConfiguringmethod of the EF Core context.
Models and Context
The sample application defines the model classes
Book class contains a list of chapters with a one-to-many relation. The
Book type also references multiple
User types with different roles. A user can be the author, the reviewer, or the project editor. To make the class ready for lazy loading, the relations need to be specified with the virtual modifier. Behind the scenes, the created proxy class derives from the
Book class and overrides these properties. The proxy then loads the data needed on first access of the property.
With the EF Core context class
BooksContext, the fluent API is used to specify the relations. The book has a list of chapters, and a chapter belongs to one book – this is specified with
WithOne. One user is associated with multiple books with the relations
The sample code uses the fluent API to specify the relation. Relations can also be specified using annotations. The book Professional C# 7 and .NET Core 2.0 covers all variants.
To access books, LINQ queries can be done like the one shown passing a where clause. After iterating the books, the references to chapters, authors, reviewers, and editors are done just by accessing the properties of the
Accessing the books, from the LINQ statement a SQL query is generated to access different columns from the
SELECT [b].[BookId], [b].[AuthorId], [b].[EditorId], [b].[Publisher], [b].[ReviewerId], [b].[Title] FROM [Books] AS [b] WHERE [b].[Publisher] LIKE N'Wrox' + N'%' AND (LEFT([b].[Publisher], LEN(N'Wrox')) = N'Wrox')
With the first query, other tables than the
Books table are not accessed. However, accessing the Chapters property, this SQL query is done:
SELECT [e].[ChapterId], [e].[BookId], [e].[Number], [e].[Title] FROM [Chapters] AS [e] WHERE [e].[BookId] = @__get_Item_0
Later on in the code, accessing the Author, Reviewer, and Editor relations, more queries are done.
SELECT [e].[UserId], [e].[Name] FROM [Users] AS [e] WHERE [e].[UserId] = @__get_Item_0
When the data was not loaded previously, every time a property is accessed that maps to a related table, another query to the database is done. Behind the scenes, the query doesn’t return the defined
Book types, but instead the class
Castle.Proxies.BookProxy is returned. This class derives from the base class
Book and overrides virtual properties.
With lazy loading, just properties need to be accessed from the C# code to get the data from the database as needed. This is easy to do, but you might miss better performance by reducing the number of queries. Using explicit loading you do have the same number of queries, it’s just easier to detect from the source code. Using the
Collection method from the
EntityEntry type, 1:n relations can be retrieved from the program invoking the
Load method. Here, the same SQL statement is generated accessing the book chapters as with lazy loading. Accessing a 1:1 relation, the
Reference method is used – again with the
You can also use the
IsLoaded property to see if the related data is already loaded. The implementation of the
Load method itself checks if the related data is already loaded to not query the database another time if the data is already in memory.
With explicit loading the source code gets more complex when accessing the objects from the EF Core database context. Related data needs to be explicit loaded using the
Loadmethod from the
ReferenceEntryreturned from the
Referencemethods. The advantage using explicit loading is that you see it from the C# source code that additional SQL queries are done. Also, the model type doesn’t need special treatment. Here, virtual properties are no longer needed.
In case you already know in advance the needed loaded relations, eager loading can be used. This should be the preferred way to get the data from the database. You can get deep relations with just one query. Defining the LINQ query, you now add calls to the
Include method and specify the relations that should be included. The
Include method is an extension to the
IQueryable type defined in the
In case you have deeper relations, and need to access relation by relation, e.g. by accessing another relation from the
Chaptertype, you can use the
The SQL statement now becomes more complex. Not just the
Books table is queried, but with my current SQL Server provider two SELECT statements are done accessing the
Users table using
LEFT JOIN and
INNER JOIN. Now just one time information from the database is retrieved instead when accessing every single book:
SELECT [b].[BookId], [b].[AuthorId], [b].[EditorId], [b].[Publisher], [b].[ReviewerId], [b].[Title], [b.Editor].[UserId], [b.Editor].[Name], [b.Reviewer].[UserId], [b.Reviewer].[Name], [b.Author].[UserId], [b.Author].[Name] FROM [Books] AS [b] LEFT JOIN [Users] AS [b.Editor] ON [b].[EditorId] = [b.Editor].[UserId] LEFT JOIN [Users] AS [b.Reviewer] ON [b].[ReviewerId] = [b.Reviewer].[UserId] LEFT JOIN [Users] AS [b.Author] ON [b].[AuthorId] = [b.Author].[UserId] WHERE [b].[Publisher] LIKE N'Wrox' + N'%' AND (LEFT([b].[Publisher], LEN(N'Wrox')) = N'Wrox') ORDER BY [b].[BookId] SELECT [b.Chapters].[ChapterId], [b.Chapters].[BookId], [b.Chapters].[Number], [b.Chapters].[Title] FROM [Chapters] AS [b.Chapters] INNER JOIN ( SELECT DISTINCT [b0].[BookId] FROM [Books] AS [b0] LEFT JOIN [Users] AS [b.Editor0] ON [b0].[EditorId] = [b.Editor0].[UserId] LEFT JOIN [Users] AS [b.Reviewer0] ON [b0].[ReviewerId] = [b.Reviewer0].[UserId] LEFT JOIN [Users] AS [b.Author0] ON [b0].[AuthorId] = [b.Author0].[UserId] WHERE [b0].[Publisher] LIKE N'Wrox' + N'%' AND (LEFT([b0].[Publisher], LEN(N'Wrox')) = N'Wrox') ) AS [t] ON [b.Chapters].[BookId] = [t].[BookId] ORDER BY [t].[BookId]
Instead of accessing the database with every property accessing a relation, the data is loaded early with less SQL statements sent to the database. Similar to explicit loading, the model doesn’t need special treatment. This scenario can also be used to return the model type and have all the associated data as needed when you can’t access the context instance to get additional data.
Using models to load related data is easy as long as the context is available. However, you need to pay attention not to create too many queries accessing the data from the database. If you know the relations needed in advance, you can use eager loading.
With lazy loading, proxy classes are created that derive from the model type. The proxy class overrides virtual properties to retrieve data needed dynamically.
The sample code is based on an EF Core sample from the book Professional C# 7 and .NET Core 2.0. In the book and in the book’s source code repository you can find samples with eager and explicit loading.
If you’ve read this far, consider buying me a coffee which helps me staying up longer and writing more articles.
Interesting Links for this article:
Enjoy learning and programming!