Skip to content

Commit 036d9f7

Browse files
committed
Detach join entity when removing added relationship
Fixes #26779 Also fix related issue where fixup to the join entity was not happening when it was synthesized from navigation change.
1 parent fe1f86d commit 036d9f7

File tree

6 files changed

+122
-12
lines changed

6 files changed

+122
-12
lines changed

src/EFCore/ChangeTracking/Internal/INavigationFixer.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public interface INavigationFixer
2323
/// any release. You should only use it directly in your code with extreme caution and knowing that
2424
/// doing so can result in application failures when updating to a new Entity Framework Core release.
2525
/// </summary>
26-
void BeginAttachGraph();
26+
bool BeginAttachGraph();
2727

2828
/// <summary>
2929
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to

src/EFCore/ChangeTracking/Internal/NavigationFixer.cs

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,17 @@ public NavigationFixer(
4040
/// any release. You should only use it directly in your code with extreme caution and knowing that
4141
/// doing so can result in application failures when updating to a new Entity Framework Core release.
4242
/// </summary>
43-
public virtual void BeginAttachGraph()
43+
public virtual bool BeginAttachGraph()
4444
{
45+
if (_inAttachGraph)
46+
{
47+
return false;
48+
}
49+
4550
_danglingJoinEntities?.Clear();
4651
_inAttachGraph = true;
52+
53+
return true;
4754
}
4855

4956
/// <summary>
@@ -120,6 +127,7 @@ public virtual void NavigationReferenceChanged(
120127
newTargetEntry = null;
121128
}
122129

130+
var delayingFixup = BeginAttachGraph();
123131
try
124132
{
125133
_inFixup = true;
@@ -227,6 +235,11 @@ public virtual void NavigationReferenceChanged(
227235
finally
228236
{
229237
_inFixup = false;
238+
239+
if (delayingFixup)
240+
{
241+
CompleteAttachGraph();
242+
}
230243
}
231244

232245
if (newValue != null
@@ -273,13 +286,18 @@ public virtual void NavigationCollectionChanged(
273286
if (oldTargetEntry != null
274287
&& oldTargetEntry.EntityState != EntityState.Detached)
275288
{
289+
var delayingFixup = BeginAttachGraph();
276290
try
277291
{
278292
_inFixup = true;
279293

280294
if (navigationBase is ISkipNavigation skipNavigation)
281295
{
282-
FindJoinEntry(entry, oldTargetEntry, skipNavigation)?.SetEntityState(EntityState.Deleted);
296+
var joinEntry = FindJoinEntry(entry, oldTargetEntry, skipNavigation);
297+
joinEntry?.SetEntityState(
298+
joinEntry.EntityState == EntityState.Added
299+
? EntityState.Detached
300+
: EntityState.Deleted);
283301

284302
Check.DebugAssert(
285303
skipNavigation.Inverse.IsCollection,
@@ -310,6 +328,11 @@ public virtual void NavigationCollectionChanged(
310328
finally
311329
{
312330
_inFixup = false;
331+
332+
if (delayingFixup)
333+
{
334+
CompleteAttachGraph();
335+
}
313336
}
314337
}
315338
}
@@ -319,6 +342,7 @@ public virtual void NavigationCollectionChanged(
319342
var newTargetEntry = stateManager.GetOrCreateEntry(newValue, targetEntityType);
320343
if (newTargetEntry.EntityState != EntityState.Detached)
321344
{
345+
var delayingFixup = BeginAttachGraph();
322346
try
323347
{
324348
_inFixup = true;
@@ -358,6 +382,11 @@ public virtual void NavigationCollectionChanged(
358382
finally
359383
{
360384
_inFixup = false;
385+
386+
if (delayingFixup)
387+
{
388+
CompleteAttachGraph();
389+
}
361390
}
362391
}
363392
else
@@ -394,6 +423,7 @@ public virtual void KeyPropertyChanged(
394423
return;
395424
}
396425

426+
var delayingFixup = BeginAttachGraph();
397427
try
398428
{
399429
_inFixup = true;
@@ -539,6 +569,11 @@ var targetDependentEntry
539569
finally
540570
{
541571
_inFixup = false;
572+
573+
if (delayingFixup)
574+
{
575+
CompleteAttachGraph();
576+
}
542577
}
543578
}
544579

test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4856,6 +4856,84 @@ static void ValidateFixup(DbContext context, IList<EntityTwo> leftEntities, ILis
48564856
}
48574857
}
48584858

4859+
[ConditionalTheory]
4860+
[InlineData(false, false)]
4861+
[InlineData(false, true)]
4862+
[InlineData(true, false)]
4863+
[InlineData(true, true)]
4864+
public virtual void Can_add_and_remove_a_new_relationship(bool modifyLeft, bool modifyRight)
4865+
{
4866+
int leftId = -1;
4867+
int rightId = -1;
4868+
4869+
ExecuteWithStrategyInTransaction(
4870+
context =>
4871+
{
4872+
var left = context.Set<EntityOne>().Where(e => !e.TwoSkip.Any()).OrderBy(e => e.Id).First();
4873+
var right = context.Set<EntityTwo>().OrderBy(e => e.Id).First();
4874+
4875+
if (modifyLeft)
4876+
{
4877+
context.Entry(left).State = EntityState.Modified;
4878+
}
4879+
4880+
if (modifyRight)
4881+
{
4882+
context.Entry(right).State = EntityState.Modified;
4883+
}
4884+
4885+
leftId = left.Id;
4886+
rightId = right.Id;
4887+
4888+
if (left.TwoSkip == null)
4889+
{
4890+
left.TwoSkip = CreateCollection<EntityTwo>();
4891+
}
4892+
left.TwoSkip.Add(right);
4893+
4894+
if (RequiresDetectChanges)
4895+
{
4896+
context.ChangeTracker.DetectChanges();
4897+
}
4898+
4899+
Assert.Same(right, left.TwoSkip.Single());
4900+
Assert.Same(left, right.OneSkip.Single());
4901+
4902+
var joinEntry = context.ChangeTracker.Entries<JoinOneToTwo>().Single();
4903+
Assert.Equal(EntityState.Added, joinEntry.State);
4904+
Assert.Same(left, joinEntry.Entity.One);
4905+
Assert.Same(right, joinEntry.Entity.Two);
4906+
Assert.Equal(left.Id, joinEntry.Entity.OneId);
4907+
Assert.Equal(right.Id, joinEntry.Entity.TwoId);
4908+
4909+
right.OneSkip.Remove(left);
4910+
4911+
if (RequiresDetectChanges)
4912+
{
4913+
context.ChangeTracker.DetectChanges();
4914+
}
4915+
4916+
Assert.Empty(left.TwoSkip);
4917+
Assert.Empty(right.OneSkip);
4918+
4919+
Assert.Equal(EntityState.Detached, joinEntry.State);
4920+
Assert.Same(left, joinEntry.Entity.One);
4921+
Assert.Same(right, joinEntry.Entity.Two);
4922+
Assert.Equal(leftId, joinEntry.Entity.OneId);
4923+
Assert.Equal(rightId, joinEntry.Entity.TwoId);
4924+
4925+
context.SaveChanges();
4926+
},
4927+
context =>
4928+
{
4929+
var left = context.Set<EntityOne>().Where(e => !e.TwoSkip.Any()).OrderBy(e => e.Id).First();
4930+
var right = context.Set<EntityTwo>().OrderBy(e => e.Id).First();
4931+
4932+
Assert.Equal(leftId, left.Id);
4933+
Assert.Equal(rightId, right.Id);
4934+
});
4935+
}
4936+
48594937
protected static void VerifyRelationshipSnapshots(DbContext context, IEnumerable<object> entities)
48604938
{
48614939
var detectChanges = context.ChangeTracker.AutoDetectChangesEnabled;

test/EFCore.Tests/ChangeTracking/Internal/InternalEntryEntrySubscriberTest.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -504,9 +504,8 @@ private class TestNavigationListener : INavigationFixer
504504
public List<Tuple<InternalEntityEntry, INavigationBase, IEnumerable<object>, IEnumerable<object>>> CollectionChanged { get; }
505505
= new();
506506

507-
public void BeginAttachGraph()
508-
{
509-
}
507+
public bool BeginAttachGraph()
508+
=> false;
510509

511510
public void CompleteAttachGraph()
512511
{

test/EFCore.Tests/ChangeTracking/Internal/StateManagerTest.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -627,9 +627,8 @@ private class TestListener : INavigationFixer
627627
public EntityState ChangingState;
628628
public EntityState ChangedState;
629629

630-
public void BeginAttachGraph()
631-
{
632-
}
630+
public bool BeginAttachGraph()
631+
=> false;
633632

634633
public void CompleteAttachGraph()
635634
{

test/EFCore.Tests/DbContextServicesTest.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -386,9 +386,8 @@ public void StateChanging(InternalEntityEntry entry, EntityState newState)
386386
public void StateChanged(InternalEntityEntry entry, EntityState oldState, bool fromQuery)
387387
=> throw new NotImplementedException();
388388

389-
public void BeginAttachGraph()
390-
{
391-
}
389+
public bool BeginAttachGraph()
390+
=> false;
392391

393392
public void CompleteAttachGraph()
394393
{

0 commit comments

Comments
 (0)