Eager Loading in Entity Framework

Eager loading is a technique where EF loads the related entities along with the main entity. All entities are loaded in a single query to database thus saving bandwidth and crucial server CPU time. This is done using the Include method, which has two overloads. One of which takes navigation property as a string. The Other Include method is an extension method and far more flexible. In this tutorial, we learn how to make load the entities eagerly. We also show how to Eager Loading from multiple Levels and multiple Tables.

Dangers of lazy loading

The following query retrieves list of all products from the database. In the for loop we access the p.ProductModel.Name property, which comes from the related navigational property ProductModel

using (AdventureWorks db = new AdventureWorks())
{
    db.Database.Log = Console.Write;
 
    var product = (from p in db.Products
                   select p).ToList();
 
    foreach (var p in product)
    {
        Console.WriteLine("{0} {1} {2}", p.ProductID, p.Name, p.ProductModel.Name);
    }
    Console.ReadLine();
}

Consider you have 100 Products in your Product table. When you iterate through over the Products collection and access the ProductModel, the EF will fire a query to retrieve the details of the ProductModel. That means EF sends 100 queries for each of those100 Products which is very inefficient.

We can solve the above problem by loading the ProductModel eagerly. along with the Products.using a Single Query.

Eager Loading in Entity Framework

One of the ways we can load entities eagerly by using the Include method. Entity Framework creates a join query, when it sees the Include method, thus bringing all the records in one single query.

The code below demonstrates the use of Include method. Disable Lazy loading before trying out any of the codes. You should also log database command to console to see the SQL command that Entity Framework sends to the database.

There are two versions of the Include method available.

The default method where you need to specify the navigational Property as a string

The other one from the System.Data.Entity namespace. It takes a lambda expression, where you need to specify the navigational Property

Include default method

The following example uses the default method. As you can see we use .Include("ProductModel") on the Products model. The ProductModel is the name of the Navigational Property (not the name of the entity).

using (AdventureWorks db = new AdventureWorks())
{
    //Disable Lazy Loading 
    db.Configuration.LazyLoadingEnabled = false;
 
    //Log SQL Command to Console
    db.Database.Log = Console.Write;

    var product = (from p in db.Products
                   .Include("ProductModel")    //ProductModel table to be included in the result 
                   where p.ProductID == 814
                   select p).ToList();  

    foreach (var p in product)
    {
        Console.WriteLine("{0} {1} {2}", p.ProductID, p.Name, p.ProductModel.Name);
    }
}

The Corresponding SQL Query of the above method.

SELECT
    [Extent1].[ProductID] AS [ProductID],
    [Extent1].[Name] AS [Name],
    [Extent1].[ProductNumber] AS [ProductNumber],
    [Extent1].[MakeFlag] AS [MakeFlag],
    [Extent1].[FinishedGoodsFlag] AS [FinishedGoodsFlag],
    [Extent1].[Color] AS [Color],
    [Extent1].[SafetyStockLevel] AS [SafetyStockLevel],
    [Extent1].[ReorderPoint] AS [ReorderPoint],
    [Extent1].[StandardCost] AS [StandardCost],
    [Extent1].[ListPrice] AS [ListPrice],
    [Extent1].[Size] AS [Size],
    [Extent1].[SizeUnitMeasureCode] AS [SizeUnitMeasureCode],
    [Extent1].[WeightUnitMeasureCode] AS [WeightUnitMeasureCode],
    [Extent1].[Weight] AS [Weight],
    [Extent1].[DaysToManufacture] AS [DaysToManufacture],
    [Extent1].[ProductLine] AS [ProductLine],
    [Extent1].[Class] AS [Class],
    [Extent1].[Style] AS [Style],
    [Extent1].[ProductSubcategoryID] AS [ProductSubcategoryID],
    [Extent1].[ProductModelID] AS [ProductModelID],
    [Extent1].[SellStartDate] AS [SellStartDate],
    [Extent1].[SellEndDate] AS [SellEndDate],
    [Extent1].[DiscontinuedDate] AS [DiscontinuedDate],
    [Extent1].[rowguid] AS [rowguid],
    [Extent1].[ModifiedDate] AS [ModifiedDate],
    [Extent2].[ProductModelID] AS [ProductModelID1],
    [Extent2].[Name] AS [Name1],
    [Extent2].[CatalogDescription] AS [CatalogDescription],
    [Extent2].[Instructions] AS [Instructions],
    [Extent2].[rowguid] AS [rowguid1],
    [Extent2].[ModifiedDate] AS [ModifiedDate1]
    FROM  [Production].[Product] AS [Extent1]
    LEFT OUTER JOIN [Production].[ProductModel] AS [Extent2] ON [Extent1].[ProductModelID] = [Extent2].[ProductModelID]
    WHERE 814 = [Extent1].[ProductID]
 

