diff --git a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs index 532f84c1eb4..542c37e1299 100644 --- a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs +++ b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs @@ -55,7 +55,8 @@ public virtual InternalEntityEntry PropagateValue(InternalEntityEntry entry, IPr var principalEntry = TryPropagateValue(entry, property); if (principalEntry == null - && property.IsKey()) + && property.IsKey() + && !property.IsForeignKeyToSelf()) { var valueGenerator = TryGetValueGenerator(property); @@ -153,19 +154,23 @@ private static InternalEntityEntry TryPropagateValue(InternalEntityEntry entry, if (principalEntry != null) { var principalProperty = foreignKey.PrincipalKey.Properties[propertyIndex]; - var principalValue = principalEntry[principalProperty]; - if (!principalProperty.ClrType.IsDefaultValue(principalValue)) + + if (principalProperty != property) { - if (principalEntry.HasTemporaryValue(principalProperty)) + var principalValue = principalEntry[principalProperty]; + if (!principalProperty.ClrType.IsDefaultValue(principalValue)) { - entry.SetTemporaryValue(property, principalValue); + if (principalEntry.HasTemporaryValue(principalProperty)) + { + entry.SetTemporaryValue(property, principalValue); + } + else + { + entry[property] = principalValue; + } + + return principalEntry; } - else - { - entry[property] = principalValue; - } - - return principalEntry; } } diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs index 0fad20513cf..f06ef1be478 100644 --- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs @@ -5,7 +5,7 @@ using System.Linq; using System.Runtime.CompilerServices; using JetBrains.Annotations; -using Microsoft.EntityFrameworkCore.ValueGeneration; +using Microsoft.EntityFrameworkCore.Utilities; namespace Microsoft.EntityFrameworkCore.Metadata.Internal { @@ -95,15 +95,39 @@ public static IProperty GetGenerationProperty([NotNull] this IProperty property) } /// - /// Gets a value indicating whether this property requires a to generate - /// values when new entities are added to the context. + /// 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 + /// 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. /// public static bool RequiresValueGenerator([NotNull] this IProperty property) => (property.ValueGenerated.ForAdd() - && !property.IsForeignKey() - && property.IsKey()) + && property.IsKey() + && (!property.IsForeignKey() || property.IsForeignKeyToSelf())) || property.GetValueGeneratorFactory() != null; + /// + /// 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 + /// 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. + /// + public static bool IsForeignKeyToSelf([NotNull] this IProperty property) + { + Check.DebugAssert(property.IsKey(), "Only call this method for properties known to be part of a key."); + + foreach (var foreignKey in property.GetContainingForeignKeys()) + { + var propertyIndex = foreignKey.Properties.IndexOf(property); + if (propertyIndex == foreignKey.PrincipalKey.Properties.IndexOf(property)) + { + return true; + } + } + + return false; + } + /// /// 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 diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs index cfd0f7db27e..3ede68550c4 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; namespace Microsoft.EntityFrameworkCore { @@ -73,6 +74,97 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con } } + public class TptIdentity : GraphUpdatesSqlServerTestBase + { + public TptIdentity(SqlServerFixture fixture) + : base(fixture) + { + } + + [ConditionalFact(Skip = "Issue #22582")] + public override void Can_add_multiple_dependents_when_multiple_possible_principal_sides() + { + } + + [ConditionalFact(Skip = "Issue #22582")] + public override void Can_add_valid_first_dependent_when_multiple_possible_principal_sides() + { + } + + [ConditionalFact(Skip = "Issue #22582")] + public override void Can_add_valid_second_dependent_when_multiple_possible_principal_sides() + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqlServerFixture : GraphUpdatesSqlServerFixtureBase + { + protected override string StoreName { get; } = "GraphTptIdentityUpdatesTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + modelBuilder.UseIdentityColumns(); + + base.OnModelCreating(modelBuilder, context); + + modelBuilder.Entity().ToTable(nameof(Root)); + modelBuilder.Entity().ToTable(nameof(Required1)); + modelBuilder.Entity().ToTable(nameof(Required1Derived)); + modelBuilder.Entity().ToTable(nameof(Required1MoreDerived)); + modelBuilder.Entity().ToTable(nameof(Required2Derived)); + modelBuilder.Entity().ToTable(nameof(Required2MoreDerived)); + modelBuilder.Entity().ToTable(nameof(Optional1)); + modelBuilder.Entity().ToTable(nameof(Optional1Derived)); + modelBuilder.Entity().ToTable(nameof(Optional1MoreDerived)); + modelBuilder.Entity().ToTable(nameof(Optional2Derived)); + modelBuilder.Entity().ToTable(nameof(Optional2MoreDerived)); + modelBuilder.Entity().ToTable(nameof(RequiredSingle1)); + modelBuilder.Entity().ToTable(nameof(OptionalSingle1)); + modelBuilder.Entity().ToTable(nameof(OptionalSingle2)); + modelBuilder.Entity().ToTable(nameof(RequiredNonPkSingle1)); + modelBuilder.Entity().ToTable(nameof(RequiredNonPkSingle2Derived)); + modelBuilder.Entity().ToTable(nameof(RequiredNonPkSingle2MoreDerived)); + modelBuilder.Entity().ToTable(nameof(RequiredAk1)); + modelBuilder.Entity().ToTable(nameof(RequiredAk1Derived)); + modelBuilder.Entity().ToTable(nameof(RequiredAk1MoreDerived)); + modelBuilder.Entity().ToTable(nameof(OptionalAk1)); + modelBuilder.Entity().ToTable(nameof(OptionalAk1Derived)); + modelBuilder.Entity().ToTable(nameof(OptionalAk1MoreDerived)); + modelBuilder.Entity().ToTable(nameof(RequiredSingleAk1)); + modelBuilder.Entity().ToTable(nameof(OptionalSingleAk1)); + modelBuilder.Entity().ToTable(nameof(OptionalSingleAk2Derived)); + modelBuilder.Entity().ToTable(nameof(OptionalSingleAk2MoreDerived)); + modelBuilder.Entity().ToTable(nameof(RequiredNonPkSingleAk1)); + modelBuilder.Entity().ToTable(nameof(RequiredAk2)); + modelBuilder.Entity().ToTable(nameof(RequiredAk2Derived)); + modelBuilder.Entity().ToTable(nameof(RequiredAk2MoreDerived)); + modelBuilder.Entity().ToTable(nameof(OptionalAk2)); + modelBuilder.Entity().ToTable(nameof(OptionalAk2Derived)); + modelBuilder.Entity().ToTable(nameof(OptionalAk2MoreDerived)); + modelBuilder.Entity().ToTable(nameof(RequiredSingleAk2)); + modelBuilder.Entity().ToTable(nameof(RequiredNonPkSingleAk2)); + modelBuilder.Entity().ToTable(nameof(RequiredNonPkSingleAk2Derived)); + modelBuilder.Entity().ToTable(nameof(RequiredNonPkSingleAk2MoreDerived)); + modelBuilder.Entity().ToTable(nameof(OptionalSingleAk2)); + modelBuilder.Entity().ToTable(nameof(RequiredComposite1)); + modelBuilder.Entity().ToTable(nameof(OptionalOverlapping2)); + modelBuilder.Entity().ToTable(nameof(BadCustomer)); + modelBuilder.Entity().ToTable(nameof(BadOrder)); + modelBuilder.Entity().ToTable(nameof(QuestTask)); + modelBuilder.Entity().ToTable(nameof(QuizTask)); + modelBuilder.Entity().ToTable(nameof(HiddenAreaTask)); + modelBuilder.Entity().ToTable(nameof(TaskChoice)); + modelBuilder.Entity().ToTable(nameof(ParentAsAChild)); + modelBuilder.Entity().ToTable(nameof(ChildAsAParent)); + modelBuilder.Entity().ToTable(nameof(Poost)); + modelBuilder.Entity().ToTable(nameof(Bloog)); + modelBuilder.Entity().ToTable(nameof(Produce)); + } + } + } + public class Identity : GraphUpdatesSqlServerTestBase { public Identity(SqlServerFixture fixture)