Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cannot save changes because same join entity seems to be added multiple times #23339

Closed
xamadev opened this issue Nov 15, 2020 · 12 comments · Fixed by #24572
Closed

Cannot save changes because same join entity seems to be added multiple times #23339

xamadev opened this issue Nov 15, 2020 · 12 comments · Fixed by #24572
Labels
area-change-tracking closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Milestone

Comments

@xamadev
Copy link

xamadev commented Nov 15, 2020

I'm buliding a mobile app which syncs it's local SQLite database with a SQL database on a server through a REST API. I'm getting an exception when saving the received data. I set up a small reproduction repo.

My first intention was that JSON.NET creates new objects for the same entity while parsing the json if it occurs multiple times. But JSON.NET does not like described here. Besides the entity UnitToElement with {FK_Element: 2, FK_Unit: 1} occurs only once in the json string.

Stack trace

System.InvalidOperationException: 'The instance of entity type 'UnitToElement' cannot be tracked because another instance with the key value '{FK_Element: 2, FK_Unit: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'

Include provider and version information

EF Core version: 5.0
Database provider: Microsoft.EntityFrameworkCore.SqlServer ,Microsoft.EntityFrameworkCore.Sqlite
Target framework: .NET Core 3.1

@ajcvickers
Copy link
Member

ajcvickers commented Nov 19, 2020

@xamadev I am unable to reproduce this. When I run your code I get a stack overflow. Is there something else needed to get this to reproduce?

@ajcvickers
Copy link
Member

@xamadev Scratch that, I had the wrong project open. 🤦

@ajcvickers
Copy link
Member

Note for triage: I am able to reproduce this, but I haven't yet figured out what is going wrong.

@ajcvickers
Copy link
Member

Note for triage: figured out what is going on here. The issue occurs when attaching a graph of entities that includes some join entities referenced not by the many-to-many relationship, but rather by some other relationship on the join entity. If EF encounters the many-to-many relationship first, then it synthesizes a join entity. This then clashes with the entity in the graph when it is found later.

The solution to this is probably to move the join entity synthesis to the delayed-fixup stage, at which point we will have encountered all entities in the graph. It might be possible to patch for this, but the change may also be too big/risky to patch.

@xamadev A workaround in this case is to make sure that the explicit join entities in the graph are attached first. Something like this:

context.AddRange(dataContextDto.Contacts.SelectMany(e => e.Responsibilities));

context.Units.AddRange(dataContextDto.Units);
context.Contacts.AddRange(dataContextDto.Contacts);
context.Elements.AddRange(dataContextDto.Elements);

context.SaveChanges();

@xamadev
Copy link
Author

xamadev commented Nov 24, 2020

@ajcvickers Thanks for the workaround!

@xamadev
Copy link
Author

xamadev commented Nov 25, 2020

@ajcvickers Unfortunately I'm facing same error with the workaround in my production project. I added some new relationships in my reproduction repo:

contacts[0].Responsibilities.Add(unitsToElements[3]);
contacts[1].Responsibilities.Add(unitsToElements[0]);
contacts[1].Responsibilities.Add(unitsToElements[1]);

I added Distinct() to avoid adding same entities multiple times which are referenced from more than one contact:

context.UnitsToElements.AddRange(dataContextDto.Contacts.SelectMany(e => e.Responsibilities).Distinct());

Stack Trace:

System.InvalidOperationException: 'The instance of entity type 'UnitToElement' cannot be tracked because another instance with the key value '{FK_Element: 1, FK_Unit: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.'

Could you please have a look?

@ajcvickers
Copy link
Member

@xamadev Can you update the code in the reproduction repo with the new model, etc. and I will take a look. I have half an idea for a more general workaround that I can flesh out a bit more with good repro code to test it on.

@xamadev
Copy link
Author

xamadev commented Nov 25, 2020

@ajcvickers It's already up to date

@xamadev
Copy link
Author

xamadev commented Nov 25, 2020

@ajcvickers I made a mistake in reproduction repo (is up to date). It's working there but not in production. I'm currently trying to reproduce it

@xamadev
Copy link
Author

xamadev commented Nov 25, 2020

@ajcvickers Could reproduce the issue, please have a look at my repo.

@ajcvickers
Copy link
Member

@xamadev Try this. The idea is to explicitly add any join entities before adding the other entities. This solution traverses the graph to find them, rather than having to extract them manually from the graph, like the previous solution was attempting to do.

private static void PreAddJoinEntities<TEntity>(DbContext context, IEnumerable<TEntity> entities)
{
    var tracked = new HashSet<object>();
    
    foreach (var unit in entities)
    {
        context.ChangeTracker.TrackGraph(unit, 0, n =>
        {
            var entity = n.Entry.Entity;
            
            if (tracked.Contains(entity))
            {
                return false;
            }
            
            if (entity is UnitToElement
                && n.Entry.State == EntityState.Detached)
            {
                n.Entry.State = EntityState.Added;
            }

            tracked.Add(entity);

            return true;
        });
    }
}

Used like so:

using (var context = GetContextSqlite())
{
    context.Database.EnsureDeleted();
    context.Database.EnsureCreated();

    PreAddJoinEntities(context, dataContextDto.Units);
    PreAddJoinEntities(context, dataContextDto.Functions);
    PreAddJoinEntities(context, dataContextDto.Groups);
    PreAddJoinEntities(context, dataContextDto.Elements);
    PreAddJoinEntities(context, dataContextDto.Contacts);

    context.Units.AddRange(dataContextDto.Units);
    context.Functions.AddRange(dataContextDto.Functions);
    context.Groups.AddRange(dataContextDto.Groups);
    context.Elements.AddRange(dataContextDto.Elements);
    context.Contacts.AddRange(dataContextDto.Contacts);

    context.SaveChanges();
}

@xamadev
Copy link
Author

xamadev commented Nov 26, 2020

@ajcvickers Works fine in reproduction and production project, Thanks for your time!

ajcvickers added a commit that referenced this issue Apr 2, 2021
Fixes #23339

This stops the change tracker creating a join entity instance for the same join entity that is later discovered in the graph.
@ajcvickers ajcvickers modified the milestones: 6.0.0, 6.0.0-preview4 Apr 2, 2021
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Apr 2, 2021
ajcvickers added a commit that referenced this issue Apr 2, 2021
Fixes #23339

This stops the change tracker creating a join entity instance for the same join entity that is later discovered in the graph.
ajcvickers added a commit that referenced this issue Apr 3, 2021
Fixes #23339

This stops the change tracker creating a join entity instance for the same join entity that is later discovered in the graph.
ajcvickers added a commit that referenced this issue Apr 3, 2021
Fixes #23339

This stops the change tracker creating a join entity instance for the same join entity that is later discovered in the graph.
@ajcvickers ajcvickers modified the milestones: 6.0.0-preview4, 6.0.0 Nov 8, 2021
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-change-tracking closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-bug
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants