Entity Framework : Including multiple navigational properties

If you have ever come across this stupid error:


The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.


This is why it happens.

Assume you have a class called Order. It has a navigation property called LineItems.

public class Order
{
   public int ID { get; set; }
   public decimal Total { get; set; } // Use decimal for money values
   public string Description { get; set; }
   public virtual ICollection<LineItem> LineItems { get; set; }
}

This LineItems property is a collection of LineItem objects like this and in this object, there's another navigation property called Product. Basically, this is a hierarchy. Think of a ProductType object inside Product class.

public class LineItem
{
   public int Qty { get; set; }
   public decimal LineTotal { get; set; } // Use decimal for money values
   public string Description { get; set; }
   public virtual Product Product { get; set; }
}

And then you have a Data Context class like this.

public partial class OrdersContext : DbContext
{
    public OrdersContext ()
            : base("Name=OrdersConnection")
    {    }

    public DbSet<LineItem> LineItems { get; set; }
    public DbSet<Product> Products { get; set; }
    public DbSet<ProductType> ProductTypes { get; set; }
    public DbSet<Order> Orders { get; set; }

}

And you try to fire a query like this to retrieve all Orders:

public IEnumerable<Orders> GetAll()
{
    List<Orders> entities;

    using (var entityContext = new OrdersContext())
        entities = entityContext.Orders 
                                .Include(u => u.LineItems)
                                .ToList();

    return entities;
}

When you write this code, you might think that if you want to ever get the ProductTypes of each product in order line item, it will work. something like

var orders = _repository.GetAll();

var productType = orders[0].LineItems[0].Product.ProductType.Name;

 But instead, you get an error...
The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.

It's because, by default Entity Framework uses lazy-loading for navigation properties. That's why these properties are marked as virtual - entity framework creates proxy class for your entity and overrides navigation properties to allow lazy-loading. If you debug, you can see the returned objects from EF looks like "Orders_34AB....." something. It is the returned proxy inherited from this entity and has provided DbContext instance to this proxy in order to allow lazy loading of membership later. So, entity has instance of DbContext which was used for loading entity. That's your problem. You have using block around Orders usage. Which disposes context before entities are returned. When some code later tries to use lazy-loaded navigation property, it fails, because context is disposed at that moment.

So, what you need to do is, pre-load all LineItems, Products, and ProductTypes and if there are any other navigation properties, all of them!

Now, a neat solution.

Lets define a extension method. Extracted from this StackOverFlow answer.

public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query, params Expression<Func<T, object>>[] includes)
    where T : class
{
    if (includes != null)
    {
        query = includes.Aggregate(query, 
                  (current, include) => current.Include(include));
    }

    return query;
}
Usage (commented on each line):

var query = context.Orders  // All orders
                   .IncludeMultiple( 
                    c => c.LineItems  // Include all line items for a order
                          .Select(o => o.Product  // each line item, include its product
                                        .Select(o => o.ProductType))); // each product, include its type

No more context disposed errors!


Popular posts from this blog

Print a receipt using a Thermal Printer with C#.NET

Automatic redirect upon session timeout using ASP.NET MVC and Javascript

Complex Master-Detail Form using Knockout.js and ASP.NET MVC