We have a weird bug that's cropped up in the past couple of days.
In a nutshell, Entity Framework appears to be attempting to lazy load a collection, that was previously eager-loaded (using an Include()
) which results in an ObjectContextDisposed
Exception
We have a very simple 2 entity model that represents a Retailer and the Languages that their website supports as a many-to-many configuration. 1 retailer site can support many languages/ 1 language can be supported by many retailers.
public class Retailer
{
public int Id {get;set;}
public string Name {get; set;}
public string Site {get; set;}
public virtual ICollection<Language> Languages { get; private set; }
//other props omitted for brevity
}
// Reciprocal class exists for Language.
Because this information doesn't change very often, we cache it in a memory cache in our WebAPI.
- The ApplicationContext is loaded into our DIContainer as
InstancePerRequest
The API Call asks the cache for all the Retailers
- If the cache has the data, and the cache expiration hasn't expired, it'll return the
List<Retailer>
from the cache - If the cache does not have the data OR the cache expiration has passed, it'll run the following query to refresh the cache and return that to the user.
//Get all retailers, eager loading their Languages subcollections context .Retailers .Include(r => r.Languages) .ToList();
- If the cache has the data, and the cache expiration hasn't expired, it'll return the
What we're seeing now is that after a certain period of time, the API call will start throwing an exception (The ObjectContext instance has been disposed of ...) while trying to access Retailer Languages.
_cache.GetByKey("retailers").FirstOrDefault().Languages
System.ObjectDisposedException: The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.
at System.Data.Entity.Core.Objects.ObjectContext.get_Connection()
at System.Data.Entity.Core.Objects.ObjectQuery`1.GetResults(Nullable`1 forMergeOption)
at System.Data.Entity.Core.Objects.DataClasses.EntityCollection`1.Load(List`1 collection, MergeOption mergeOption)
at System.Data.Entity.Core.Objects.DataClasses.RelatedEnd.DeferredLoad()
at System.Data.Entity.Core.Objects.Internal.LazyLoadBehavior.LoadProperty[TItem](TItem propertyValue, String relationshipName, String targetRoleName, Boolean mustBeNull, Object wrapperObject)
at System.Data.Entity.Core.Objects.Internal.LazyLoadBehavior.<>c__DisplayClass7`2.<GetInterceptorDelegate>b__1(TProxy proxy, TItem item)
at System.Data.Entity.DynamicProxies.Retailer_AADFD3EC4BC12C1210279CFB5CE3F49612501FB98800B437CE0DA19DFDAF3293.get_Languages()
It seems completely arbitrary when this occurs. It's almost like some internal part of EntityFramework is deciding the that Languages Proxy Collection is no longer valid and needs to be re-loaded/re-lazy-loaded.
There are a few different things we can do here:
- Cache DTOs instead of the EF Entities
- Iterate over the results and use
dbContext.Entry(entity).State = EntityState.Detached;
to detach each one - Try Calling
AsNoTracking
on our query
But We're all pretty experienced with EF here and have been working with it for years so we're a bit stumped why this is even happening. Are there any circumstances that EntityFramework will attempt to re-lazyload a collection that was previously eager-loaded with Include()