5

In ASP.NET MVC 2, using Entity Framework 4, I'm getting this error "An entity object cannot be referenced by multiple instances of IEntityChangeTracker".

A search of SO shows that it is probably because I have different instances of the Entity Framework ObjectContext, when it should be only one ObjectContext instance for each HttpContext.

I have this code (written long before I joined) that appears to do just that - have one ObjectContext for every HttpContext. But I am getting the "IEntityChangeTracker" exception frequently so it is probably not working as intended:

// in ObjectContextManager.cs
public const string ConnectionString = "name=MyAppEntities";
public const string ContainerName = "MyAppEntities";

public static ObjectContext GetObjectContext()
{
    ObjectContext objectContext = GetCurrentObjectContext();
    if (objectContext == null) // create and store the object context
    {   
        objectContext = new ObjectContext(ConnectionString, ContainerName);     
        objectContext.ContextOptions.LazyLoadingEnabled = true;    
        StoreCurrentObjectContext(objectContext);
    }
    return objectContext;
}

private static void StoreCurrentObjectContext(ObjectContext objectContext)
{
    if (HttpContext.Current.Items.Contains("EF.ObjectContext"))
        HttpContext.Current.Items["EF.ObjectContext"] = objectContext;
    else
        HttpContext.Current.Items.Add("EF.ObjectContext", objectContext);
}

private static ObjectContext GetCurrentObjectContext()
{
    ObjectContext objectContext = null;
    if (HttpContext.Current.Items.Contains("EF.ObjectContext")
        objectContext = (ObjectContext)HttpContext.Current.Items["EF.ObjectContext"];
    return objectContext;
}

I've examined this code and it looks correct. It does as far as I can tell return one ObjectContext instance for each HttpContext. Is the code wrong?

If the code is not wrong, why else would I get the "An entity object cannot be referenced by multiple instances of IEntityChangeTracker" exception?

EDIT: To show how the ObjectContext is disposed:

// in HttpRequestModule.cs
private void Application_EndRequest(object source, EventArgs e)
{
    ServiceLocator.Current.GetInstance<IRepositoryContext>().Terminate();
}

// in RepositoryContext.cs
public void Terminate() 
{
    ObjectContextManager.RemoveCurrentObjectContext();
}

// in ObjectContextManager.cs
public static void RemoveCurrentObjectContext()
{
    ObjectContext objectContext = GetCurrentObjectContext();
    if (objectContext != null)
    {
        HttpContext.Current.Items.Remove("EF.ObjectContext");
        objectContext.Dispose();
    }
}
JK.
  • 21,477
  • 35
  • 135
  • 214

1 Answers1

5

My guess is that you've stored an object somewhere in memory (most likely the http cache using in-process mode, but could also be any manual cache such as a shared dictionary), and now you've somehow associated that object with something else, for example:

newOrder.OwnerUser = currentUser; // <== let's say currentUser came from cache
                                  // and newOrder was on your new entity context

Hence, a problem if the cached object still thinks it is attached to a context; not least, you are probably keeping an entire graph alive accidentally.


The code looks OK (as long as you are disposing it at the end of the request), but this would be a good time to add:

private const string EFContextKey = "EF.ObjectContext";

and use that in place of the 5 literals. Avoids a few risks ;p

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • Actually I have stored an object in cache (Currency DefaultCurrency) and then attached it to the order object (order.Currency = DefaultCurrency) - and that is exactly where the exception is thrown .. That is a very promising lead. When I saved DefaultCurrency to cache it is of type `System.Data.Entity.DynamicProxies.Currency_F4008E27DE_etc` instead of the POCO class `Entities.Currency`. What do I need to do with this object to be able to safely store it in the cache and then add it to another object later, detach it? – JK. Jul 14 '11 at 06:06
  • 1
    @JK - tricky; since there may be multiple threads trying to use it at once, the best thing I can suggest is to write some code that clones it (creating a vanilla POCO that isn't attached to the context), and *store* a clone (avoids the risk of keeping a graph alive), and clone it *again* every time you fetch it out. I don't know if it is possible in EF, but if this was L2S I'd be setting the `DefaultCurrencyId` rather than `DefaultCurrency` - avoids a few problem cases. – Marc Gravell Jul 14 '11 at 06:10
  • Thanks I will see what I can do there – JK. Jul 14 '11 at 06:38