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
- 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);
}