1

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.

  1. The ApplicationContext is loaded into our DIContainer as InstancePerRequest
  2. The API Call asks the cache for all the Retailers

    1. If the cache has the data, and the cache expiration hasn't expired, it'll return the List<Retailer> from the cache
    2. 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();

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()

Eoin Campbell
  • 43,500
  • 17
  • 101
  • 157
  • Disabling Lazy-Loading might help, as suggested in https://stackoverflow.com/q/19681773/558486 and https://stackoverflow.com/questions/18398356/solving-the-objectcontext-instance-has-been-disposed-and-can-no-longer-be-used – Rui Jarimba Sep 11 '18 at 10:23
  • Does the `Languages` property need to be `virtual`? – spender Sep 11 '18 at 10:25
  • @RuiJarimba yeah I saw that but the "fix" is to use an include. We're already doing that. And it works. Until it doesn't and at some arbitrary point in the future it tries to reload/rehydrate that sub collection. thats the bit we're confused by. – Eoin Campbell Sep 11 '18 at 10:44
  • @spender "need" is a strong word. The EF Model & Infra code is in a project that's shared by many other top-level projects/solutions. We're only seeing this in our WebAPI but the same data model/context/dll is used under our Web Front End App Service, our Windows Services, our Azure Functions, etc... so I'm loathe to start introducing special cases and break lazy loading across the board for this one entity, especially when I don't understand the root cause of the issue – Eoin Campbell Sep 11 '18 at 10:47
  • What is the EF version (6.1.3 or 6.2)? Also is this happening only for these two entities? If yes, is there something unusual in these entity models / configuration? – Ivan Stoev Sep 11 '18 at 11:41
  • EF 6.2 - Have only experienced it for these 2 as these are the only ones that are persisted in this particular cache... and nothing strange or unusual in their configurations. – Eoin Campbell Sep 11 '18 at 13:04
  • Occam's razor.. this ended up being a bit of a PEBKAC issue. Some code had been added elsewhere in the stack that was creating a poison cache. The code which handled repopulating the cache had the `.Include()` in it. A separate api endpoint utilizing the same cache was ommitting this `Include()` So when the cache expired, whichever API endpoint got hit first would either load the cache correctly... or load it with the omitted `include()` resulting in lazyloading. – Eoin Campbell Sep 13 '18 at 09:03
  • 1
    I've found that lazy-loading can turn the pit-of-success into the-pit-of-failure for very similar reasons. IMO it's important to be explicit about what you want to fetch from the database and when you want to do it. The state-dependent behaviour of lazy-loading means that important but non-obvious things happen away from the call-site. When you mess it up, without lazy-loading, failure is much easier to see. – spender Sep 14 '18 at 00:19

0 Answers0