Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is it possible to stop Entity Framework "fixing up" the relationships after SaveChanges?

I am getting this exception from Entity Framework 4.3.1 (incidentally from an ASP .NET MVC 3 web application built in Visual Studio 2010 against .NET 4):

System.InvalidOperationException: The changes to the database were committed successfully, but an error occurred while updating the object context. The ObjectContext might be in an inconsistent state. Inner exception message: The object cannot be added to the object context. The object's EntityKey has an ObjectStateEntry that indicates that the object is already participating in a different relationship.

I did some Googling and I came to this (on msdn):

InvalidOperationException when saving

In that Jeff Derstadt (the accepted answer) says:

When you then call SaveChanges, the context persists the changes and then tries to fixup the state entries. When it does this, it performs "key fixup" where is transforms temporary EntityKeys to full EntityKeys. In this case, temporary key TKP2 will now be equal to the Key for P2. The context tries to "promote" the Key Entry in the state manager to the full Entity Entry, and in the process fixes up the graph. Here is where the exception happens because there is a conflict: P1.Spouse = P2, and so P2.Spouse should = P1, but because there were added items, P2.Spouse already equals P3 and the EntityReference cannot have more than one value. Unfrtunately this case is not always possible to detect before the database save happens because temporary EntityKeys are not equivalent to full EntityKeys and so we cannot identify future colisions like this.

I am thinking in a bit of a "dirty" way here and so my question is this...

Since I do not really care what the object graph looks like after the save changes (that is the end of my request so is thrown away anyway) and the data has been inserted (and is actually perfectly OK when I look in SQL Server at it after I let it continue on in the debugger) - is there a way I can tell Entity Framework to not do this step (and therefore not get this exception and, presumably, save a bit of time too)?

like image 614
kmp Avatar asked Oct 04 '12 09:10

kmp


2 Answers

You should post a little bit of code if you want a better help.

Still, I've found myself fighting around with some of these errors and came up with a basic truth: if you are referencing something that already exists in the database, do reference it. Do not just use keys or codes, use the actual objects represented in your DbContexts. You can do this finding them with the DbContext.Entry() method.

On the other hand, if both the original AND the referenced objects are created at runtime, make sure that they are given a unique ID, and that ID is known by the Entity framework (not by the database) at runtime. Otherwise (for instance if you rely on a NEWID() Sql function) it may be that object A references object B with a runtime ID that is not equal to the ID that the same object gets once stored in the database.

As I say, if you'd please post a bit of code we might help better ;)

like image 110
Isaac Llopis Avatar answered Oct 13 '22 06:10

Isaac Llopis


there is no official way to do this, but try this out:

private static readonly Lazy<MethodInfo> internalDeleteMethod = new Lazy<MethodInfo>(
    () => typeof(ObjectContext).Assembly.GetType("System.Data.Objects.EntityEntry").GetMethod("Delete", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(bool) }, null)); 


public static void Delete<TEntity>(Func<DbContext> dbContextFactory, TEntity entity,
    Func<DbContext, IEnumerable<DbEntityValidationResult>> onSaving = null,
    Action<DbContext> onSaved = null,
    bool referenceFixup = true)
        where TEntity : class
{
    Guard.ArgumentNotNull(dbContextFactory, "dbContextFactory");
    Guard.ArgumentNotNull(entity, "entity");
    onSaving = onSaving ?? (_ => null);
    onSaved = onSaved ?? (_ => { });

    using (var dbContext = dbContextFactory())
    {
        var set = dbContext.Set<TEntity>();
        set.Attach(entity);

        var entry = ((IObjectContextAdapter)dbContext).ObjectContext.ObjectStateManager.GetObjectStateEntry(entity);
        internalDeleteMethod.Value.Invoke(entry, new object[] { referenceFixup });

        dbContext.SaveChanges();
     }
 }
like image 43
benwasd Avatar answered Oct 13 '22 06:10

benwasd