diff --git a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/CompositePrincipalKeyValueFactory.cs b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/CompositePrincipalKeyValueFactory.cs index e89f50ebe7b..ffa57da426c 100644 --- a/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/CompositePrincipalKeyValueFactory.cs +++ b/src/Microsoft.EntityFrameworkCore/ChangeTracking/Internal/CompositePrincipalKeyValueFactory.cs @@ -86,7 +86,7 @@ public bool Equals(object[] x, object[] y) for (var i = 0; i < x.Length; i++) { - if (!x[i].Equals(y[i])) + if (!Equals(x[i], y[i])) { return false; } diff --git a/test/Microsoft.EntityFrameworkCore.Tests/DbContextTest.cs b/test/Microsoft.EntityFrameworkCore.Tests/DbContextTest.cs index 328e87628f1..7e273be3f8b 100644 --- a/test/Microsoft.EntityFrameworkCore.Tests/DbContextTest.cs +++ b/test/Microsoft.EntityFrameworkCore.Tests/DbContextTest.cs @@ -5011,5 +5011,99 @@ public void Dispose() } } } + + [Fact] + public void Adding_entities_with_shadow_keys_should_not_throw() + { + using (var context = new NullShadowKeyContext()) + { + var assembly = new TestAssembly { Name = "Assembly1" }; + var testClass = new TestClass { Assembly = assembly, Name = "Class1" }; + var test = context.Tests.Add(new Test { Class = testClass, Name = "Test1" }).Entity; + + context.SaveChanges(); + + ValidateGraph(context, assembly, testClass, test); + } + + using (var context = new NullShadowKeyContext()) + { + var test = context.Tests.Single(); + var assembly = context.Assemblies.Single(); + var testClass = context.Classes.Single(); + + ValidateGraph(context, assembly, testClass, test); + } + } + + private static void ValidateGraph(NullShadowKeyContext context, TestAssembly assembly, TestClass testClass, Test test) + { + Assert.Equal(EntityState.Unchanged, context.Entry(assembly).State); + Assert.Equal("Assembly1", assembly.Name); + Assert.Same(testClass, test.Class); + + Assert.Equal(EntityState.Unchanged, context.Entry(testClass).State); + Assert.Equal("Class1", testClass.Name); + Assert.Equal("Assembly1", context.Entry(testClass).Property("AssemblyName").CurrentValue); + Assert.Same(test, testClass.Tests.Single()); + Assert.Same(assembly, testClass.Assembly); + + Assert.Equal(EntityState.Unchanged, context.Entry(test).State); + Assert.Equal("Test1", test.Name); + Assert.Equal("Assembly1", context.Entry(test).Property("AssemblyName").CurrentValue); + Assert.Equal("Class1", context.Entry(test).Property("ClassName").CurrentValue); + Assert.Same(testClass, assembly.Classes.Single()); + } + + private class TestAssembly + { + [System.ComponentModel.DataAnnotations.Key] + public string Name { get; set; } + public ICollection Classes { get; } = new List(); + } + + private class TestClass + { + public TestAssembly Assembly { get; set; } + public string Name { get; set; } + public ICollection Tests { get; } = new List(); + } + + private class Test + { + public TestClass Class { get; set; } + public string Name { get; set; } + } + + private class NullShadowKeyContext : DbContext + { + public DbSet Assemblies { get; set; } + public DbSet Classes { get; set; } + public DbSet Tests { get; set; } + + protected internal override void OnConfiguring(DbContextOptionsBuilder options) + => options.UseInMemoryDatabase(); + + protected internal override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.Entity( + x => + { + x.Property("AssemblyName"); + x.HasKey("AssemblyName", nameof(TestClass.Name)); + x.HasOne(c => c.Assembly).WithMany(a => a.Classes) + .HasForeignKey("AssemblyName"); + }); + + modelBuilder.Entity( + x => + { + x.Property("AssemblyName"); + x.HasKey("AssemblyName", "ClassName", nameof(Test.Name)); + x.HasOne(t => t.Class).WithMany(c => c.Tests) + .HasForeignKey("AssemblyName", "ClassName"); + }); + } + } } }