4
votes

I have an ASP.NET MVC application utilizing Entity Framework for the data layer.

In one of my methods I retrieve the seasonal availability data for a product, and afterwards, the best tax rate for the product.

public ProductList FetchProductSearchList(ProductSearchCriteria criteria)
{
    ...
    var avail = ProductAvailabilityTemplate.Get(criteria.ProductID);
    ...
    var tr = TaxRate.BestMatchFor(criteria.ProductID, criteria.TaxCode);
    ...
}

In the data layer for ProductAvailabilityTemplate.Get, I had been optimizing the performance of my LINQ code. In particular, I had set ctx.ObjectContext.ContextOptions.LazyLoadingEnabled = false; to prevent EF from loading some entities (via navigation properties) that I don't need in this scenario.

However, once this change was made I noticed that my TaxRates weren't loading fully, because ctx.ObjectContext.ContextOptions.LazyLoadingEnabled was still false in my Tax data layer code. This meant that an entity linked to TaxRate via a navigation property wasn't being loaded.

To overcome this problem I simply set ctx.ObjectContext.ContextOptions.LazyLoadingEnabled = true; in the Tax data layer method, but I am concerned that an unrelated change could cause a problem like this. It seems that you can't safely disable lazy loading for one feature without potentially affecting the operation of whatever is called afterwards. I am tempted to remove all navigation properties, disable lazy loading, and use good old fashioned joins to load exactly what I need for each data layer call, no more no less.

Would welcome any advice.

2
"In particular, I had set ctx.ObjectContext.ContextOptions.LazyLoadingEnabled = false; to prevent EF from loading some entities (via navigation properties) that I don't need in this scenario." -- Lazy loading causes entities to get loaded when you access navigation properties, so if you don't need them, why are you accessing those properties? If you don't need them and just don't use them, they won't get loaded.user743382

2 Answers

3
votes

It's a trade off:

Lazy Loading

  • gives you the benefit of not needing to specify the depth of graph during loading
  • will need to return to the database to retrieve missing results
  • requires some pollution of the POCO's (e.g. virtual properties with proxies)
  • requires the DbContext to be longer lived, for the duration of all data accesses.

Eager Loading

  • requires a lot more thought into the depth of loading during each fetch
  • will typically generate fewer queries with wider joins to fetch the graph at once
  • does not require any alteration or ceremony around your entities
  • Allows much shorter lived connections and DbContexts

FWIW, I've generally done prototype work with Lazy Loading enabled, to get software to a demonstrable state, and once the data access patterns stabilize, then switch off Lazy Loading and move to explicitly Included references. A few Unit Tests checking for null references will also do wonders at this point. I am loathe to deliver a production system with Lazy Loading still enabled, as there is an element of non-determinism (e.g. difficult to fully test), and the need to return to the DB for further data will hurt performance.

Either way, I wouldn't switch off all Navigation and do explicit Joins - you are losing the power of navigability that an ORM provides. When you switch out of Lazy Loading, simply explicitly define the entities to be eager loaded with applicable Includes

2
votes

I was fond of lazy loading when I started using EF, but after a while I realized that it was affecting performance since it effectively disables joins and pulls all subdata in separate queries even if you need to consume it all at once.

So now I'm rather using Includes to eagerly load the sub-entities that I'm interested in. You could also do this somewhat dynamic, for instance by providing a includeDetails parameter:

public IEnumerable<Customer> LoadCustomersStartingWithName(string name, bool includeDetails)
{
    using (var db = new MyContext())
    {
        var customers = db.Customers;
        if (includeDetails)
            customers = customers.Include(x => x.Orders).Include(x => x.ContactPersons);

        customers = customers.Where(x => x.Name.StartsWith(name));

        return customers;        
    }
}

For the code to work in EF6, you would also need to include

using System.Data.Entity;

at the top of the class