Include Lambda Method

The Include method above takes the string as the input parameter. This can be little inconvenient as no compile-time errors are thrown in case any spelling mistakes. Hence it is advisable to use the Include extension method from the namespace System.Data.Entity

using System.Data.Entity;     //import it first


var product = (from p in db.Products
               .Include(p => p.ProductModel) // Using Lambda Expression instead of a string
               where p.ProductID == 814
               select p).ToList();

The method IncludeThen is introduced in Entity Framework Core, It is not available in Entity Framework 6

Eager Loading from Multiple Tables

You can load multiple related tables by using the following syntax. In the code below we have two tables ProductModel and ProductSubcategory

using (AdventureWorks db = new AdventureWorks())
{
    //Disable Lazy Loading 
    db.Configuration.LazyLoadingEnabled = false;
    db.Database.Log = Console.Write;

    var product = (from p in db.Products
                    .Include(p => p.ProductModel)
                    .Include(p => p.ProductSubcategory)
                    where p.ProductID == 931
                    select p).ToList();

    foreach (var p in product)
    {
        Console.WriteLine("{0} {1} {2}", p.ProductID, p.Name, p.ProductModel.Name, p.ProductSubcategory.Name)  ;
    }
}

The corresponding SQL Query.

SELECT
    [Extent1].[ProductID] AS [ProductID],
    [Extent1].[Name] AS [Name],
    [Extent1].[ProductNumber] AS [ProductNumber],
    [Extent1].[MakeFlag] AS [MakeFlag],
    [Extent1].[FinishedGoodsFlag] AS [FinishedGoodsFlag],
    [Extent1].[Color] AS [Color],
    [Extent1].[SafetyStockLevel] AS [SafetyStockLevel],
    [Extent1].[ReorderPoint] AS [ReorderPoint],
    [Extent1].[StandardCost] AS [StandardCost],
    [Extent1].[ListPrice] AS [ListPrice],
    [Extent1].[Size] AS [Size],
    [Extent1].[SizeUnitMeasureCode] AS [SizeUnitMeasureCode],
    [Extent1].[WeightUnitMeasureCode] AS [WeightUnitMeasureCode],
    [Extent1].[Weight] AS [Weight],
    [Extent1].[DaysToManufacture] AS [DaysToManufacture],
    [Extent1].[ProductLine] AS [ProductLine],
    [Extent1].[Class] AS [Class],
    [Extent1].[Style] AS [Style],
    [Extent1].[ProductSubcategoryID] AS [ProductSubcategoryID],
    [Extent1].[ProductModelID] AS [ProductModelID],
    [Extent1].[SellStartDate] AS [SellStartDate],
    [Extent1].[SellEndDate] AS [SellEndDate],
    [Extent1].[DiscontinuedDate] AS [DiscontinuedDate],
    [Extent1].[rowguid] AS [rowguid],
    [Extent1].[ModifiedDate] AS [ModifiedDate],
    [Extent2].[ProductModelID] AS [ProductModelID1],
    [Extent2].[Name] AS [Name1],
    [Extent2].[CatalogDescription] AS [CatalogDescription],
    [Extent2].[Instructions] AS [Instructions],
    [Extent2].[rowguid] AS [rowguid1],
    [Extent2].[ModifiedDate] AS [ModifiedDate1],
    [Extent3].[ProductSubcategoryID] AS [ProductSubcategoryID1],
    [Extent3].[ProductCategoryID] AS [ProductCategoryID],
    [Extent3].[Name] AS [Name2],
    [Extent3].[rowguid] AS [rowguid2],
    [Extent3].[ModifiedDate] AS [ModifiedDate2]
    FROM   [Production].[Product] AS [Extent1]
    LEFT OUTER JOIN [Production].[ProductModel] AS [Extent2] ON [Extent1].[ProductModelID] = [Extent2].[ProductModelID]
    LEFT OUTER JOIN [Production].[ProductSubcategory] AS [Extent3] ON [Extent1].[ProductSubcategoryID] = [Extent3].[ProductSubcategoryID]
    WHERE 931 = [Extent1].[ProductID]

Eager Loading Multiple Levels

You can also load entities to multiple levels using the following syntax. The include statement includes bothEmployee and Person table (note that both are reference types). The Query will use join to bring data from both the tables as shown below.

