vkhorikov / DddAndEFCore

Source code for the DDD and EF Core Pluralsight course
https://enterprisecraftsmanship.com/ps-ef-core
MIT License
249 stars 90 forks source link

EF Core instance of entity type cannot be tracked #10

Closed robertlarkins closed 3 years ago

robertlarkins commented 3 years ago

I've been following the approaches proposed in this repo and the associated pluralsight course, and they have worked really well, but recently been getting the following exception thrown by EFCore:

"The instance of entity type 'YourEntity' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached. Consider using 'DbContextOptionsBuilder.EnableSensitiveDataLogging' to see the conflicting key values."

I have created a minimum example to demonstrate the issue (using EFCore inmemory and a Sqlite DB) here: https://github.com/robertlarkins/efcore-cannot-track-entity-issue

As far as I can determine it is related to static entities (such as Course in this repo, or Appointment Status in my linked repo), and it occurs when the context is holding an instance of AppointmentStatus pulled from the database, and the same AppointmentStatus is added to the context from the static AppointmentStatus reference in code. When trying to save the context to the database the above exception gets thrown.

This exception occurs on this line:

IEnumerable<EntityEntry> enumerationEntries = ChangeTracker.Entries()
                .Where(x => EnumerationTypes.Contains(x.Entity.GetType()));

so it never gets to the point of trying to set the EntityState as unchanged.

Is this an issue you have come across before? I couldn't find how this repo stops this occurring, hopefully I have just missed something, but if not, then I assume it could potentially happen within this repo as well. Alternatively should this issue be presented on the EFCore issue tracker?

robertlarkins commented 3 years ago

As a side note, this issue can be avoided by getting the desired AppointmentStatus instance from the db directly rather than from the static entity value, but this defeats the purpose of having static values, and requires additional calls to the db.

robertlarkins commented 3 years ago

I finally found EFCore issues that (appear to) relate to this issue: https://github.com/dotnet/efcore/issues/23615#issuecomment-742065461 https://github.com/dotnet/efcore/issues/20124 (currently open) https://github.com/dotnet/efcore/issues/20116#issuecomment-593499871 https://github.com/dotnet/efcore/issues/19984#issuecomment-594086811 https://github.com/dotnet/efcore/issues/19877 https://github.com/dotnet/efcore/issues/11457 (currently open)

@vkhorikov What is your thoughts on the best way of resolving this issue when using entities with static values (immutable entities)? Is there anything that could be added to the Entity class (in CSharpFunctionalExtensions) to assist with this?

I have also found the following, but unsure if they will help for the above situation:

vkhorikov commented 3 years ago

Not sure why this is not working. Try adding lazy loading to your reference project, maybe that's the difference here.

It's impossible to make changes in CSharpFunctionalExtensions to avoid this issue, it is core to how EF keeps track of entities in its DbContext. As usual, it's unnecessarily overcomplicated; it's a solved problem in NHibernate.

robertlarkins commented 3 years ago

In our main project we have lazy loading, and this issue is occurring 😢. I couldn't figure out if or how to add lazy loading to the example solution, I suspect that the InMemoryDB doesn't cater for it.

From reading and my (limited) understanding what is happening is some query pulls the static defined variable into the context (say AppointmentStatus.Finished in this case), such as when getting all the Appointments, so this become the instance known to the context, but when updating an appointment to AppointmentStatus.Finished from the static code, this becomes a second instance tracked by the context. EF then sees these as two separate instances for the same thing (even though they are the same, and immutable), as EF determines equivalency by reference equals.

You're right, because of how EF tracks entities (using reference equals) nothing on the Entity class will help. At this stage I guess we continue with pulling the values from the db when necessary until EF better handles this (whenever that might be).

I'll go and create this as an EFCore issue and see what is said. Thanks again for your response @vkhorikov.

pantonis commented 1 year ago

@robertlarkins I have the same issue in latest EF Core. What is the solution to this? How can this be fixed? Thank you

pantonis commented 1 year ago

FYI I opened a ticket for this here