From fdf3998cca72ace9455b22cfa4541b705a7e3f38 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Fri, 27 Aug 2021 09:41:12 +0100 Subject: [PATCH] Detatching principal keeps track of unreferenced dependents (#25702) --- .../ChangeTracking/Internal/StateManager.cs | 19 +++++- .../ChangeTracking/Internal/FixupTest.cs | 58 +++++++++++++++++++ 2 files changed, 74 insertions(+), 3 deletions(-) diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index 608fbfb4323..292a8b49906 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -587,10 +587,23 @@ public virtual void StopTracking(InternalEntityEntry entry, EntityState oldState _internalEntityEntrySubscriber.Unsubscribe(entry); } - var entityType = entry.EntityType; - - foreach (var key in entityType.GetKeys()) + foreach (var key in entry.EntityType.GetKeys()) { + foreach (var foreignKey in key.GetReferencingForeignKeys()) + { + var dependentToPrincipal = foreignKey.DependentToPrincipal; + if (dependentToPrincipal != null) + { + foreach (InternalEntityEntry dependentEntry in GetDependents(entry, foreignKey)) + { + if (dependentEntry[dependentToPrincipal] == entry.Entity) + { + RecordReferencedUntrackedEntity(entry.Entity, dependentToPrincipal, dependentEntry); + } + } + } + } + FindIdentityMap(key)?.Remove(entry); } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs index 23f926af86d..3c77d68ee77 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs @@ -3281,5 +3281,63 @@ protected internal override void OnModelCreating(ModelBuilder modelBuilder) }); } } + + private class Dependent + { + public int Id { get; set; } + public string Url { get; set; } + public Principal Principal { get; set; } + } + + private class Principal + { + public int Id { get; set; } + public string Title { get; set; } + } + + private class DetachingContext : DbContext + { + public DbSet Principals { get; set; } + public DbSet Dependents { get; set; } + + protected internal override void OnConfiguring(DbContextOptionsBuilder options) + => options + .UseInternalServiceProvider(InMemoryFixture.DefaultServiceProvider) + .UseInMemoryDatabase(nameof(DetachingContext)); + } + + [ConditionalFact] // Issue #21949 + public void Detatching_principal_tracks_unreferenced_foreign_keys() + { + using var context = new DetachingContext(); + + var dependent = new Dependent { Url = "http://myblog.net" }; + var principal = new Principal { Title = "Hello World" }; + dependent.Principal = principal; + context.AddRange(dependent, principal); + + Assert.Equal(EntityState.Added, context.Entry(principal).State); + Assert.Equal(EntityState.Added, context.Entry(dependent).State); + + var principalId = principal.Id; + Assert.Equal(principalId, context.Entry(dependent).Property("PrincipalId").CurrentValue); + Assert.Same(principal, dependent.Principal); + + context.Entry(principal).State = EntityState.Detached; + + Assert.Equal(EntityState.Detached, context.Entry(principal).State); + Assert.Equal(EntityState.Added, context.Entry(dependent).State); + + principal.Id = 0; // So it re-adds + + context.Add(principal); + Assert.NotEqual(principalId, principal.Id); + + Assert.Equal(principal.Id, context.Entry(dependent).Property("PrincipalId").CurrentValue); + Assert.Same(principal, dependent.Principal); + + Assert.Equal(EntityState.Added, context.Entry(principal).State); + Assert.Equal(EntityState.Added, context.Entry(dependent).State); + } } }