dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.
https://docs.microsoft.com/ef/
MIT License
13.75k stars 3.18k forks source link

The instance of entity type '**Entity**' cannot be tracked because another instance with the same key value for {'Id'} is already being tracked. #23615

Closed mm-ryo closed 2 years ago

mm-ryo commented 3 years ago

Exception details :

The instance of entity type 'Entity' 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.

Steps:

  1. get the test entity from the database and map it to our domain model.
  2. without any changes then map it to db model
  3. call EF core api to remove it.
  4. call save changes
var testEntityModel = Mapper.Map<TestEntityModel>(DbContext.TestEntity.First());
var testEntity = Mapper.Map<TestEntity>(testEntityModel);

DbContext.TestEntity.RemoveRange(testEntity );  -- **the exception throw out.**

DbContext.SaveChanges();

Stack traces

   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.ThrowIdentityConflict(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry, Boolean updateDuplicate)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges, Boolean modifyProperties)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean modifyProperties, Nullable`1 forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode`1 node)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph[TState](EntityEntryGraphNode`1 node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState targetState, EntityState storeGeneratedWithKeySetTargetState, Boolean forceStateWhenUnknownKey)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.RemoveRange(IEnumerable`1 entities)
   at Microsoft.EntityFrameworkCore.DbContext.RemoveRange(Object[] entities)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.RemoveRange(TEntity[] entities)
   at ...........
   at Castle.Proxies.Invocations.ISolutionSharedRepository_LoadSolutions.InvokeMethodOnTarget()
   at Castle.DynamicProxy.AbstractInvocation.Proceed()
   at ...........

Provider and Version information

EF Core version: Microsoft.EntityFrameworkCore (3.1.7) Database provider: Npgsql.EntityFrameworkCore.PostgreSQL (3.1.4) Target framework: 3.1.7 Operating system: Microsoft Windows 10 Enterprise 64bit IDE: Visual Studio 2019 16.3

TehWardy commented 3 years ago

I seem to be getting this all over the place lately ... Did something change recently to cause this to happen more often?

I've also noticed if I post an array of something to the api that then gets an entity from the db and makes changes to it more than once EF gets all confused about what's needed rough steps to produce the issue ...

I get this error ^

ajcvickers commented 3 years ago

@robinNode Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

@TehWardy Please open a new issue and attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

mm-ryo commented 3 years ago

@robinNode Please attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

@TehWardy Please open a new issue and attach a small, runnable project or post a small, runnable code listing that reproduces what you are seeing so that we can investigate.

@ajcvickers I have attached a demo. please take a look at it. thanks a lot. EFCoreTest.zip

roji commented 3 years ago

@robinNode you seem to be using AutoMapper to map an entity instance to some other type, then map back to the entity type, and then attempting to interact with change tracking using the newly-converted instance. This isn't supported, since there are now two entity instances representing the same thing (i.e. with the same key). If I remove the back-and-forth mapping, everything works fine. Possible solutions are:

/cc @ajcvickers this bears some resemblance to the immutable scenario, where the user wants to replace an already-tracked instance with another instance.

ajcvickers commented 3 years ago

@roji Yes, I have been thinking a lot about the immutable scenario, and I think both this and that are dependent on #20124. It's then about conventions and patterns for how resolution happens in that hook. I agree that we should think about these things in a holistic way.

roji commented 3 years ago

In addition to #20124, maybe some sort of Replace method on the change tracker... Are we tracking something like this already?

Abbasalburaei commented 2 years ago

I have got this issue and I resolved it with context.ChangeTracker.Clear() with automapper

parko65 commented 10 months ago

I have got this issue and I resolved it with context.ChangeTracker.Clear() with automapper

Are you able to supply a sample snippet, I'm also mapping an incoming dto to a dbEntity before saving changes, however I'm doing all the logic within the service layer of the project and don't have direct access to the context but the repository does.

parko65 commented 10 months ago

@robinNode

After playing around with your example code, you are indeed asking EF Core to track multiple instances of the same entity.

A simple fix is to do your mapping later on the modified entity:

using (var context = new TestContext())
{
    var testEntityModel = context.TestEntity.First();
    context.TestEntity.RemoveRange(testEntityModel); // **no exception and table is updated later**

    mapper.Map<TestEntity>(testEntityModel); // **removed storage var here just for demo purposes**

    context.SaveChanges();
}
TehWardy commented 8 months ago

Interesting. I'm sure when it comes to how I use EF i'm probably like many, massively abusing it, but I've found that as a testament to how well written it is sometimes just taking an out of the box approach can improve things.

I found that a lot of my problems went away when I instanced up a new context for literally every single row operation on the db. Pooling helped offset the perf hits but on occasion I would get an error from one context where I had somehow attached it to another.

Correct me if i'm wrong but Tracking what entity is attached to what context is certainly not a fun task. I think the bulk of this thread comes down to "EF did a strange thing, something unexpected was tracked on an instance"

Random thought The change tracker: do we really need it at all?

Bear with me on this ... So in my situation I do something like this:

With context pooling and pooled factories this seems to be really fast, (like stupid fast) What does the context give us by having just the one "request scoped" and doing all the work on the one instance that I would lose with this approach?

Another idea I had was to put a thread safety wrapper around a singleton context and just throw out all changes after every transaction with the wrapper but that felt ugly and would likely result in poorer perf since in order to scale i'm using a ton of instances at the moment. but if I could "queue" work to be done on an EF context I could probably make that fairly decent as an approach.

Side quest I noticed that "model caching" / using a single model instance for all contexts doesn't work with query filters. Is this a resolvable problem, I'd really like to take advantage of those gains?

TehWardy commented 8 months ago

I thought it best to put this in its own ticket ^