By default, related objects are not loaded immediately. They are loaded on demand when we access them.
Carefully observe the relationship between the tables shown above:
- Category and Post have a one-to-many relationship. This means there can be several posts in the same category.
- Post and comments have a one-to-many relationship, which means there can be many comments on a post.
- User and Comment have a one-to-many relationship, which means a user may post several comments on a post.
Example 1. Fetch number of posts in each category.
public class Program
{
public static void Main()
{
var db = new BlogDbContext();
var categories = db.Categories.ToList();
foreach(var cat in categories)
{
Console.WriteLine("Category :{0} | Number of Posts :{1}", cat.CategoryName,cat.Posts.Count());
}
Console.ReadLine();
}
}
In the above example, we are first loading all the categories into the memory using the ToList() method, which causes immediate execution, but even with the immediate execution, we couldn't load the related objects. This means that db.categories.ToList() query only loads the category objects into the memory and not the related post objects.
When we iterate over the query variable and fetch any property of the related object, then only the LINQ provider fires the necessary T-SQL to the database and loads the related object into memory. In our case, cat.Posts.Count() statement loads the post object (count) into the memory.
Output:
N+1 Issue
Lazy loading causes n+1 issue, meaning that to get N entities and their related entities, you will end up with n+1 query requests to the database. The diagram shown below explains it in more detail:
How does Lazy Loading work behind the scenes?
When we generate an entity for any type of relationship, the corresponding navigation properties are also created. When you look closely, you will find that all navigation properties are marked as virtual. See the following entities generated by Entity Framework:
If you have a little knowledge about the OOPs polymorphism concept, you would know that the virtual keyword is used to achieve run-time polymorphism. We can change the behavior of a method/property at run time by simply overriding it. The same concept is being applied here; at run time, the navigation properties change their behavior, as shown below.
So, with each iteration, a new request goes to the server to load the related entities, which results in too many requests and degrades the performance, so it's better not to use lazy loading unless you really need it.
How to Disable Lazy Loading
Lazy loading is enabled by default. In order to deactivate it, you have two options:
- Delete the virtual keyword from navigation properties.
- Disable it for the whole project by configuring it in the DbContext as shown below:
Disable Lazy Loading