diff --git a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs index 034a7b89895..e501f6aea1a 100644 --- a/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs +++ b/src/EFCore.Relational/Update/Internal/CommandBatchPreparer.cs @@ -6,9 +6,11 @@ using System.Linq; using System.Text; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; using Microsoft.Extensions.DependencyInjection; @@ -290,9 +292,8 @@ private void AddUnchangedSharingEntries( // 2. Commands deleting rows or modifying the foreign key values must precede // commands deleting rows or modifying the candidate key values (when supported) of rows // that are currently being referenced by the former - // 3. Commands deleting rows or modifying the foreign key values must precede - // commands adding or modifying the foreign key values to the same values - // if foreign key is unique + // 3. Commands deleting rows or modifying unique constraint values must precede + // commands adding or modifying unique constraint values to the same values /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -570,6 +571,12 @@ private void AddForeignKeyEdges( { foreach (var command in commandGraph.Vertices) { + if (command.Predecessor != null) + { + // This is usually an implicit relationship between TPT rows for the same entity + commandGraph.AddEdge(command.Predecessor, command, null); + } + switch (command.EntityState) { case EntityState.Modified: @@ -638,7 +645,7 @@ private static void AddMatchingPredecessorEdge( IKeyValueIndex dependentKeyValue, Multigraph commandGraph, ModificationCommand command, - IForeignKey foreignKey) + IAnnotatable edge) { if (predecessorsMap.TryGetValue(dependentKeyValue, out var predecessorCommands)) { @@ -646,7 +653,7 @@ private static void AddMatchingPredecessorEdge( { if (predecessor != command) { - commandGraph.AddEdge(predecessor, command, foreignKey); + commandGraph.AddEdge(predecessor, command, edge); } } } @@ -654,14 +661,10 @@ private static void AddMatchingPredecessorEdge( private void AddUniqueValueEdges(Multigraph commandGraph) { - Dictionary> predecessorsMap = null; + Dictionary> indexPredecessorsMap = null; + var keyPredecessorsMap = new Dictionary>(); foreach (var command in commandGraph.Vertices) { - if (command.Predecessor != null) - { - commandGraph.AddEdge(command.Predecessor, command, null); - } - if (command.EntityState != EntityState.Modified && command.EntityState != EntityState.Deleted) { @@ -682,11 +685,11 @@ private void AddUniqueValueEdges(Multigraph c var valueFactory = index.GetNullableValueFactory(); if (valueFactory.TryCreateFromOriginalValues(entry, out var indexValue)) { - predecessorsMap ??= new Dictionary>(); - if (!predecessorsMap.TryGetValue(index, out var predecessorCommands)) + indexPredecessorsMap ??= new Dictionary>(); + if (!indexPredecessorsMap.TryGetValue(index, out var predecessorCommands)) { predecessorCommands = new Dictionary(valueFactory.EqualityComparer); - predecessorsMap.Add(index, predecessorCommands); + indexPredecessorsMap.Add(index, predecessorCommands); } if (!predecessorCommands.ContainsKey(indexValue)) @@ -695,19 +698,41 @@ private void AddUniqueValueEdges(Multigraph c } } } - } - } - if (predecessorsMap == null) - { - return; + if (command.EntityState != EntityState.Deleted) + { + continue; + } + + foreach (var key in entry.EntityType.GetKeys().Where(k => k.GetMappedConstraints().Any())) + { + var principalKeyValue = _keyValueIndexFactorySource + .GetKeyValueIndexFactory(key) + .CreatePrincipalKeyValue(entry, null); + + if (principalKeyValue != null) + { + if (!keyPredecessorsMap.TryGetValue(principalKeyValue, out var predecessorCommands)) + { + predecessorCommands = new List(); + keyPredecessorsMap.Add(principalKeyValue, predecessorCommands); + } + + predecessorCommands.Add(command); + } + } + } } - foreach (var command in commandGraph.Vertices) + if (indexPredecessorsMap != null) { - if (command.EntityState == EntityState.Modified - || command.EntityState == EntityState.Added) + foreach (var command in commandGraph.Vertices) { + if (command.EntityState == EntityState.Deleted) + { + continue; + } + foreach (var entry in command.Entries) { foreach (var index in entry.EntityType.GetIndexes().Where(i => i.IsUnique && i.GetMappedTableIndexes().Any())) @@ -720,7 +745,7 @@ private void AddUniqueValueEdges(Multigraph c var valueFactory = index.GetNullableValueFactory(); if (valueFactory.TryCreateFromCurrentValues(entry, out var indexValue) - && predecessorsMap.TryGetValue(index, out var predecessorCommands) + && indexPredecessorsMap.TryGetValue(index, out var predecessorCommands) && predecessorCommands.TryGetValue(indexValue, out var predecessor) && predecessor != command) { @@ -730,6 +755,33 @@ private void AddUniqueValueEdges(Multigraph c } } } + + if (keyPredecessorsMap != null) + { + foreach (var command in commandGraph.Vertices) + { + if (command.EntityState != EntityState.Added) + { + continue; + } + + foreach (var entry in command.Entries) + { + foreach (var key in entry.EntityType.GetKeys().Where(k => k.GetMappedConstraints().Any())) + { + var principalKeyValue = _keyValueIndexFactorySource + .GetKeyValueIndexFactory(key) + .CreatePrincipalKeyValue(entry, null); + + if (principalKeyValue != null) + { + AddMatchingPredecessorEdge( + keyPredecessorsMap, principalKeyValue, commandGraph, command, key); + } + } + } + } + } } } } diff --git a/src/EFCore.Relational/Update/Internal/IKeyValueIndexFactory.cs b/src/EFCore.Relational/Update/Internal/IKeyValueIndexFactory.cs index 65259493c70..79dd6f88f8b 100644 --- a/src/EFCore.Relational/Update/Internal/IKeyValueIndexFactory.cs +++ b/src/EFCore.Relational/Update/Internal/IKeyValueIndexFactory.cs @@ -20,7 +20,7 @@ public interface IKeyValueIndexFactory /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IKeyValueIndex CreatePrincipalKeyValue([NotNull] IUpdateEntry entry, [NotNull] IForeignKey foreignKey); + IKeyValueIndex CreatePrincipalKeyValue([NotNull] IUpdateEntry entry, [CanBeNull] IForeignKey foreignKey); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -28,7 +28,7 @@ public interface IKeyValueIndexFactory /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - IKeyValueIndex CreatePrincipalKeyValueFromOriginalValues([NotNull] IUpdateEntry entry, [NotNull] IForeignKey foreignKey); + IKeyValueIndex CreatePrincipalKeyValueFromOriginalValues([NotNull] IUpdateEntry entry, [CanBeNull] IForeignKey foreignKey); /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore.Relational/Update/Internal/KeyValueIndex.cs b/src/EFCore.Relational/Update/Internal/KeyValueIndex.cs index e158995c455..6d9163e14e5 100644 --- a/src/EFCore.Relational/Update/Internal/KeyValueIndex.cs +++ b/src/EFCore.Relational/Update/Internal/KeyValueIndex.cs @@ -28,7 +28,7 @@ public sealed class KeyValueIndex : IKeyValueIndex /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public KeyValueIndex( - [NotNull] IForeignKey foreignKey, + [CanBeNull] IForeignKey foreignKey, [NotNull] TKey keyValue, [NotNull] IEqualityComparer keyComparer, bool fromOriginalValues) diff --git a/src/EFCore/ChangeTracking/Internal/IdentityMap.cs b/src/EFCore/ChangeTracking/Internal/IdentityMap.cs index a7f1f2cc19e..94f4f1b233c 100644 --- a/src/EFCore/ChangeTracking/Internal/IdentityMap.cs +++ b/src/EFCore/ChangeTracking/Internal/IdentityMap.cs @@ -289,7 +289,8 @@ private void Add(TKey key, InternalEntityEntry entry, bool updateDuplicate) } } - if (!bothStatesEquivalent) + if (!bothStatesEquivalent + && Key.IsPrimaryKey()) { entry.SharedIdentityEntry = existingEntry; existingEntry.SharedIdentityEntry = entry; @@ -413,7 +414,11 @@ protected virtual void Remove([NotNull] TKey key, [NotNull] InternalEntityEntry if (otherEntry == null) { - _identityMap.Remove(key); + if (_identityMap.TryGetValue(key, out var existingEntry) + && existingEntry == entry) + { + _identityMap.Remove(key); + } } else { diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToManyAk.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToManyAk.cs index 95c805982d6..14883cfc3df 100644 --- a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToManyAk.cs +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToManyAk.cs @@ -327,6 +327,13 @@ public virtual void Save_required_many_to_one_dependents_with_alternate_key( new1d.Parent = null; new1dd.Parent = null; + var old1 = root.RequiredChildrenAk.OrderBy(c => c.Id).Last(); + + // Test replacing entity with the same AK, but different PK + new1.AlternateId = old1.AlternateId; + context.Remove(old1); + context.RemoveRange(old1.Children); + context.RemoveRange(old1.CompositeChildren); context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); }