6
votes

TryGetObjectStateEntry returns false but when i try to attach the entity i get 'An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key.'

The entity key is of type Guid.

How is this possible?

Edit: i am attaching 2 entities with different key. the error occurs always on the second entity of this type that i attach. if i swap them the error is still on the 2nd one.

    public bool IsAttached<T>(T obj) where T : class
    {
        ObjectStateEntry entry = null;

        ObjectContext objCtx = GetObjectContext();

        bool isKeyAttached = false;

        EntityContainer container = objCtx.MetadataWorkspace.GetEntityContainer(objCtx.DefaultContainerName, DataSpace.CSpace);
        EntitySetBase entitySet = container.BaseEntitySets.Where(item => item.ElementType.Name.Equals(typeof(T).Name)).FirstOrDefault();
        System.Data.EntityKey key = objCtx.CreateEntityKey(entitySet.Name, obj);

        if (objCtx.ObjectStateManager.TryGetObjectStateEntry(key, out entry))
        {
            isKeyAttached = entry.State != System.Data.EntityState.Detached;
        }

        return isKeyAttached;
     }
2

2 Answers

1
votes

This problem can occur if the entities you attach have navigation properties refering to other entities. Example:

public class Parent
{
    public int Id { get; set; }
    public Child Child { get; set; }
}

public class Child
{
    public int Id { get; set; }
}

The following code will throw an exception:

using (var context = new MyDbContext())
{
    var parent = new Parent { Id = 1 };
    var child1 = new Child { Id = 1 };
    parent.Child = child1;

    var child2 = new Child { Id = 1 };  // same key

    context.Children.Attach(child2);    // child with key = 1 is attached now

    var objContext = ((IObjectContextAdapter)context).ObjectContext;

    ObjectStateEntry entry;
    bool isAttached = objContext.ObjectStateManager.TryGetObjectStateEntry(
        new EntityKey("MyDbContext.Parents", "Id", parent.Id), out entry);
    // isAttached will be false because a Parent with Id = 1 is not attached
    if (!isAttached)
    {
        // we assume now that we could attach the parent safely
        context.Parents.Attach(parent);

        // Assumption is wrong -> Exception, because Attach attaches the whole
        // object graph, so it tries also to attach child1 together with parent
        // But child1 has the same key as child2 which is already attached
    }
}

So, the point is that TryGetObjectStateEntry only checks the state of the base entity and doesn't consider any navigation properties. Attach on the other hand does not only attach the base entity but also the children which are not yet attached, leading to the exception.

0
votes

I was having the same problem in Entity Framework 6.4.4 but the problem was that I misunderstood the syntax for EntityKey's qualifiedEntitySetName constructor parameter.

In my example, I have a SQL Server table like this:

CREATE TABLE dbo.Orders (
    TenantId int NOT NULL,
    OrderId  int NOT NULL,
    etc

    CONSTRAINT PK_Orders PRIMARY KEY ( TenantId, OrderId )
)

The qualifiedEntitySetName argument must consist of two labels separated by a dot, e.g. Foo.Bar - I thought the name would be the fully-qualified database object name (i.e. I thought it would be dbo.Orders), but the first part must actually be the DbContext name.

This was my incorrect code:

Boolean TryGetOrderFromContext( this ContosoDbContext db, Int32 tenantId, Int32 orderId, out Order order )
{
    EntityKey orderKey;
    {
        KeyValuePair<String,Object>[] orderKeyValues = new[]
        {
            new KeyValuePair<String,Object>( "TenantId", tenantId ),
            new KeyValuePair<String,Object>( "OrderId" , orderId  )
        };

        orderKey = new EntityKey( qualifiedEntitySetName: "dbo.Orders", entityKeyValues: orderKeyValues )
    }
    
    ObjectContext      objCtx = ((IObjectContextAdapter)db).ObjectContext;
    ObjectStateManager objMgr = db.ObjectStateManager;
    
    if( osm.TryGetObjectStateEntry( orderKey, out ObjectStateEntry se ) )
    {
        order = se.Entity;
        if( order is null ) throw new InvalidOperationException( "se.Entity is null." );
        return true;
    }

    order = default;
    return false;
}

As my DbContext is named ContosoDbContext the EntityKey constructor and the rest of the code should have been like this:

Boolean TryGetOrderFromContext( this ContosoDbContext db, Int32 tenantId, Int32 orderId, out Order order )
{
    EntityKey orderKey;
    {
        KeyValuePair<String,Object>[] orderKeyValues = new[]
        {
            new KeyValuePair<String,Object>( "TenantId", tenantId ),
            new KeyValuePair<String,Object>( "OrderId" , orderId  )
        };

        orderKey = new EntityKey( qualifiedEntitySetName: "ContosoDbContext.Orders", entityKeyValues: orderKeyValues )
    }
    
    // etc
}

...and now TryGetObjectStateEntry correctly returns true!