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

Adding multiple entities to a join table with a composite key throws #9962

Closed
pariesz opened this issue Oct 4, 2017 · 3 comments
Closed
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@pariesz
Copy link

pariesz commented Oct 4, 2017

Note: The exception doesn't occur if both if both properties in the composite key are different.

Exception message:
System.ArgumentNullException : Value cannot be null.
Parameter name: key

Stack trace:
   at System.ThrowHelper.ThrowArgumentNullException(ExceptionArgument argument)
   at System.Collections.Generic.Dictionary`2.Remove(TKey key)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Remove(TKey key, InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.IdentityMap`1.Remove(InternalEntityEntry entry)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.StateManager.StopTracking(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.NavigationFixer.ConditionallyNullForeignKeyProperties(InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.ToDependentFixup(InternalEntityEntry dependentEntry, InternalEntityEntry principalEntry, IForeignKey foreignKey)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.InitialFixup(InternalEntityEntry entry, ISet`1 handledForeignKeys, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.NavigationFixer.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery)
   at Microsoft.EntityFrameworkCore.ChangeTracking.Internal.InternalEntityEntryNotifier.StateChanged(InternalEntityEntry entry, EntityState oldState, Boolean fromQuery)
   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 forceStateWhenUnknownKey)
   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 Tests.EntityFrameworkBug.Composite_Key_bug() in C:\Sites\CentralApi\Services\Core.Tests\EntityFrameworkBug.cs:line 19

Steps to reproduce

using Microsoft.EntityFrameworkCore;
using Xunit;

namespace Tests {
    public class EntityFrameworkBug {
        [Fact]
        public void Composite_Key_bug() {
            var opts = new DbContextOptionsBuilder<TestContext>().UseInMemoryDatabase("test").Options;
            using(var db = new TestContext(opts)) {
                var a = new AEntity();
                db.AEntities.Add(a);

                var b1 = new BEntity();
                var b2 = new BEntity();
                db.BEntities.AddRange(b1, b2);
                db.SaveChanges();

                db.EntityJoins.Add(new JoinEntity { EntityA = a, EntityB = b1 });
                db.EntityJoins.Add(new JoinEntity { EntityA = a, EntityB = b2 });
            }
        }

        public class AEntity {
            public int Id { get; set; }
        }
        public class BEntity {
            public int Id { get; set; }
        }
        public class JoinEntity {
            public int EntityAId { get; set; }
            public int EntityBId { get; set; }
            public virtual AEntity EntityA { get; set; }
            public virtual BEntity EntityB { get; set; }
        }
        public class TestContext : DbContext {
            public DbSet<AEntity> AEntities { get; set; }
            public DbSet<BEntity> BEntities { get; set; }
            public DbSet<JoinEntity> EntityJoins { get; set; }

            public TestContext(DbContextOptions<TestContext> options) : base(options) { }

            protected override void OnModelCreating(ModelBuilder builder) {
                builder
                    .Entity<AEntity>(c => { c.HasKey(x => x.Id); })
                    .Entity<BEntity>(c => { c.HasKey(x => x.Id); })
                    .Entity<JoinEntity>(c => {
                        c.HasKey(x => new { x.EntityAId, x.EntityBId });
                        c.HasOne(x => x.EntityA).WithOne().HasForeignKey<JoinEntity>(x => x.EntityAId);
                        c.HasOne(x => x.EntityB).WithOne().HasForeignKey<JoinEntity>(x => x.EntityBId);
                    });
            }
        }
    }
}

Further technical details

EF Core version: 2.0.0
Database Provider: In Memory
Operating system: Windows 10 Pro
IDE: Visual Studio Community 2017 Version 15.3.5

@ajcvickers
Copy link
Member

@pariesz This is not a normal mapping for a "join table". It is instead two 1:1 relationships:

  • AEntity (principal) maps to JoinEntity (dependent) using unique FK EntityAId
  • BEntity (principal) maps to JoinEntity (dependent) using unique FK EntityBId

This means that EntityBId is being used as a unique FK on it's own--that is, not part of the composite--such that adding to entities with the same EntityBId will fail. It shouldn't throw the exception you are seeing--that is a bug, but it should fail at least when saving to the database.

Did you intend to setup a join table like that used in a many-to-many relationship? If so, then try this mapping:

c.HasOne(x => x.EntityA).WithMany().HasForeignKey(x => x.EntityAId);
c.HasOne(x => x.EntityB).WithMany().HasForeignKey(x => x.EntityBId);

@ajcvickers ajcvickers self-assigned this Oct 4, 2017
@ajcvickers ajcvickers added this to the 2.1.0 milestone Oct 4, 2017
@pariesz
Copy link
Author

pariesz commented Oct 5, 2017

Thanks Arthur for looking at that so quickly!

I had configured the relationships badly, using WithMany() fixed it. I haven't closed the issue as you said you want to work on a clearer exception.

@ajcvickers ajcvickers modified the milestones: 2.1.0-preview1, 2.1.0 Jan 17, 2018
@divega divega modified the milestones: 2.1.0-preview2, 2.1.0 Apr 2, 2018
@ajcvickers
Copy link
Member

No longer throws bad exception in preview2 bits.

@ajcvickers ajcvickers added closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. and removed poachable labels Apr 8, 2018
@ajcvickers ajcvickers modified the milestones: 2.1.0, 2.1.0-preview2 Apr 8, 2018
@ajcvickers ajcvickers modified the milestones: 2.1.0-preview2, 2.1.0 Nov 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

3 participants