Home > database >  The instance of the entity type cannot be tracked because another instance with the same key value p
The instance of the entity type cannot be tracked because another instance with the same key value p

Time:10-05

I know that this question has been asked several times and mostly people are directly suggesting solution to the code piece asked in the question but I really wanted to know the core reason behind this error as in due to which condition do we get this error ? I have seen in a number of questions people telling different answers that could resolve the issue but still I haven't found the exact reason for this error. I have read that it is due to the tracking behavior of EF Core, but what exactly in that behavior causes this issue ?

Some small code samples would be appreciated. thanks.

CodePudding user response:

Well, I have debugged this part of EF Core. There are at least two checks in ChangeTracker for entity existence.

  1. Find Entry by entity reference, if it is found - everything ok.
  2. Find Entry by entity's Primary Key, if it is found - you have this exception.

If second check is passed, attached entity is registered in both dictionaries (if you have several keys, it will be additional dictionaries to find entry by key).

It is simplified explanation, but it explains why better to look first at already tracked entities and then decide which Entry to use to avoid such exception.

CodePudding user response:

The reason is, in essence, there in the error message..

When you use EF to download something from a database then it will, unless you tell it not to, keep track of the entity it downloaded. No matter what you do with the entity you got, EF will also remember it. It's not the sole reason but a highly visible reason is that it makes life much easier when it's time to save changes:

var u = db.Users.FirstOrDefault(x => x.Name == "John");
u.Name = "Jim";
db.SaveChanges();

If EF had literally just downloaded the data, made a User and handed it over and not kept any memory of it then the SaveChanges wouldn't work; it would have to look something like

db.SaveChanges(u);

i.e. you'd have to give the altered data back. This would also pose more complications if you wanted to use optimistic concurrency because that generally works by comparing the in-db values with the originally-downloaded values to know whether someone else has edited the db in the time during which you've had the object.

An optimistic save might look like:

UPDATE user SET name = 'Jim' WHERE id = 123 and name = 'John'

The original name we knew the user to have ("John") is included in the update query. If no one else changed the user name then great, the update will succeed because the WHERE clause will be true. If someone did rename this user before we did, then the update fails (updates 0 rows) and we can deal with it.

We couldn't do any of that that if EF lost all memory of what the old name was; a User object just has a simple string property for Name that doesn't remember "John" before we overwrote it with "Jim".. This emans that if EF had made a User, and just simply turned it over to you, it could never have known what the original name of the user was when it was time to update it back to the db

It's fair to say there's really quite a lot of smart stuff going in on the background of an EF context, far beyond what we see come out (i.e. "a User object") and remembering the objects it sees and data about them, their original values, current values, whether to add/update/delete them etc is vital to being able to function effectively


Where the problems come is if you do something like creating another whole new User entity with the same primary key as the one EF already knows, and try to attach it to EF's object store:

var u1 = db.Users.FirstOrDefault(x => x.Name == "John"); //EF now knows user 123
...
var u2 = new User { Id = 123, Name = "Mary"}
db.Users.Add(u2); //EF already knows user 123; John

EF won't be able to establish which record is authoritatively the truth of record if it knows about two users both with ID 123, so it refuses to start remembering another different user with an ID it already knows. In the code above the two users are different objects in different places in memory, and they share the same ID. This couldn't be mapped back to the DB


It should probably also be pointed out that if you've told EF that the ID is generated by the database it won't matter at all what you put for the ID when you do the Add: EF will know it's an added entity that will have its ID overwritten by the db when it's saved so you won't get an "Another instance with the same id" error if your "temporary" conflicts with an existing one

  • Related