using (AdventureWorks db = new AdventureWorks())
{
    //Disable Lazy Loading 
    db.Configuration.LazyLoadingEnabled = false;
    db.Database.Log = Console.Write;

    var person = (from p in db.SalesPersons
                    .Include(p => p.Employee.Person)
                    select p).Take(5).ToList();

    foreach (var p in person)
    {
        Console.WriteLine("{0}", p.Employee.Person.FirstName);
    }
}
SELECT TOP (5)
    [Extent1].[BusinessEntityID] AS [BusinessEntityID],
    [Extent1].[TerritoryID] AS [TerritoryID],
    [Extent1].[SalesQuota] AS [SalesQuota],
    [Extent1].[Bonus] AS [Bonus],
    [Extent1].[CommissionPct] AS [CommissionPct],
    [Extent1].[SalesYTD] AS [SalesYTD],
    [Extent1].[SalesLastYear] AS [SalesLastYear],
    [Extent1].[rowguid] AS [rowguid],
    [Extent1].[ModifiedDate] AS [ModifiedDate],
    [Extent2].[BusinessEntityID] AS [BusinessEntityID1],
    [Extent2].[NationalIDNumber] AS [NationalIDNumber],
    [Extent2].[LoginID] AS [LoginID],
    [Extent2].[OrganizationLevel] AS [OrganizationLevel],
    [Extent2].[JobTitle] AS [JobTitle],
    [Extent2].[BirthDate] AS [BirthDate],
    [Extent2].[MaritalStatus] AS [MaritalStatus],
    [Extent2].[Gender] AS [Gender],
    [Extent2].[HireDate] AS [HireDate],
    [Extent2].[SalariedFlag] AS [SalariedFlag],
    [Extent2].[VacationHours] AS [VacationHours],
    [Extent2].[SickLeaveHours] AS [SickLeaveHours],
    [Extent2].[CurrentFlag] AS [CurrentFlag],
    [Extent2].[rowguid] AS [rowguid1],
    [Extent2].[ModifiedDate] AS [ModifiedDate1],
    [Join2].[BusinessEntityID1] AS [BusinessEntityID2],
    [Join2].[PersonType] AS [PersonType],
    [Join2].[NameStyle] AS [NameStyle],
    [Join2].[Title] AS [Title],
    [Join2].[FirstName] AS [FirstName],
    [Join2].[MiddleName] AS [MiddleName],
    [Join2].[LastName] AS [LastName],
    [Join2].[Suffix] AS [Suffix],
    [Join2].[EmailPromotion] AS [EmailPromotion],
    [Join2].[AdditionalContactInfo] AS [AdditionalContactInfo],
    [Join2].[Demographics] AS [Demographics],
    [Join2].[rowguid1] AS [rowguid2],
    [Join2].[ModifiedDate1] AS [ModifiedDate2]
    FROM   [Sales].[SalesPerson] AS [Extent1]
    INNER JOIN [HumanResources].[Employee] AS [Extent2] ON [Extent1].[BusinessEntityID] = [Extent2].[BusinessEntityID]
    LEFT OUTER JOIN  (SELECT [Extent3].[BusinessEntityID] AS [BusinessEntityID1], [Extent3].[PersonType] AS [PersonType], [Extent3].[NameStyle] AS [NameStyle], [Extent3].[Title] AS [Title], [Extent3].[FirstName] AS [FirstName], [Extent3].[MiddleName] AS [MiddleName], [Extent3].[LastName] AS [LastName], [Extent3].[Suffix] AS [Suffix], [Extent3].[EmailPromotion] AS [EmailPromotion], [Extent3].[AdditionalContactInfo] AS [AdditionalContactInfo], [Extent3].[Demographics] AS [Demographics], [Extent3].[rowguid] AS [rowguid1], [Extent3].[ModifiedDate] AS [ModifiedDate1], [Extent4].[BusinessEntityID] AS [BusinessEntityID2]
        FROM  [Person].[Person] AS [Extent3]
        LEFT OUTER JOIN [HumanResources].[Employee] AS [Extent4] ON [Extent3].[BusinessEntityID] = [Extent4].[BusinessEntityID] ) AS [Join2] ON [Extent1].[BusinessEntityID] = [Join2].[BusinessEntityID2]

The following example Queries ProductVendors which is a collection along with the Product. To get the Vendor we can make use of the select statement. Take a look at the complex query the EF generates for this task.

using (AdventureWorks db = new AdventureWorks())
{
    //Disable Lazy Loading 
    db.Configuration.LazyLoadingEnabled = false;
    db.Database.Log = Console.Write;

    var product = (from p in db.Products
                    .Include(p => p.ProductModel)
                    .Include(p => p.ProductVendors.Select(i=> i.Vendor))
                    where p.ProductID == 931
                    select p).ToList();

    foreach (var p in product)
    {
        Console.WriteLine("{0} {1} {2}", p.ProductID, p.Name, p.ProductModel.Name);

        foreach (var v in p.ProductVendors)
        {
            Console.WriteLine("{0}", v.Vendor.Name);  //The Vendor name is already loaded.
        }
    }
}

