Home > Blockchain >  EF6 Could not attach a detached entity to context
EF6 Could not attach a detached entity to context

Time:10-07

I have an issue where I cannot attached an entity to DBContext although it shows its Enity.State as Detached.

I Get the entity using a DBContext that I then dispose of and then make some changes and try to save those new changes using a new DBContext, So obviously the new DBContext does not see those entities as attached, hence why I need to attach them to it.

When I try to attach the enitity using code below

db.Receipts.Attach(receipt);

I get that error

"An object with the same key already exists in the ObjectStateManager. The ObjectStateManager cannot track multiple objects with the same key"

I tried getting entities using AsNoTracking() in the first DBContext fetch, but it still showed same error.

I tried attaching using db.Entry(receipt).State = System.Data.Entity.EntityState.Modified;

Also tried using objectContext.ObjectStateManager.ChangeObjectState(receipt, System.Data.Entity.EntityState.Modified);

And still having same issue.

I like to mention that I am using old .Net Framework 4.0 and Enity Framework 6.0.

Any help would be much appreciated.

CodePudding user response:

You can do the following to get the changes to the new DBContext :

            System.Data.Entity.Core.Objects.ObjectContext objectContext = ((IObjectContextAdapter)db).ObjectContext;
            string entitySetName = objectContext.CreateObjectSet<Receipts>().EntitySet.Name;
            objectContext.ApplyCurrentValues<Receipts>(entitySetName, receipt);

Edit: You will have to load the original entity form the db first (if it is not already loaded):

IDbSet<Receipts> dbset = db.Set<Receipts>();
var dbReceipt = dbset.Find(id)

CodePudding user response:

This error commonly appears when processing rows in a loop and working with related entities, especially where these entities have been serialized/de-serialized, or cloned as part of the modification process.

Say I load a list of Players that reference the same Team from a DbContext.

 Player (Id: 1, Name: George, TeamId: 10) [REF 101]
 Player (Id: 2, Name: Simon, TeamId: 10) [REF 102]
 Team (Id: 10, Name: Jazz) [REF 103]

Both George and Simon would be returned eager loading their team which both would reference a single team entity [REF 103].

If we detach these, then open a new DbContext and go to re-attach:

context.Players.Attach(player1);
context.Teams.Attach(player1.Team);
context.Players.Attach(player2);
context.Teams.Attach(player2.Team);

This should work as expected. Even though we are attaching the same team twice, it is the same reference and EF will skip the second call seeing the reference is already attached. Caveat: It's been a long time since I've worked with EF4 so this behavior may be different, verified with EF6. You may need to check the DbContext to see if an entity is detached before attaching:

if(context.Entry(player1).State == EntityState.Detached)
    context.Players.Attach(player1);
if(context.Entry(player1.Team).State == EntityState.Detached)
    context.Teams.Attach(player1.Team);
if(context.Entry(player2).State == EntityState.Detached)
    context.Players.Attach(player2);
if(context.Entry(player2.Team).State == EntityState.Detached)
    context.Teams.Attach(player2.Team);

Now, if those entities get fed through a serializer and back (Such as is done with ASP.Net form submission or Ajax calls etc.) what we get back are new disconnected entities with new references, but one key difference:

Player (Id: 1, Name: George, TeamId: 10) [REF 201]
Player (Id: 2, Name: Simon, TeamId: 10) [REF 202]
Team (Id: 10, Name: Jazz) [REF 203]
Team (Id: 10, Name: Jazz) [REF 204]

George will reference a Team, in this case REF 203, while Simon will reference an instance of the same team, but REF 204. 2 instances containing the same data.

When we go to attach Player 1 [REF 201]/w Team 10 [REF 203] to a new DbContext, everything goes as expected. However, when we attach player 2 we will get the error with Player 2's team reference:

context.Players.Attach(player1);
context.Teams.Attach(player1.Team);
context.Players.Attach(player2);
context.Teams.Attach(player2.Team); // <-- Boom

The DbContext will be tracking a Team #10 from player 1's reference. Even checks for attached/detached state won't help as these are distinct references.

To address this we need to always check the DbContext for existing tracked references before attaching. This can be done by querying against the Local property of the DbSet. This will not hit the DB, it will just check for locally tracked references. The safe way to treat detached entities:

var trackedTeam = context.Teams.Local.SingleOrDefault(x => x.Id == player1.Team.Id);
if (trackedTeam == null)
    context.Teams.Attach(player1.Team);
else
    player1.Team = trackedTeam;

var trackedPlayer = context.Players.Local.SingleOrDefault(x => x.Id == player1.Id);
if (trackedPlayer == null)
    context.Players.Attach(player1);


trackedTeam = context.Teams.Local.SingleOrDefault(x => x.Id == player2.Team.Id);
if (trackedTeam == null)
    context.Teams.Attach(player2.Team);
else
    player2.Team = trackedTeam;

trackedPlayer = context.Players.Local.SingleOrDefault(x => x.Id == player2.Id);
if (trackedPlayer == null)
    context.Players.Attach(player2);

In the case where a DbContext is already tracking an entity, you may need additional code to compare and copy values across from the untracked copy to the tracked existing instance. Working with detached entities safely with references can be rather clumsy process.

  • Related