Solving "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection" InvalidOperationException
By default Entity Framework uses lazy-loading for navigation properties. That's why these properties should be marked as virtual - EF creates proxy class for your entity and overrides navigation properties to allow lazy-loading. E.g. if you have this entity:
public class MemberLoan
{
public string LoandProviderCode { get; set; }
public virtual Membership Membership { get; set; }
}
Entity Framework will return proxy inherited from this entity and provide DbContext instance to this proxy in order to allow lazy loading of membership later:
public class MemberLoanProxy : MemberLoan
{
private CosisEntities db;
private int membershipId;
private Membership membership;
public override Membership Membership
{
get
{
if (membership == null)
membership = db.Memberships.Find(membershipId);
return membership;
}
set { membership = value; }
}
}
So, entity has instance of DbContext which was used for loading entity. That's your problem. You have using
block around CosisEntities 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.
To fix this behavior you can use eager loading of navigation properties which you will need later:
IQueryable<MemberLoan> query = db.MemberLoans.Include(m => m.Membership);
That will pre-load all memberships and lazy-loading will not be used. For details see Loading Related Entities article on MSDN.
The CosisEntities
class is your DbContext
. When you create a context in a using
block, you're defining the boundaries for your data-oriented operation.
In your code, you're trying to emit the result of a query from a method and then end the context within the method. The operation you pass the result to then tries to access the entities in order to populate the grid view. Somewhere in the process of binding to the grid, a lazy-loaded property is being accessed and Entity Framework is trying to perform a lookup to obtain the values. It fails, because the associated context has already ended.
You have two problems:
You're lazy-loading entities when you bind to the grid. This means that you're doing lots of separate query operations to SQL Server, which are going to slow everything down. You can fix this issue by either making the related properties eager-loaded by default, or asking Entity Framework to include them in the results of this query by using the
Include
extension method.You're ending your context prematurely: a
DbContext
should be available throughout the unit of work being performed, only disposing it when you're done with the work at hand. In the case of ASP.NET, a unit of work is typically the HTTP request being handled.
Bottom Line
Your code has retrieved data (entities) via entity-framework with lazy-loading enabled and after the DbContext has been disposed, your code is referencing properties (related/relationship/navigation entities) that was not explicitly requested.
More Specifically
The InvalidOperationException
with this message always means the same thing: you are requesting data (entities) from entity-framework after the DbContext has been disposed.
A simple case:
(these classes will be used for all examples in this answer, and assume all navigation properties have been configured correctly and have associated tables in the database)
public class Person
{
public int Id { get; set; }
public string name { get; set; }
public int? PetId { get; set; }
public Pet Pet { get; set; }
}
public class Pet
{
public string name { get; set; }
}
using (var db = new dbContext())
{
var person = db.Persons.FirstOrDefaultAsync(p => p.id == 1);
}
Console.WriteLine(person.Pet.Name);
The last line will throw the InvalidOperationException
because the dbContext has not disabled lazy-loading and the code is accessing the Pet navigation property after the Context has been disposed by the using statement.
Debugging
How do you find the source of this exception? Apart from looking at the exception itself, which will be thrown exactly at the location where it occurs, the general rules of debugging in Visual Studio apply: place strategic breakpoints and inspect your variables, either by hovering the mouse over their names, opening a (Quick)Watch window or using the various debugging panels like Locals and Autos.
If you want to find out where the reference is or isn't set, right-click its name and select "Find All References". You can then place a breakpoint at every location that requests data, and run your program with the debugger attached. Every time the debugger breaks on such a breakpoint, you need to determine whether your navigation property should have been populated or if the data requested is necessary.
Ways to Avoid
Disable Lazy-Loading
public class MyDbContext : DbContext
{
public MyDbContext()
{
this.Configuration.LazyLoadingEnabled = false;
}
}
Pros: Instead of throwing the InvalidOperationException the property will be null. Accessing properties of null or attempting to change the properties of this property will throw a NullReferenceException.
How to explicitly request the object when needed:
using (var db = new dbContext())
{
var person = db.Persons
.Include(p => p.Pet)
.FirstOrDefaultAsync(p => p.id == 1);
}
Console.WriteLine(person.Pet.Name); // No Exception Thrown
In the previous example, Entity Framework will materialize the Pet in addition to the Person. This can be advantageous because it’s a single call the the database. (However, there can also be huge performance problems depending on the number of returned results and the number of navigation properties requested, in this instance, there would be no performance penalty because both instances are only a single record and a single join).
or
using (var db = new dbContext())
{
var person = db.Persons.FirstOrDefaultAsync(p => p.id == 1);
var pet = db.Pets.FirstOrDefaultAsync(p => p.id == person.PetId);
}
Console.WriteLine(person.Pet.Name); // No Exception Thrown
In the previous example, Entity Framework will materialize the Pet independently of the Person by making an additional call to the database. By default, Entity Framework tracks objects it has retrieved from the database and if it finds navigation properties that match it will auto-magically populate these entities. In this instance because the PetId
on the Person
object matches the Pet.Id
, Entity Framework will assign the Person.Pet
to the Pet
value retrieved, before the value is assigned to the pet variable.
I always recommend this approach as it forces programmers to understand when and how code is request data via Entity Framework. When code throws a null reference exception on a property of an entity, you can almost always be sure you have not explicitly requested that data.