SQL Query

SELECT
    [Project1].[ProductID] AS [ProductID],
    [Project1].[Name] AS [Name],
    [Project1].[ProductNumber] AS [ProductNumber],
    [Project1].[MakeFlag] AS [MakeFlag],
    [Project1].[FinishedGoodsFlag] AS [FinishedGoodsFlag],
    [Project1].[Color] AS [Color],
    [Project1].[SafetyStockLevel] AS [SafetyStockLevel],
    [Project1].[ReorderPoint] AS [ReorderPoint],
    [Project1].[StandardCost] AS [StandardCost],
    [Project1].[ListPrice] AS [ListPrice],
    [Project1].[Size] AS [Size],
    [Project1].[SizeUnitMeasureCode] AS [SizeUnitMeasureCode],
    [Project1].[WeightUnitMeasureCode] AS [WeightUnitMeasureCode],
    [Project1].[Weight] AS [Weight],
    [Project1].[DaysToManufacture] AS [DaysToManufacture],
    [Project1].[ProductLine] AS [ProductLine],
    [Project1].[Class] AS [Class],
    [Project1].[Style] AS [Style],
    [Project1].[ProductSubcategoryID] AS [ProductSubcategoryID],
    [Project1].[ProductModelID] AS [ProductModelID],
    [Project1].[SellStartDate] AS [SellStartDate],
    [Project1].[SellEndDate] AS [SellEndDate],
    [Project1].[DiscontinuedDate] AS [DiscontinuedDate],
    [Project1].[rowguid] AS [rowguid],
    [Project1].[ModifiedDate] AS [ModifiedDate],
    [Project1].[ProductModelID1] AS [ProductModelID1],
    [Project1].[Name1] AS [Name1],
    [Project1].[CatalogDescription] AS [CatalogDescription],
    [Project1].[Instructions] AS [Instructions],
    [Project1].[rowguid1] AS [rowguid1],
    [Project1].[ModifiedDate1] AS [ModifiedDate1],
    [Project1].[C1] AS [C1],
    [Project1].[ProductID1] AS [ProductID1],
    [Project1].[BusinessEntityID] AS [BusinessEntityID],
    [Project1].[AverageLeadTime] AS [AverageLeadTime],
    [Project1].[StandardPrice] AS [StandardPrice],
    [Project1].[LastReceiptCost] AS [LastReceiptCost],
    [Project1].[LastReceiptDate] AS [LastReceiptDate],
    [Project1].[MinOrderQty] AS [MinOrderQty],
    [Project1].[MaxOrderQty] AS [MaxOrderQty],
    [Project1].[OnOrderQty] AS [OnOrderQty],
    [Project1].[UnitMeasureCode] AS [UnitMeasureCode],
    [Project1].[ModifiedDate2] AS [ModifiedDate2],
    [Project1].[BusinessEntityID1] AS [BusinessEntityID1],
    [Project1].[AccountNumber] AS [AccountNumber],
    [Project1].[Name2] AS [Name2],
    [Project1].[CreditRating] AS [CreditRating],
    [Project1].[PreferredVendorStatus] AS [PreferredVendorStatus],
    [Project1].[ActiveFlag] AS [ActiveFlag],
    [Project1].[PurchasingWebServiceURL] AS [PurchasingWebServiceURL],
    [Project1].[ModifiedDate3] AS [ModifiedDate3]
    FROM ( SELECT
        [Extent1].[ProductID] AS [ProductID],
        [Extent1].[Name] AS [Name],
        [Extent1].[ProductNumber] AS [ProductNumber],
        [Extent1].[MakeFlag] AS [MakeFlag],
        [Extent1].[FinishedGoodsFlag] AS [FinishedGoodsFlag],
        [Extent1].[Color] AS [Color],
        [Extent1].[SafetyStockLevel] AS [SafetyStockLevel],
        [Extent1].[ReorderPoint] AS [ReorderPoint],
        [Extent1].[StandardCost] AS [StandardCost],
        [Extent1].[ListPrice] AS [ListPrice],
        [Extent1].[Size] AS [Size],
        [Extent1].[SizeUnitMeasureCode] AS [SizeUnitMeasureCode],
        [Extent1].[WeightUnitMeasureCode] AS [WeightUnitMeasureCode],
        [Extent1].[Weight] AS [Weight],
        [Extent1].[DaysToManufacture] AS [DaysToManufacture],
        [Extent1].[ProductLine] AS [ProductLine],
        [Extent1].[Class] AS [Class],
        [Extent1].[Style] AS [Style],
        [Extent1].[ProductSubcategoryID] AS [ProductSubcategoryID],
        [Extent1].[ProductModelID] AS [ProductModelID],
        [Extent1].[SellStartDate] AS [SellStartDate],
        [Extent1].[SellEndDate] AS [SellEndDate],
        [Extent1].[DiscontinuedDate] AS [DiscontinuedDate],
        [Extent1].[rowguid] AS [rowguid],
        [Extent1].[ModifiedDate] AS [ModifiedDate],
        [Extent2].[ProductModelID] AS [ProductModelID1],
        [Extent2].[Name] AS [Name1],
        [Extent2].[CatalogDescription] AS [CatalogDescription],
        [Extent2].[Instructions] AS [Instructions],
        [Extent2].[rowguid] AS [rowguid1],
        [Extent2].[ModifiedDate] AS [ModifiedDate1],
        [Join2].[ProductID] AS [ProductID1],
        [Join2].[BusinessEntityID1] AS [BusinessEntityID],
        [Join2].[AverageLeadTime] AS [AverageLeadTime],
        [Join2].[StandardPrice] AS [StandardPrice],
        [Join2].[LastReceiptCost] AS [LastReceiptCost],
        [Join2].[LastReceiptDate] AS [LastReceiptDate],
        [Join2].[MinOrderQty] AS [MinOrderQty],
        [Join2].[MaxOrderQty] AS [MaxOrderQty],
        [Join2].[OnOrderQty] AS [OnOrderQty],
        [Join2].[UnitMeasureCode] AS [UnitMeasureCode],
        [Join2].[ModifiedDate1] AS [ModifiedDate2],
        [Join2].[BusinessEntityID2] AS [BusinessEntityID1],
        [Join2].[AccountNumber] AS [AccountNumber],
        [Join2].[Name] AS [Name2],
        [Join2].[CreditRating] AS [CreditRating],
        [Join2].[PreferredVendorStatus] AS [PreferredVendorStatus],
        [Join2].[ActiveFlag] AS [ActiveFlag],
        [Join2].[PurchasingWebServiceURL] AS [PurchasingWebServiceURL],
        [Join2].[ModifiedDate2] AS [ModifiedDate3],
        CASE WHEN ([Join2].[ProductID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1]
        FROM   [Production].[Product] AS [Extent1]
        LEFT OUTER JOIN [Production].[ProductModel] AS [Extent2] ON [Extent1].[ProductModelID] = [Extent2].[ProductModelID]
        LEFT OUTER JOIN  (SELECT [Extent3].[ProductID] AS [ProductID], [Extent3].[BusinessEntityID] AS [BusinessEntityID1], [Extent3].[AverageLeadTime] AS [AverageLeadTime], [Extent3].[StandardPrice] AS [StandardPrice], [Extent3].[LastReceiptCost] AS [LastReceiptCost], [Extent3].[LastReceiptDate] AS [LastReceiptDate], [Extent3].[MinOrderQty] AS [MinOrderQty], [Extent3].[MaxOrderQty] AS [MaxOrderQty], [Extent3].[OnOrderQty] AS [OnOrderQty], [Extent3].[UnitMeasureCode] AS [UnitMeasureCode], [Extent3].[ModifiedDate] AS [ModifiedDate1], [Extent4].[BusinessEntityID] AS [BusinessEntityID2], [Extent4].[AccountNumber] AS [AccountNumber], [Extent4].[Name] AS [Name], [Extent4].[CreditRating] AS [CreditRating], [Extent4].[PreferredVendorStatus] AS [PreferredVendorStatus], [Extent4].[ActiveFlag] AS [ActiveFlag], [Extent4].[PurchasingWebServiceURL] AS [PurchasingWebServiceURL], [Extent4].[ModifiedDate] AS [ModifiedDate2]
            FROM  [Purchasing].[ProductVendor] AS [Extent3]
            INNER JOIN [Purchasing].[Vendor] AS [Extent4] ON [Extent3].[BusinessEntityID] = [Extent4].[BusinessEntityID] ) AS [Join2] ON [Extent1].[ProductID] = [Join2].[ProductID]
        WHERE 931 = [Extent1].[ProductID]
    )  AS [Project1]
    ORDER BY [Project1].[ProductID] ASC, [Project1].[ProductModelID1] ASC, [Project1].[C1] ASC

Leave a Comment

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Scroll to Top