C# Entity Framework using only one ObjectContext per HttpContext
Asked Answered
E

1

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();
    }
}
Englut answered 14/7, 2011 at 5:19 Comment(2)
are you disposing the context in the EndRequest method?Septennial
Updated to show disposal methodEnglut
D
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

Denunciate answered 14/7, 2011 at 5:34 Comment(3)
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?Englut
@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.Denunciate
Thanks I will see what I can do thereEnglut

© 2022 - 2024 — McMap. All rights reserved.