The cost of eager loading in Entity Framework

Everything in life is about choices and usually we have to evaluate our options before we make those choices. So it goes with eager loading vs. deferred loading in Entity Framework.

We have the option of eager loading with the Include method and can use it very creatively.

For an entity that has a lot of relationships you could do something like:

context.Customers.Include("Orders.LineItems.Product")
                  .Include("Addresses")

and retrieve a very rich Customer graph with only a single hit to the database.

There's another way to eager load as well -- using projection:

 var custs = from cust in context.Customers
             from order in cust.Orders
             from lineitem in order.LineItems
             select new { cust, cust.Orders,
                          order.LineItems, lineitem.Product};
foreach (var item in custs){Customer cust = item.cust;
}

When this query is executed, all of the entities will be joined in graphs as they are retrieved. So that cust in the enumeration will already have all of it's Orders with LineItems and Products attached.

You could also defer the loading of the related objects, thereby querying for just the customers and then loading more data later.

You might choose this route if you need to make some decisions about whether or n ot you really want that data. This example is a little bit exaggerated.

ObjectQuery<Customer> custs=context.Customers;
foreach (Customer c in custs)
{
  if (IWantTheOrders())
  {
   c.Orders.Load();  //hit the database again
    foreach (Order o in c.Orders)
    {
      if (IWantTheLIneItems())
        o.LineItems.Load(); //hit the database again }}

Now comes the question of performance.

Let's first take a look at the store queries.

If you are sure that you want all of that data up front, then the Include  is certainly the easiest thing to code.

The store query isn't pretty because all of the joins and outer joins need to be built. I have made the painful decision to paste the whole thing here. Remember that the query will actually flatten all of the data from the joins into individual rows, one for each unique customer.Order.LineItem.Product combo.

The projection query is different, but not terribly different and looks just as nasty. I'll leave it to a dba to discern if it's any better or worse than the above.

Store Query From Includes
SELECT
[UnionAll1].[CustomerID] AS [C1],
[UnionAll1].[NameStyle] AS [C2],
[UnionAll1].[Title] AS [C3],
[UnionAll1].[FirstName] AS [C4],
[UnionAll1].[MiddleName] AS [C5],
[UnionAll1].[LastName] AS [C6],
[UnionAll1].[Suffix] AS [C7],
[UnionAll1].[CompanyName] AS [C8],
[UnionAll1].[SalesPerson] AS [C9],
[UnionAll1].[EmailAddress] AS [C10],
[UnionAll1].[Phone] AS [C11],
[UnionAll1].[PasswordHash] AS [C12],
[UnionAll1].[PasswordSalt] AS [C13],
[UnionAll1].[rowguid] AS [C14],
[UnionAll1].[ModifiedDate] AS [C15],
[UnionAll1].[C2] AS [C16],
[UnionAll1].[C1] AS [C17],
[UnionAll1].[SalesOrderID] AS [C18],
[UnionAll1].[RevisionNumber] AS [C19],
[UnionAll1].[OrderDate] AS [C20],
[UnionAll1].[DueDate] AS [C21],
[UnionAll1].[ShipDate] AS [C22],
[UnionAll1].[Status] AS [C23],
[UnionAll1].[OnlineOrderFlag] AS [C24],
[UnionAll1].[SalesOrderNumber] AS [C25],
[UnionAll1].[PurchaseOrderNumber] AS [C26],
[UnionAll1].[AccountNumber] AS [C27],
[UnionAll1].[ShipMethod] AS [C28],
[UnionAll1].[CreditCardApprovalCode] AS [C29],
[UnionAll1].[SubTotal] AS [C30],
[UnionAll1].[TaxAmt] AS [C31],
[UnionAll1].[Freight] AS [C32],
[UnionAll1].[TotalDue] AS [C33],
[UnionAll1].[Comment] AS [C34],
[UnionAll1].[rowguid1] AS [C35],
[UnionAll1].[ModifiedDate1] AS [C36],
[UnionAll1].[C3] AS [C37],
[UnionAll1].[BillToAddressID] AS [C38],
[UnionAll1].[ShipToAddressID] AS [C39],
[UnionAll1].[CustomerID1] AS [C40],
[UnionAll1].[C4] AS [C41],
[UnionAll1].[C5] AS [C42],
[UnionAll1].[SalesOrderID1] AS [C43],
[UnionAll1].[SalesOrderDetailID] AS [C44],
[UnionAll1].[OrderQty] AS [C45],
[UnionAll1].[UnitPrice] AS [C46],
[UnionAll1].[UnitPriceDiscount] AS [C47],
[UnionAll1].[LineTotal] AS [C48],
[UnionAll1].[rowguid2] AS [C49],
[UnionAll1].[ModifiedDate2] AS [C50],
[UnionAll1].[C6] AS [C51],
[UnionAll1].[ProductID] AS [C52],
[UnionAll1].[Name] AS [C53],
[UnionAll1].[ProductNumber] AS [C54],
[UnionAll1].[Color] AS [C55],
[UnionAll1].[StandardCost] AS [C56],
[UnionAll1].[ListPrice] AS [C57],
[UnionAll1].[Size] AS [C58],
[UnionAll1].[Weight] AS [C59],
[UnionAll1].[SellStartDate] AS [C60],
[UnionAll1].[SellEndDate] AS [C61],
[UnionAll1].[DiscontinuedDate] AS [C62],
[UnionAll1].[ThumbNailPhoto] AS [C63],
[UnionAll1].[ThumbnailPhotoFileName] AS [C64],
[UnionAll1].[rowguid3] AS [C65],
[UnionAll1].[ModifiedDate3] AS [C66],
[UnionAll1].[ProductCategoryID] AS [C67],
[UnionAll1].[ProductModelID] AS [C68],
[UnionAll1].[C7] AS [C69],
[UnionAll1].[C12] AS [C70],
[UnionAll1].[C11] AS [C71],
[UnionAll1].[C8] AS [C72],
[UnionAll1].[C9] AS [C73],
[UnionAll1].[C10] AS [C74]
FROM  (SELECT
    [Project2].[C2] AS [C1],
    [Extent1].[CustomerID] AS [CustomerID],
    [Extent1].[NameStyle] AS [NameStyle],
    [Extent1].[Title] AS [Title],
    [Extent1].[FirstName] AS [FirstName],
    [Extent1].[MiddleName] AS [MiddleName],
    [Extent1].[LastName] AS [LastName],
    [Extent1].[Suffix] AS [Suffix],
    [Extent1].[CompanyName] AS [CompanyName],
    [Extent1].[SalesPerson] AS [SalesPerson],
    [Extent1].[EmailAddress] AS [EmailAddress],
    [Extent1].[Phone] AS [Phone],
    [Extent1].[PasswordHash] AS [PasswordHash],
    [Extent1].[PasswordSalt] AS [PasswordSalt],
    [Extent1].[rowguid] AS [rowguid],
    [Extent1].[ModifiedDate] AS [ModifiedDate],
    1 AS [C2],
    [Project2].[SalesOrderID] AS [SalesOrderID],
    [Project2].[RevisionNumber] AS [RevisionNumber],
    [Project2].[OrderDate] AS [OrderDate],
    [Project2].[DueDate] AS [DueDate],
    [Project2].[ShipDate] AS [ShipDate],
    [Project2].[Status] AS [Status],
    [Project2].[OnlineOrderFlag] AS [OnlineOrderFlag],
    [Project2].[SalesOrderNumber] AS [SalesOrderNumber],
    [Project2].[PurchaseOrderNumber] AS [PurchaseOrderNumber],
    [Project2].[AccountNumber] AS [AccountNumber],
    [Project2].[ShipMethod] AS [ShipMethod],
    [Project2].[CreditCardApprovalCode] AS [CreditCardApprovalCode],
    [Project2].[SubTotal] AS [SubTotal],
    [Project2].[TaxAmt] AS [TaxAmt],
    [Project2].[Freight] AS [Freight],
    [Project2].[TotalDue] AS [TotalDue],
    [Project2].[Comment] AS [Comment],
    [Project2].[rowguid] AS [rowguid1],
    [Project2].[ModifiedDate] AS [ModifiedDate1],
    [Project2].[C2] AS [C3],
    [Project2].[BillToAddressID] AS [BillToAddressID],
    [Project2].[ShipToAddressID] AS [ShipToAddressID],
    [Project2].[CustomerID] AS [CustomerID1],
    [Project2].[C1] AS [C4],
    [Project2].[C1] AS [C5],
    [Project2].[SalesOrderID1] AS [SalesOrderID1],
    [Project2].[SalesOrderDetailID] AS [SalesOrderDetailID],
    [Project2].[OrderQty] AS [OrderQty],
    [Project2].[UnitPrice] AS [UnitPrice],
    [Project2].[UnitPriceDiscount] AS [UnitPriceDiscount],
    [Project2].[LineTotal] AS [LineTotal],
    [Project2].[rowguid1] AS [rowguid2],
    [Project2].[ModifiedDate1] AS [ModifiedDate2],
    [Project2].[C1] AS [C6],
    [Project2].[ProductID] AS [ProductID],
    [Project2].[Name] AS [Name],
    [Project2].[ProductNumber] AS [ProductNumber],
    [Project2].[Color] AS [Color],
    [Project2].[StandardCost] AS [StandardCost],
    [Project2].[ListPrice] AS [ListPrice],
    [Project2].[Size] AS [Size],
    [Project2].[Weight] AS [Weight],
    [Project2].[SellStartDate] AS [SellStartDate],
    [Project2].[SellEndDate] AS [SellEndDate],
    [Project2].[DiscontinuedDate] AS [DiscontinuedDate],
    [Project2].[ThumbNailPhoto] AS [ThumbNailPhoto],
    [Project2].[ThumbnailPhotoFileName] AS [ThumbnailPhotoFileName],
    [Project2].[rowguid2] AS [rowguid3],
    [Project2].[ModifiedDate2] AS [ModifiedDate3],
    [Project2].[ProductCategoryID] AS [ProductCategoryID],
    [Project2].[ProductModelID] AS [ProductModelID],
    CAST(NULL AS int) AS [C7],
    CAST(NULL AS varchar(1)) AS [C8],
    CAST(NULL AS uniqueidentifier) AS [C9],
    CAST(NULL AS datetime2) AS [C10],
    CAST(NULL AS int) AS [C11],
    CAST(NULL AS int) AS [C12]
    FROM  [SalesLT].[Customer] AS [Extent1]
    LEFT OUTER JOIN  (SELECT
        [Extent2].[SalesOrderID] AS [SalesOrderID],
        [Extent2].[RevisionNumber] AS [RevisionNumber],
        [Extent2].[OrderDate] AS [OrderDate],
        [Extent2].[DueDate] AS [DueDate],
        [Extent2].[ShipDate] AS [ShipDate],
        [Extent2].[Status] AS [Status],
        [Extent2].[OnlineOrderFlag] AS [OnlineOrderFlag],
        [Extent2].[SalesOrderNumber] AS [SalesOrderNumber],
        [Extent2].[PurchaseOrderNumber] AS [PurchaseOrderNumber],
        [Extent2].[AccountNumber] AS [AccountNumber],
        [Extent2].[CustomerID] AS [CustomerID],
        [Extent2].[ShipToAddressID] AS [ShipToAddressID],
        [Extent2].[BillToAddressID] AS [BillToAddressID],
        [Extent2].[ShipMethod] AS [ShipMethod],
        [Extent2].[CreditCardApprovalCode] AS [CreditCardApprovalCode],
        [Extent2].[SubTotal] AS [SubTotal],
        [Extent2].[TaxAmt] AS [TaxAmt],
        [Extent2].[Freight] AS [Freight],
        [Extent2].[TotalDue] AS [TotalDue],
        [Extent2].[Comment] AS [Comment],
        [Extent2].[rowguid] AS [rowguid],
        [Extent2].[ModifiedDate] AS [ModifiedDate],
        [Project1].[SalesOrderID] AS [SalesOrderID1],
        [Project1].[SalesOrderDetailID] AS [SalesOrderDetailID],
        [Project1].[OrderQty] AS [OrderQty],
        [Project1].[UnitPrice] AS [UnitPrice],
        [Project1].[UnitPriceDiscount] AS [UnitPriceDiscount],
        [Project1].[LineTotal] AS [LineTotal],
        [Project1].[rowguid] AS [rowguid1],
        [Project1].[ModifiedDate] AS [ModifiedDate1],
        [Project1].[ProductID] AS [ProductID],
        [Project1].[Name] AS [Name],
        [Project1].[ProductNumber] AS [ProductNumber],
        [Project1].[Color] AS [Color],
        [Project1].[StandardCost] AS [StandardCost],
        [Project1].[ListPrice] AS [ListPrice],
        [Project1].[Size] AS [Size],
        [Project1].[Weight] AS [Weight],
        [Project1].[ProductCategoryID] AS [ProductCategoryID],
        [Project1].[ProductModelID] AS [ProductModelID],
        [Project1].[SellStartDate] AS [SellStartDate],
        [Project1].[SellEndDate] AS [SellEndDate],
        [Project1].[DiscontinuedDate] AS [DiscontinuedDate],
        [Project1].[ThumbNailPhoto] AS [ThumbNailPhoto],
        [Project1].[ThumbnailPhotoFileName] AS [ThumbnailPhotoFileName],
        [Project1].[rowguid1] AS [rowguid2],
        [Project1].[ModifiedDate1] AS [ModifiedDate2],
        [Project1].[C1] AS [C1],
        1 AS [C2]
        FROM  [SalesLT].[SalesOrderHeader] AS [Extent2]
        LEFT OUTER JOIN  (SELECT
            [Extent3].[SalesOrderID] AS [SalesOrderID],
            [Extent3].[SalesOrderDetailID] AS [SalesOrderDetailID],
            [Extent3].[OrderQty] AS [OrderQty],
            [Extent3].[UnitPrice] AS [UnitPrice],
            [Extent3].[UnitPriceDiscount] AS [UnitPriceDiscount],
            [Extent3].[LineTotal] AS [LineTotal],
            [Extent3].[rowguid] AS [rowguid],
            [Extent3].[ModifiedDate] AS [ModifiedDate],
            [Extent4].[ProductID] AS [ProductID],
            [Extent4].[Name] AS [Name],
            [Extent4].[ProductNumber] AS [ProductNumber],
            [Extent4].[Color] AS [Color],
            [Extent4].[StandardCost] AS [StandardCost],
            [Extent4].[ListPrice] AS [ListPrice],
            [Extent4].[Size] AS [Size],
            [Extent4].[Weight] AS [Weight],
            [Extent4].[ProductCategoryID] AS [ProductCategoryID],
            [Extent4].[ProductModelID] AS [ProductModelID],
            [Extent4].[SellStartDate] AS [SellStartDate],
            [Extent4].[SellEndDate] AS [SellEndDate],
            [Extent4].[DiscontinuedDate] AS [DiscontinuedDate],
            [Extent4].[ThumbNailPhoto] AS [ThumbNailPhoto],
            [Extent4].[ThumbnailPhotoFileName] AS [ThumbnailPhotoFileName],
            [Extent4].[rowguid] AS [rowguid1],
            [Extent4].[ModifiedDate] AS [ModifiedDate1],
            1 AS [C1]
            FROM  [SalesLT].[SalesOrderDetail] AS [Extent3]
            LEFT OUTER JOIN [SalesLT].[Product] AS [Extent4] ON [Extent3].[ProductID] = [Extent4].[ProductID] ) AS [Project1] ON [Extent2].[SalesOrderID] = [Project1].[SalesOrderID] ) AS [Project2] ON [Extent1].[CustomerID] = [Project2].[CustomerID]
UNION ALL
    SELECT
    2 AS [C1],
    [Extent5].[CustomerID] AS [CustomerID],
    [Extent5].[NameStyle] AS [NameStyle],
    [Extent5].[Title] AS [Title],
    [Extent5].[FirstName] AS [FirstName],
    [Extent5].[MiddleName] AS [MiddleName],
    [Extent5].[LastName] AS [LastName],
    [Extent5].[Suffix] AS [Suffix],
    [Extent5].[CompanyName] AS [CompanyName],
    [Extent5].[SalesPerson] AS [SalesPerson],
    [Extent5].[EmailAddress] AS [EmailAddress],
    [Extent5].[Phone] AS [Phone],
    [Extent5].[PasswordHash] AS [PasswordHash],
    [Extent5].[PasswordSalt] AS [PasswordSalt],
    [Extent5].[rowguid] AS [rowguid],
    [Extent5].[ModifiedDate] AS [ModifiedDate],
    1 AS [C2],
    CAST(NULL AS int) AS [C3],
    CAST(NULL AS tinyint) AS [C4],
    CAST(NULL AS datetime2) AS [C5],
    CAST(NULL AS datetime2) AS [C6],
    CAST(NULL AS datetime2) AS [C7],
    CAST(NULL AS tinyint) AS [C8],
    CAST(NULL AS bit) AS [C9],
    CAST(NULL AS varchar(1)) AS [C10],
    CAST(NULL AS varchar(1)) AS [C11],
    CAST(NULL AS varchar(1)) AS [C12],
    CAST(NULL AS varchar(1)) AS [C13],
    CAST(NULL AS varchar(1)) AS [C14],
    CAST(NULL AS decimal(19,4)) AS [C15],
    CAST(NULL AS decimal(19,4)) AS [C16],
    CAST(NULL AS decimal(19,4)) AS [C17],
    CAST(NULL AS decimal(19,4)) AS [C18],
    CAST(NULL AS varchar(1)) AS [C19],
    CAST(NULL AS uniqueidentifier) AS [C20],
    CAST(NULL AS datetime2) AS [C21],
    CAST(NULL AS int) AS [C22],
    CAST(NULL AS int) AS [C23],
    CAST(NULL AS int) AS [C24],
    CAST(NULL AS int) AS [C25],
    CAST(NULL AS int) AS [C26],
    CAST(NULL AS int) AS [C27],
    CAST(NULL AS int) AS [C28],
    CAST(NULL AS int) AS [C29],
    CAST(NULL AS smallint) AS [C30],
    CAST(NULL AS decimal(19,4)) AS [C31],
    CAST(NULL AS decimal(19,4)) AS [C32],
    CAST(NULL AS decimal(38,6)) AS [C33],
    CAST(NULL AS uniqueidentifier) AS [C34],
    CAST(NULL AS datetime2) AS [C35],
    CAST(NULL AS int) AS [C36],
    CAST(NULL AS int) AS [C37],
    CAST(NULL AS varchar(1)) AS [C38],
    CAST(NULL AS varchar(1)) AS [C39],
    CAST(NULL AS varchar(1)) AS [C40],
    CAST(NULL AS decimal(19,4)) AS [C41],
    CAST(NULL AS decimal(19,4)) AS [C42],
    CAST(NULL AS varchar(1)) AS [C43],
    CAST(NULL AS decimal(8,2)) AS [C44],
    CAST(NULL AS datetime2) AS [C45],
    CAST(NULL AS datetime2) AS [C46],
    CAST(NULL AS datetime2) AS [C47],
    CAST(NULL AS varbinary(1)) AS [C48],
    CAST(NULL AS varchar(1)) AS [C49],
    CAST(NULL AS uniqueidentifier) AS [C50],
    CAST(NULL AS datetime2) AS [C51],
    CAST(NULL AS int) AS [C52],
    CAST(NULL AS int) AS [C53],
    1 AS [C54],
    [Extent6].[AddressType] AS [AddressType],
    [Extent6].[rowguid] AS [rowguid1],
    [Extent6].[ModifiedDate] AS [ModifiedDate1],
    [Extent6].[AddressID] AS [AddressID],
    [Extent6].[CustomerID] AS [CustomerID1]
    FROM  [SalesLT].[Customer] AS [Extent5]
    INNER JOIN [SalesLT].[CustomerAddress] AS [Extent6] ON [Extent5].[CustomerID] = [Extent6].[CustomerID]) AS [UnionAll1]
ORDER BY [UnionAll1].[CustomerID] ASC, [UnionAll1].[C1] ASC, [UnionAll1].[SalesOrderID] ASC, [UnionAll1].[C4] ASC
Store Query from Projection
SELECT
[UnionAll1].[CustomerID] AS [C1],
[UnionAll1].[NameStyle] AS [C2],
[UnionAll1].[Title] AS [C3],
[UnionAll1].[FirstName] AS [C4],
[UnionAll1].[MiddleName] AS [C5],
[UnionAll1].[LastName] AS [C6],
[UnionAll1].[Suffix] AS [C7],
[UnionAll1].[CompanyName] AS [C8],
[UnionAll1].[SalesPerson] AS [C9],
[UnionAll1].[EmailAddress] AS [C10],
[UnionAll1].[Phone] AS [C11],
[UnionAll1].[PasswordHash] AS [C12],
[UnionAll1].[PasswordSalt] AS [C13],
[UnionAll1].[rowguid] AS [C14],
[UnionAll1].[ModifiedDate] AS [C15],
[UnionAll1].[SalesOrderID] AS [C16],
[UnionAll1].[SalesOrderDetailID] AS [C17],
[UnionAll1].[ProductID] AS [C18],
[UnionAll1].[ProductID1] AS [C19],
[UnionAll1].[C2] AS [C20],
[UnionAll1].[C3] AS [C21],
[UnionAll1].[C4] AS [C22],
[UnionAll1].[C5] AS [C23],
[UnionAll1].[SalesOrderID1] AS [C24],
[UnionAll1].[RevisionNumber] AS [C25],
[UnionAll1].[OrderDate] AS [C26],
[UnionAll1].[DueDate] AS [C27],
[UnionAll1].[ShipDate] AS [C28],
[UnionAll1].[Status] AS [C29],
[UnionAll1].[OnlineOrderFlag] AS [C30],
[UnionAll1].[SalesOrderNumber] AS [C31],
[UnionAll1].[PurchaseOrderNumber] AS [C32],
[UnionAll1].[AccountNumber] AS [C33],
[UnionAll1].[ShipMethod] AS [C34],
[UnionAll1].[CreditCardApprovalCode] AS [C35],
[UnionAll1].[SubTotal] AS [C36],
[UnionAll1].[TaxAmt] AS [C37],
[UnionAll1].[Freight] AS [C38],
[UnionAll1].[TotalDue] AS [C39],
[UnionAll1].[Comment] AS [C40],
[UnionAll1].[rowguid1] AS [C41],
[UnionAll1].[ModifiedDate1] AS [C42],
[UnionAll1].[BillToAddressID] AS [C43],
[UnionAll1].[ShipToAddressID] AS [C44],
[UnionAll1].[CustomerID1] AS [C45],
[UnionAll1].[C6] AS [C46],
[UnionAll1].[ProductID2] AS [C47],
[UnionAll1].[Name] AS [C48],
[UnionAll1].[ProductNumber] AS [C49],
[UnionAll1].[Color] AS [C50],
[UnionAll1].[StandardCost] AS [C51],
[UnionAll1].[ListPrice] AS [C52],
[UnionAll1].[Size] AS [C53],
[UnionAll1].[Weight] AS [C54],
[UnionAll1].[SellStartDate] AS [C55],
[UnionAll1].[SellEndDate] AS [C56],
[UnionAll1].[DiscontinuedDate] AS [C57],
[UnionAll1].[ThumbNailPhoto] AS [C58],
[UnionAll1].[ThumbnailPhotoFileName] AS [C59],
[UnionAll1].[rowguid2] AS [C60],
[UnionAll1].[ModifiedDate2] AS [C61],
[UnionAll1].[ProductCategoryID] AS [C62],
[UnionAll1].[ProductModelID] AS [C63],
[UnionAll1].[C1] AS [C64],
[UnionAll1].[C7] AS [C65],
[UnionAll1].[SalesOrderID2] AS [C66],
[UnionAll1].[RevisionNumber1] AS [C67],
[UnionAll1].[OrderDate1] AS [C68],
[UnionAll1].[DueDate1] AS [C69],
[UnionAll1].[ShipDate1] AS [C70],
[UnionAll1].[Status1] AS [C71],
[UnionAll1].[OnlineOrderFlag1] AS [C72],
[UnionAll1].[SalesOrderNumber1] AS [C73],
[UnionAll1].[PurchaseOrderNumber1] AS [C74],
[UnionAll1].[AccountNumber1] AS [C75],
[UnionAll1].[ShipMethod1] AS [C76],
[UnionAll1].[CreditCardApprovalCode1] AS [C77],
[UnionAll1].[SubTotal1] AS [C78],
[UnionAll1].[TaxAmt1] AS [C79],
[UnionAll1].[Freight1] AS [C80],
[UnionAll1].[TotalDue1] AS [C81],
[UnionAll1].[Comment1] AS [C82],
[UnionAll1].[rowguid3] AS [C83],
[UnionAll1].[ModifiedDate3] AS [C84],
[UnionAll1].[BillToAddressID1] AS [C85],
[UnionAll1].[ShipToAddressID1] AS [C86],
[UnionAll1].[CustomerID2] AS [C87],
[UnionAll1].[C8] AS [C88],
[UnionAll1].[C17] AS [C89],
[UnionAll1].[C9] AS [C90],
[UnionAll1].[C10] AS [C91],
[UnionAll1].[C11] AS [C92],
[UnionAll1].[C12] AS [C93],
[UnionAll1].[C13] AS [C94],
[UnionAll1].[C14] AS [C95],
[UnionAll1].[C15] AS [C96],
[UnionAll1].[C16] AS [C97]
FROM  (SELECT
    CASE WHEN ([Extent5].[SalesOrderID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1],
    [Extent1].[CustomerID] AS [CustomerID],
    [Extent1].[NameStyle] AS [NameStyle],
    [Extent1].[Title] AS [Title],
    [Extent1].[FirstName] AS [FirstName],
    [Extent1].[MiddleName] AS [MiddleName],
    [Extent1].[LastName] AS [LastName],
    [Extent1].[Suffix] AS [Suffix],
    [Extent1].[CompanyName] AS [CompanyName],
    [Extent1].[SalesPerson] AS [SalesPerson],
    [Extent1].[EmailAddress] AS [EmailAddress],
    [Extent1].[Phone] AS [Phone],
    [Extent1].[PasswordHash] AS [PasswordHash],
    [Extent1].[PasswordSalt] AS [PasswordSalt],
    [Extent1].[rowguid] AS [rowguid],
    [Extent1].[ModifiedDate] AS [ModifiedDate],
    [Extent3].[SalesOrderID] AS [SalesOrderID],
    [Extent3].[SalesOrderDetailID] AS [SalesOrderDetailID],
    [Extent4].[ProductID] AS [ProductID],
    [Extent4].[ProductID] AS [ProductID1],
    1 AS [C2],
    1 AS [C3],
    1 AS [C4],
    1 AS [C5],
    [Extent2].[SalesOrderID] AS [SalesOrderID1],
    [Extent2].[RevisionNumber] AS [RevisionNumber],
    [Extent2].[OrderDate] AS [OrderDate],
    [Extent2].[DueDate] AS [DueDate],
    [Extent2].[ShipDate] AS [ShipDate],
    [Extent2].[Status] AS [Status],
    [Extent2].[OnlineOrderFlag] AS [OnlineOrderFlag],
    [Extent2].[SalesOrderNumber] AS [SalesOrderNumber],
    [Extent2].[PurchaseOrderNumber] AS [PurchaseOrderNumber],
    [Extent2].[AccountNumber] AS [AccountNumber],
    [Extent2].[ShipMethod] AS [ShipMethod],
    [Extent2].[CreditCardApprovalCode] AS [CreditCardApprovalCode],
    [Extent2].[SubTotal] AS [SubTotal],
    [Extent2].[TaxAmt] AS [TaxAmt],
    [Extent2].[Freight] AS [Freight],
    [Extent2].[TotalDue] AS [TotalDue],
    [Extent2].[Comment] AS [Comment],
    [Extent2].[rowguid] AS [rowguid1],
    [Extent2].[ModifiedDate] AS [ModifiedDate1],
    [Extent2].[BillToAddressID] AS [BillToAddressID],
    [Extent2].[ShipToAddressID] AS [ShipToAddressID],
    [Extent2].[CustomerID] AS [CustomerID1],
    1 AS [C6],
    [Extent4].[ProductID] AS [ProductID2],
    [Extent4].[Name] AS [Name],
    [Extent4].[ProductNumber] AS [ProductNumber],
    [Extent4].[Color] AS [Color],
    [Extent4].[StandardCost] AS [StandardCost],
    [Extent4].[ListPrice] AS [ListPrice],
    [Extent4].[Size] AS [Size],
    [Extent4].[Weight] AS [Weight],
    [Extent4].[SellStartDate] AS [SellStartDate],
    [Extent4].[SellEndDate] AS [SellEndDate],
    [Extent4].[DiscontinuedDate] AS [DiscontinuedDate],
    [Extent4].[ThumbNailPhoto] AS [ThumbNailPhoto],
    [Extent4].[ThumbnailPhotoFileName] AS [ThumbnailPhotoFileName],
    [Extent4].[rowguid] AS [rowguid2],
    [Extent4].[ModifiedDate] AS [ModifiedDate2],
    [Extent4].[ProductCategoryID] AS [ProductCategoryID],
    [Extent4].[ProductModelID] AS [ProductModelID],
    CASE WHEN ([Extent5].[SalesOrderID] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C7],
    [Extent5].[SalesOrderID] AS [SalesOrderID2],
    [Extent5].[RevisionNumber] AS [RevisionNumber1],
    [Extent5].[OrderDate] AS [OrderDate1],
    [Extent5].[DueDate] AS [DueDate1],
    [Extent5].[ShipDate] AS [ShipDate1],
    [Extent5].[Status] AS [Status1],
    [Extent5].[OnlineOrderFlag] AS [OnlineOrderFlag1],
    [Extent5].[SalesOrderNumber] AS [SalesOrderNumber1],
    [Extent5].[PurchaseOrderNumber] AS [PurchaseOrderNumber1],
    [Extent5].[AccountNumber] AS [AccountNumber1],
    [Extent5].[ShipMethod] AS [ShipMethod1],
    [Extent5].[CreditCardApprovalCode] AS [CreditCardApprovalCode1],
    [Extent5].[SubTotal] AS [SubTotal1],
    [Extent5].[TaxAmt] AS [TaxAmt1],
    [Extent5].[Freight] AS [Freight1],
    [Extent5].[TotalDue] AS [TotalDue1],
    [Extent5].[Comment] AS [Comment1],
    [Extent5].[rowguid] AS [rowguid3],
    [Extent5].[ModifiedDate] AS [ModifiedDate3],
    [Extent5].[BillToAddressID] AS [BillToAddressID1],
    [Extent5].[ShipToAddressID] AS [ShipToAddressID1],
    [Extent5].[CustomerID] AS [CustomerID2],
    CAST(NULL AS int) AS [C8],
    CAST(NULL AS int) AS [C9],
    CAST(NULL AS smallint) AS [C10],
    CAST(NULL AS decimal(19,4)) AS [C11],
    CAST(NULL AS decimal(19,4)) AS [C12],
    CAST(NULL AS decimal(38,6)) AS [C13],
    CAST(NULL AS uniqueidentifier) AS [C14],
    CAST(NULL AS datetime2) AS [C15],
    CAST(NULL AS int) AS [C16],
    CAST(NULL AS int) AS [C17]
    FROM     [SalesLT].[Customer] AS [Extent1]
    INNER JOIN [SalesLT].[SalesOrderHeader] AS [Extent2] ON [Extent1].[CustomerID] = [Extent2].[CustomerID]
    INNER JOIN [SalesLT].[SalesOrderDetail] AS [Extent3] ON [Extent2].[SalesOrderID] = [Extent3].[SalesOrderID]
    LEFT OUTER JOIN [SalesLT].[Product] AS [Extent4] ON [Extent3].[ProductID] = [Extent4].[ProductID]
    LEFT OUTER JOIN [SalesLT].[SalesOrderHeader] AS [Extent5] ON [Extent1].[CustomerID] = [Extent5].[CustomerID]
UNION ALL
    SELECT
    2 AS [C1],
    [Extent6].[CustomerID] AS [CustomerID],
    [Extent6].[NameStyle] AS [NameStyle],
    [Extent6].[Title] AS [Title],
    [Extent6].[FirstName] AS [FirstName],
    [Extent6].[MiddleName] AS [MiddleName],
    [Extent6].[LastName] AS [LastName],
    [Extent6].[Suffix] AS [Suffix],
    [Extent6].[CompanyName] AS [CompanyName],
    [Extent6].[SalesPerson] AS [SalesPerson],
    [Extent6].[EmailAddress] AS [EmailAddress],
    [Extent6].[Phone] AS [Phone],
    [Extent6].[PasswordHash] AS [PasswordHash],
    [Extent6].[PasswordSalt] AS [PasswordSalt],
    [Extent6].[rowguid] AS [rowguid],
    [Extent6].[ModifiedDate] AS [ModifiedDate],
    [Extent8].[SalesOrderID] AS [SalesOrderID],
    [Extent8].[SalesOrderDetailID] AS [SalesOrderDetailID],
    [Extent9].[ProductID] AS [ProductID],
    [Extent9].[ProductID] AS [ProductID1],
    1 AS [C2],
    1 AS [C3],
    1 AS [C4],
    1 AS [C5],
    [Extent7].[SalesOrderID] AS [SalesOrderID1],
    [Extent7].[RevisionNumber] AS [RevisionNumber],
    [Extent7].[OrderDate] AS [OrderDate],
    [Extent7].[DueDate] AS [DueDate],
    [Extent7].[ShipDate] AS [ShipDate],
    [Extent7].[Status] AS [Status],
    [Extent7].[OnlineOrderFlag] AS [OnlineOrderFlag],
    [Extent7].[SalesOrderNumber] AS [SalesOrderNumber],
    [Extent7].[PurchaseOrderNumber] AS [PurchaseOrderNumber],
    [Extent7].[AccountNumber] AS [AccountNumber],
    [Extent7].[ShipMethod] AS [ShipMethod],
    [Extent7].[CreditCardApprovalCode] AS [CreditCardApprovalCode],
    [Extent7].[SubTotal] AS [SubTotal],
    [Extent7].[TaxAmt] AS [TaxAmt],
    [Extent7].[Freight] AS [Freight],
    [Extent7].[TotalDue] AS [TotalDue],
    [Extent7].[Comment] AS [Comment],
    [Extent7].[rowguid] AS [rowguid1],
    [Extent7].[ModifiedDate] AS [ModifiedDate1],
    [Extent7].[BillToAddressID] AS [BillToAddressID],
    [Extent7].[ShipToAddressID] AS [ShipToAddressID],
    [Extent7].[CustomerID] AS [CustomerID1],
    1 AS [C6],
    [Extent9].[ProductID] AS [ProductID2],
    [Extent9].[Name] AS [Name],
    [Extent9].[ProductNumber] AS [ProductNumber],
    [Extent9].[Color] AS [Color],
    [Extent9].[StandardCost] AS [StandardCost],
    [Extent9].[ListPrice] AS [ListPrice],
    [Extent9].[Size] AS [Size],
    [Extent9].[Weight] AS [Weight],
    [Extent9].[SellStartDate] AS [SellStartDate],
    [Extent9].[SellEndDate] AS [SellEndDate],
    [Extent9].[DiscontinuedDate] AS [DiscontinuedDate],
    [Extent9].[ThumbNailPhoto] AS [ThumbNailPhoto],
    [Extent9].[ThumbnailPhotoFileName] AS [ThumbnailPhotoFileName],
    [Extent9].[rowguid] AS [rowguid2],
    [Extent9].[ModifiedDate] AS [ModifiedDate2],
    [Extent9].[ProductCategoryID] AS [ProductCategoryID],
    [Extent9].[ProductModelID] AS [ProductModelID],
    CAST(NULL AS int) AS [C7],
    CAST(NULL AS int) AS [C8],
    CAST(NULL AS tinyint) AS [C9],
    CAST(NULL AS datetime2) AS [C10],
    CAST(NULL AS datetime2) AS [C11],
    CAST(NULL AS datetime2) AS [C12],
    CAST(NULL AS tinyint) AS [C13],
    CAST(NULL AS bit) AS [C14],
    CAST(NULL AS varchar(1)) AS [C15],
    CAST(NULL AS varchar(1)) AS [C16],
    CAST(NULL AS varchar(1)) AS [C17],
    CAST(NULL AS varchar(1)) AS [C18],
    CAST(NULL AS varchar(1)) AS [C19],
    CAST(NULL AS decimal(19,4)) AS [C20],
    CAST(NULL AS decimal(19,4)) AS [C21],
    CAST(NULL AS decimal(19,4)) AS [C22],
    CAST(NULL AS decimal(19,4)) AS [C23],
    CAST(NULL AS varchar(1)) AS [C24],
    CAST(NULL AS uniqueidentifier) AS [C25],
    CAST(NULL AS datetime2) AS [C26],
    CAST(NULL AS int) AS [C27],
    CAST(NULL AS int) AS [C28],
    CAST(NULL AS int) AS [C29],
    1 AS [C30],
    [Extent10].[SalesOrderDetailID] AS [SalesOrderDetailID1],
    [Extent10].[OrderQty] AS [OrderQty],
    [Extent10].[UnitPrice] AS [UnitPrice],
    [Extent10].[UnitPriceDiscount] AS [UnitPriceDiscount],
    [Extent10].[LineTotal] AS [LineTotal],
    [Extent10].[rowguid] AS [rowguid3],
    [Extent10].[ModifiedDate] AS [ModifiedDate3],
    [Extent10].[ProductID] AS [ProductID3],
    [Extent10].[SalesOrderID] AS [SalesOrderID2]
    FROM     [SalesLT].[Customer] AS [Extent6]
    INNER JOIN [SalesLT].[SalesOrderHeader] AS [Extent7] ON [Extent6].[CustomerID] = [Extent7].[CustomerID]
    INNER JOIN [SalesLT].[SalesOrderDetail] AS [Extent8] ON [Extent7].[SalesOrderID] = [Extent8].[SalesOrderID]
    LEFT OUTER JOIN [SalesLT].[Product] AS [Extent9] ON [Extent8].[ProductID] = [Extent9].[ProductID]
    INNER JOIN [SalesLT].[SalesOrderDetail] AS [Extent10] ON [Extent7].[SalesOrderID] = [Extent10].[SalesOrderID]) AS [UnionAll1]
ORDER BY [UnionAll1].[CustomerID] ASC, [UnionAll1].[SalesOrderID] ASC, [UnionAll1].[SalesOrderDetailID] ASC, [UnionAll1].[ProductID] ASC, [UnionAll1].[ProductID1] ASC, [UnionAll1].[SalesOrderID1] ASC, [UnionAll1].[ProductID2] ASC, [UnionAll1].[C1] ASC

 

What about the time that it takes to process these queries. I don't mean execute them, but compile them into store queries.

I tested this by calling ToTraceString to get the query compiled. The query using Include is an ObjectQuery, so not casting was involved. The projection query is a LINQ to Entities query and therefore I cast it to an ObjectQuery in order to use the ToTrace String method. I tested these independently a number of times, each time in a new application instance. The ObjectQuery.Include query was slightly faster. Looking at the functions that were executed, it looks like the LINQ expression was parsed before ToTraceSTring was called, so that didn't add to the ToTraceString time.

I'll also compare these to 4 tests of calling ToTraceString on *only* getting customers (context.Customers), with no eager loading of related data.

test Include Projection No Eager Loading
1 1090.26 ms 1176.54 510.51
2 909.89 ms 1155.44 518.89
3 899.92ms 1164.55 515.28
4 917.48ms 1294.00 520.88

So the include is a tad faster than the projection and easier to code, too. And eager loading takes about twice as long as only getting the base objects. When you call subsequent loads for the related data, there's much less work to do because only single entities are being returned, not complex, shaped data.

Don't ignore the fact that when you are eager loading, you are also bring back a *lot* more data. And all of that data needs to get materialized.

So we're back to balance.

Don't forget about pre-compiling queries

Eager loading is more expensive, not only because of the additional data, but because of the extra effort in compiling the queries. But don't forget about pre-compiling LINQ queries, pre-compiling views and the benefits of Entity SQL query caching. All of these tools can be leveraged to *greatly* reduce the expense of compiling the queries.

Then you can weigh these costs against returning to the database over and over again to get related data. Having an understanding of what the costs are so that you can make those decisions is essential in applications where performance is critical.

I was inspired to look more deeply into this because of a recommendation  made by Jarek Kowalksi (from the EF team) to a poster on the MSDN EF Forums who was concerned about query performance. You can read that thread here

#1 Matt on 4.22.2009 at 1:28 PM

We could avoid a lot of pointless loading if EF exposed the foreign key columns, instead of hiding them and making us work with the full objects.// something that should be this simplewidget.CategoryID = categoryID;// becomeswidget.Categories = context.CategorySet.Where( c => c.CategoryID == categoryID ).First();// setting a dropdown value should be this easyddlCategoryID.SelectedValue = widget.CategoryID.ToString();// but we have to do something like thisWidget widget = ... .Include("Categories").First();if( widget.Categories != null ){ ddlCategoryID.SelectedValue = widget.Categories.CategoryID.ToString();}Note that "Categories" in the above examples is a plural name for a singular object.It's pretty lame that EF doesn't have settings to allow it to represent plural table names properly, making it fall far short of other professional ORM tools.

#2 Julia Lerman on 4.22.2009 at 1:36 PM

MattTake a look at the EFDesign blog post about FKs coming in the next version of EF. :-)http://blogs.devsource.com/devlife/content/data_access/aspnet_dynamic_data_websites.htmlWRT to the plural names - the EF Wizard definitely falls short here and we have to modify the model ourselves to make the names follow good conventions. We'll see improvement on this for the next version as well. In the meantime, either we have to modify the names in the model or use a tool like Huagati's EF Tools to help.julie

#3 Matt on 4.27.2009 at 3:07 PM

Thanks, the Huagati Tools are gonna be a huge (or huage?) help!

#4 César on 7.28.2009 at 11:34 AM

_compiledSUPPLIERSGraf = CompiledQuery.Compile((Entities ctx, string IdCliente) =>

from c in ctx.SUPPLIERS

.Include("ARTICLE")

.Include("ARTICLE.FAMILY")

where c.CODIGO == IdClient

select c);

Hi Julie.

First of all, great book!.

On the other hand, I wonder if you can help me to solve the problem I’m having with the above query. The query works fine when the supplier entity has associated information (articles) but when it doesn’t I get an error warning me that the entity parameter cannot be empty.

When I change the query as follows (deleting the "ARTICLE.FAMILY" Include):

_compiledSUPPLIERSGraf = CompiledQuery.Compile((Entities ctx, string IdCliente) =>

from c in ctx.SUPPLIERS

.Include("ARTICLE")

where c.CODIGO == IdClient

select c);

it always works (no matter whether the suppliers entity has data), but I cannot access the family information.

Any idea about how to solve this.

Thanks in advance.

#5 Julie on 7.30.2009 at 6:37 PM

Hi Cesar, there's nothing in the query to indicate that you want the family data. Your options are: add teh Include back in, do a projection (select c, c.article.family) or after the query has been executed you can load the FAMILY data for a particular article (anARticle.Family.Load). You can find all of these variations in the book. HTH. Julie