Efcore: Adding an entity with specified key followed by one without it leads to an error

Created on 3 Sep 2017  路  6Comments  路  Source: dotnet/efcore

Hey, I've created a SO question for this as well as a reproduction repository.

In short, when setting up minimal EF Core code-first in-memory DB and the doing this:

public void Configure(IApplicationBuilder app, IHostingEnvironment env, EfKeyContext context)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseMvc();
    context.Todos.Add(new Todo() { Id = 1, Name = "First" });
    context.SaveChanges();
    context.Todos.Add(new Todo() { Name = "Seconds" });
    context.SaveChanges();
}

I get this exception:

Unhandled Exception: System.InvalidOperationException: The instance of entity type 'Todo' cannot be tracked because another instance with the key value 'Id:1' is already being
tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Add(TKey key, InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StartTracking(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState oldState, EntityState newState, Boolean acceptChanges)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntry.SetEntityState(EntityState entityState, Boolean acceptChanges, Boolean forceStateWhenUnknownKey)

   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.PaintAction(EntityEntryGraphNode node)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityEntryGraphIterator.TraverseGraph(EntityEntryGraphNode node, Func`2 handleNode)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.EntityGraphAttacher.AttachGraph(InternalEntityEntry rootEntry, EntityState entityState, Boolean forceStateWhenUnknow
nKey)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState(InternalEntityEntry entry, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.SetEntityState[TEntity](TEntity entity, EntityState entityState)
   at Microsoft.EntityFrameworkCore.DbContext.Add[TEntity](TEntity entity)
   at Microsoft.EntityFrameworkCore.Internal.InternalDbSet`1.Add(TEntity entity)
   at ef_key_problem.Startup.Configure(IApplicationBuilder app, IHostingEnvironment env, EfKeyContext context) in C:\Users\Tom\Desktop\ef-key-problem\Startup.cs:line 71
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.AspNetCore.Hosting.ConventionBasedStartup.Configure(IApplicationBuilder app)
   at Microsoft.AspNetCore.Hosting.Internal.AutoRequestServicesStartupFilter.<>c__DisplayClass0_0.<Configure>b__0(IApplicationBuilder builder)
   at Microsoft.AspNetCore.Hosting.Internal.WebHost.BuildApplication()
   at Microsoft.AspNetCore.Hosting.WebHostBuilder.Build()
   at ef_key_problem.Program.BuildWebHost(String[] args) in C:\Users\Tom\Desktop\ef-key-problem\Program.cs:line 21
   at ef_key_problem.Program.Main(String[] args) in C:\Users\Tom\Desktop\ef-key-problem\Program.cs:line 17

I would have assumed the change tracker here would figure out that the first entity was added with a specific key and continue with a subsequent key when adding the other one without it. When I use Attach for the first entity, it doesn't work either, but I think Attach should make no difference anyway, because both of the instances of the Todo class are different.

closed-duplicate

All 6 comments

@TomasHubelbauer When using database-generated keys at the same time as keys chosen by the application it is the responsibility of the application to not choose keys that collide with those generated by the database.

We could make the key generator in the in-memory database smarter, so leaving this open to discuss in triage, but I think even if we decide to do so, it will likely be low priority.

@ajcvickers thank you. One use-case that I think would be well served by a smarter in-memory key generator like this would be seeding the database from a file capturing entities with their keys, which is what I am trying to do.

For anyone experiencing this issue, I recommend you instead materialize your entities and use navigation properties to store the graph at once. You may not get the same IDS, but the relationships should be preserved. This might get a little tricky around cyclic relationships, though.

Triage: we will consider this change at the same time as the other issues around the in-memory key generator, as tracked by #6872

The linked issue unfortunately only provided this behavior for in-memory, from the commit:

This allows seeding with explicit values, followed by normal use with generated values

However my described desired scenario is still not supported: when I seed my database with entities with fixed ID for the change tracker to start assigning IDs that follow in that sequence instead of running into conflicts.

I'm mostly summarizing what happened in the other thread for the sake of having it here, not necessarily arguing for re-opening, because I've since adopted the practice of letting the seed (which is defined in code) has its IDs autogenerated and have related entities pull out the autogenerated ID where they need it or better yet use navigation properties to define relationships so no IDs are passed around, only references.

Although I would like to see the duplicate label removed if possible as this issue is not.

@TomasHubelbauer The scenario above should now be working fine. If the repro in the repostitory linked above is updated to the latest 3.0 builds, does it still fail?

I updated the repository to verify and indeed this now works, which is awesome! Thank you.

Was this page helpful?
0 / 5 - 0 ratings