diff --git a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs index d3bd5a49c98..4de01863479 100644 --- a/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs +++ b/src/EFCore.SqlServer/Extensions/SqlServerPropertyExtensions.cs @@ -375,6 +375,7 @@ public static SqlServerValueGenerationStrategy GetValueGenerationStrategy( { return sharedTableRootProperty.GetValueGenerationStrategy(storeObject) == SqlServerValueGenerationStrategy.IdentityColumn + && !property.GetContainingForeignKeys().Any(fk => !fk.IsBaseLinking()) ? SqlServerValueGenerationStrategy.IdentityColumn : SqlServerValueGenerationStrategy.None; } diff --git a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs index ac529646b01..39a0886f218 100644 --- a/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs +++ b/src/EFCore.SqlServer/Infrastructure/Internal/SqlServerModelValidator.cs @@ -291,63 +291,77 @@ protected override void ValidateCompatible( var duplicatePropertyStrategy = duplicateProperty.GetValueGenerationStrategy(storeObject); if (propertyStrategy != duplicatePropertyStrategy) { - throw new InvalidOperationException( - SqlServerStrings.DuplicateColumnNameValueGenerationStrategyMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), - duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), - property.Name, - columnName, - storeObject.DisplayName())); + var isConflicting = ((IConventionProperty)property) + .FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy) + ?.GetConfigurationSource() == ConfigurationSource.Explicit + || propertyStrategy != SqlServerValueGenerationStrategy.None; + var isDuplicateConflicting = ((IConventionProperty)duplicateProperty) + .FindAnnotation(SqlServerAnnotationNames.ValueGenerationStrategy) + ?.GetConfigurationSource() == ConfigurationSource.Explicit + || duplicatePropertyStrategy != SqlServerValueGenerationStrategy.None; + + if (isConflicting && isDuplicateConflicting) + { + throw new InvalidOperationException( + SqlServerStrings.DuplicateColumnNameValueGenerationStrategyMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + storeObject.DisplayName())); + } } - - switch (propertyStrategy) + else { - case SqlServerValueGenerationStrategy.IdentityColumn: - var increment = property.GetIdentityIncrement(storeObject); - var duplicateIncrement = duplicateProperty.GetIdentityIncrement(storeObject); - if (increment != duplicateIncrement) - { - throw new InvalidOperationException( - SqlServerStrings.DuplicateColumnIdentityIncrementMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), - duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), - property.Name, - columnName, - storeObject.DisplayName())); - } - - var seed = property.GetIdentitySeed(storeObject); - var duplicateSeed = duplicateProperty.GetIdentitySeed(storeObject); - if (seed != duplicateSeed) - { - throw new InvalidOperationException( - SqlServerStrings.DuplicateColumnIdentitySeedMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), - duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), - property.Name, - columnName, - storeObject.DisplayName())); - } - - break; - case SqlServerValueGenerationStrategy.SequenceHiLo: - if (property.GetHiLoSequenceName(storeObject) != duplicateProperty.GetHiLoSequenceName(storeObject) - || property.GetHiLoSequenceSchema(storeObject) != duplicateProperty.GetHiLoSequenceSchema(storeObject)) - { - throw new InvalidOperationException( - SqlServerStrings.DuplicateColumnSequenceMismatch( - duplicateProperty.DeclaringEntityType.DisplayName(), - duplicateProperty.Name, - property.DeclaringEntityType.DisplayName(), - property.Name, - columnName, - storeObject.DisplayName())); - } - - break; + switch (propertyStrategy) + { + case SqlServerValueGenerationStrategy.IdentityColumn: + var increment = property.GetIdentityIncrement(storeObject); + var duplicateIncrement = duplicateProperty.GetIdentityIncrement(storeObject); + if (increment != duplicateIncrement) + { + throw new InvalidOperationException( + SqlServerStrings.DuplicateColumnIdentityIncrementMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + storeObject.DisplayName())); + } + + var seed = property.GetIdentitySeed(storeObject); + var duplicateSeed = duplicateProperty.GetIdentitySeed(storeObject); + if (seed != duplicateSeed) + { + throw new InvalidOperationException( + SqlServerStrings.DuplicateColumnIdentitySeedMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + storeObject.DisplayName())); + } + + break; + case SqlServerValueGenerationStrategy.SequenceHiLo: + if (property.GetHiLoSequenceName(storeObject) != duplicateProperty.GetHiLoSequenceName(storeObject) + || property.GetHiLoSequenceSchema(storeObject) != duplicateProperty.GetHiLoSequenceSchema(storeObject)) + { + throw new InvalidOperationException( + SqlServerStrings.DuplicateColumnSequenceMismatch( + duplicateProperty.DeclaringEntityType.DisplayName(), + duplicateProperty.Name, + property.DeclaringEntityType.DisplayName(), + property.Name, + columnName, + storeObject.DisplayName())); + } + + break; + } } } diff --git a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs index f04e841c716..91e862b2d89 100644 --- a/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs +++ b/src/EFCore/ChangeTracking/Internal/ChangeDetector.cs @@ -370,20 +370,16 @@ private void DetectNavigationChange(InternalEntityEntry entry, INavigationBase n Check.DebugAssert(navigationBase is INavigation, "Issue #21673. Non-collection skip navigations not supported."); var navigation = (INavigation)navigationBase; - if (!navigation.ForeignKey.IsOwnership - || !navigation.IsOnDependent) + if (_loggingOptions.IsSensitiveDataLoggingEnabled) { - if (_loggingOptions.IsSensitiveDataLoggingEnabled) - { - _logger.ReferenceChangeDetectedSensitive(entry, navigation, snapshotValue, currentValue); - } - else - { - _logger.ReferenceChangeDetected(entry, navigation, snapshotValue, currentValue); - } - - stateManager.InternalEntityEntryNotifier.NavigationReferenceChanged(entry, navigation, snapshotValue, currentValue); + _logger.ReferenceChangeDetectedSensitive(entry, navigation, snapshotValue, currentValue); + } + else + { + _logger.ReferenceChangeDetected(entry, navigation, snapshotValue, currentValue); } + + stateManager.InternalEntityEntryNotifier.NavigationReferenceChanged(entry, navigation, snapshotValue, currentValue); } } } diff --git a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs index c4f7c191434..0a1d9339115 100644 --- a/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs +++ b/test/EFCore.Design.Tests/Migrations/ModelSnapshotSqlServerTest.cs @@ -1575,9 +1575,7 @@ public virtual void Shared_columns_are_stored_in_the_snapshot() modelBuilder.Entity(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", b => { b.Property(""Id"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b.Property(""AlternateId"") .ValueGeneratedOnUpdateSometimes() @@ -1838,9 +1836,7 @@ public virtual void Owned_types_are_stored_in_snapshot() b.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", ""EntityWithTwoProperties"", b1 => { b1.Property(""AlternateId"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b1.Property(""EntityWithStringKeyId"") .HasColumnType(""nvarchar(450)""); @@ -2063,9 +2059,7 @@ public virtual void Owned_types_are_stored_in_snapshot_when_excluded() b.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+EntityWithTwoProperties"", ""EntityWithTwoProperties"", b1 => { b1.Property(""AlternateId"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b1.Property(""EntityWithStringKeyId"") .HasColumnType(""nvarchar(450)""); @@ -2236,9 +2230,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+OrderDetails"", ""OrderBillingDetails"", b1 => { b1.Property(""OrderId"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b1.HasKey(""OrderId""); @@ -2250,9 +2242,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b1.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+StreetAddress"", ""StreetAddress"", b2 => { b2.Property(""OrderDetailsOrderId"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b2.Property(""City"") .HasColumnType(""nvarchar(max)""); @@ -2271,9 +2261,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+OrderDetails"", ""OrderShippingDetails"", b1 => { b1.Property(""OrderId"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b1.HasKey(""OrderId""); @@ -2285,9 +2273,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b1.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+StreetAddress"", ""StreetAddress"", b2 => { b2.Property(""OrderDetailsOrderId"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b2.Property(""City"") .HasColumnType(""nvarchar(max)""); @@ -2306,9 +2292,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+OrderInfo"", ""OrderInfo"", b1 => { b1.Property(""OrderId"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b1.HasKey(""OrderId""); @@ -2320,9 +2304,7 @@ public virtual void Weak_owned_types_are_stored_in_snapshot() b1.OwnsOne(""Microsoft.EntityFrameworkCore.Migrations.ModelSnapshotSqlServerTest+StreetAddress"", ""StreetAddress"", b2 => { b2.Property(""OrderInfoOrderId"") - .ValueGeneratedOnAdd() - .HasColumnType(""int"") - .UseIdentityColumn(); + .HasColumnType(""int""); b2.Property(""City"") .HasColumnType(""nvarchar(max)""); diff --git a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs index 0c5e0b07dcd..d417c37a1bf 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NullSemanticsQueryTestBase.cs @@ -723,9 +723,9 @@ public virtual void Where_contains_on_parameter_empty_array_with_relational_null var names = new string[0]; var result = context.Entities1 .Where(e => names.Contains(e.NullableStringA)) - .Select(e => e.NullableStringA).ToList(); + .Select(e => e.NullableStringA).ToList().Count; - Assert.Empty(result); + Assert.Equal(0, result); } [ConditionalFact] @@ -735,9 +735,9 @@ public virtual void Where_contains_on_parameter_array_with_just_null_with_relati var names = new string[] { null }; var result = context.Entities1 .Where(e => names.Contains(e.NullableStringA)) - .Select(e => e.NullableStringA).ToList(); + .Select(e => e.NullableStringA).ToList().Count; - Assert.Empty(result); + Assert.Equal(0, result); } [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs index abbffc620e6..6696a32b8ea 100644 --- a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs @@ -1413,6 +1413,7 @@ public override int GetHashCode() protected class RequiredSingle1 : NotifyingEntity { private int _id; + private bool _bool; private Root _root; private RequiredSingle2 _single; @@ -1422,6 +1423,12 @@ public int Id set => SetWithNotify(value, ref _id); } + public bool Bool + { + get => _bool; + set => SetWithNotify(value, ref _bool); + } + public Root Root { get => _root; @@ -1447,6 +1454,7 @@ public override int GetHashCode() protected class RequiredSingle2 : NotifyingEntity { private int _id; + private bool _bool; private RequiredSingle1 _back; public int Id @@ -1455,6 +1463,12 @@ public int Id set => SetWithNotify(value, ref _id); } + public bool Bool + { + get => _bool; + set => SetWithNotify(value, ref _bool); + } + public RequiredSingle1 Back { get => _back; diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOne.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOne.cs index bf866b41d5d..20904ced441 100644 --- a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOne.cs +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOne.cs @@ -1224,7 +1224,11 @@ public virtual void Sever_required_one_to_one( Assert.False(context.ChangeTracker.HasChanges()); Assert.Null(old1.Root); - Assert.Null(old2.Back); + if (!context.Entry(old2).Metadata.IsOwned()) + { + // Navigations to owners are preserved when these are owned + Assert.Null(old2.Back); + } Assert.Equal(old1.Id, old2.Id); } }, @@ -1238,8 +1242,17 @@ public virtual void Sever_required_one_to_one( AssertKeys(root, loadedRoot); AssertPossiblyNullNavigations(loadedRoot); - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingle).Count(e => e.Id == old1.Id); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingle != null)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingle).Select(r => r.Single) + // .Count(e => e.Id == old2.Id); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingle).Any(r => r.Single != null)); } }); } @@ -1654,9 +1667,6 @@ public virtual void Required_one_to_one_are_cascade_deleted( Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Same(root, removed.Root); Assert.Same(orphaned, removed.Single); } @@ -1670,8 +1680,17 @@ public virtual void Required_one_to_one_are_cascade_deleted( Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingle).Count(e => e.Id == removedId); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingle != null)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingle).Select(r => r.Single) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingle).Any(r => r.Single != null)); } }); } @@ -1722,7 +1741,6 @@ public virtual void Required_one_to_one_leaf_can_be_deleted( Assert.Equal(EntityState.Detached, context.Entry(removed).State); Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); Assert.Same(parent, removed.Back); }, context => @@ -1731,7 +1749,13 @@ public virtual void Required_one_to_one_leaf_can_be_deleted( var parent = root.RequiredSingle; Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingle).Select(r => r.Single) + // .Count(e => e.Id == removedId); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingle).Any(r => r.Single != null)); }); } @@ -1868,7 +1892,6 @@ public virtual void Required_non_PK_one_to_one_leaf_can_be_deleted( Assert.Equal(EntityState.Detached, context.Entry(removed).State); Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); Assert.Same(parent, removed.Back); }, context => @@ -1941,9 +1964,6 @@ public virtual void Required_one_to_one_are_cascade_deleted_in_store( Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Same(root, removed.Root); Assert.Same(orphaned, removed.Single); } @@ -2127,8 +2147,17 @@ public virtual void Required_one_to_one_are_cascade_deleted_starting_detached( { Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingle).Count(e => e.Id == removedId); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingle != null)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingle).Select(r => r.Single) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingle).Any(r => r.Single != null)); } }); } @@ -2317,8 +2346,17 @@ public virtual void Required_one_to_one_are_cascade_detached_when_Added( Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingle).Count(e => e.Id == removedId); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingle != null)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingle).Select(r => r.Single) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingle).Any(r => r.Single != null)); } }); } diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOneAk.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOneAk.cs index cfd1576336e..bd34aff62f6 100644 --- a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOneAk.cs +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOneAk.cs @@ -1045,9 +1045,15 @@ public virtual void Save_required_one_to_one_changed_by_reference_with_alternate if (useExistingEntities) { - new1 = context.Set().Single(e => e.Id == new1.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2c = context.Set().Single(e => e.Id == new2c.Id); + newRoot = context.Set() + .Include(e => e.RequiredSingleAk).ThenInclude(e => e.Single) + .Include(e => e.RequiredSingleAk).ThenInclude(e => e.SingleComposite) + .OrderBy(e => e.Id) + .Single(e => e.Id == newRoot.Id); + + new1 = newRoot.RequiredSingleAk; + new2 = new1.Single; + new2c = new1.SingleComposite; } else { @@ -1106,13 +1112,24 @@ public virtual void Save_required_one_to_one_changed_by_reference_with_alternate Assert.Same(new1, new2.Back); Assert.Same(new1, new2c.Back); + Assert.Equal(EntityState.Detached, context.Entry(old1).State); + Assert.Equal(EntityState.Detached, context.Entry(old2).State); + Assert.Equal(EntityState.Detached, context.Entry(old2c).State); Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Null(old2c.Back); + if (!context.Entry(old2).Metadata.IsOwned()) + { + // Navigations to owners are preserved when these are owned + Assert.Null(old2.Back); + Assert.Null(old2c.Back); + } + Assert.NotEqual(new1.Id, old1.Id); + Assert.NotEqual(new2.Id, old2.Id); + Assert.NotEqual(new2c.Id, old2c.Id); Assert.Equal(old1.AlternateId, old2.BackId); Assert.Equal(old1.Id, old2c.BackId); Assert.Equal(old1.AlternateId, old2c.BackAlternateId); + context.Entry(newRoot).State = EntityState.Detached; entries = context.ChangeTracker.Entries().ToList(); } }, @@ -1128,9 +1145,25 @@ public virtual void Save_required_one_to_one_changed_by_reference_with_alternate AssertKeys(root, loadedRoot); AssertNavigations(loadedRoot); - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2c.Id)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingleAk).Count(e => e.Id == old1.Id); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingleAk != null && r.RequiredSingleAk.Id == old1.Id)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.Single) + // .Count(e => e.Id == old2.Id); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk) + .Any(r => r.Single != null && r.Single.Id == old2.Id)); + + //var orphanedCCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.SingleComposite) + // .Count(e => e.Id == old2c.Id); + //Assert.Equal(0, orphanedCCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk) + .Any(r => r.SingleComposite != null && r.SingleComposite.Id == old2c.Id)); } }); } @@ -1457,8 +1490,12 @@ public virtual void Sever_required_one_to_one_with_alternate_key( Assert.False(context.ChangeTracker.HasChanges()); Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Null(old2c.Back); + if (!context.Entry(old2).Metadata.IsOwned()) + { + // Navigations to owners are preserved when these are owned + Assert.Null(old2.Back); + Assert.Null(old2c.Back); + } Assert.Equal(old1.AlternateId, old2.BackId); Assert.Equal(old1.Id, old2c.BackId); Assert.Equal(old1.AlternateId, old2c.BackAlternateId); @@ -1475,9 +1512,25 @@ public virtual void Sever_required_one_to_one_with_alternate_key( AssertKeys(root, loadedRoot); AssertPossiblyNullNavigations(loadedRoot); - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2c.Id)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingleAk).Count(e => e.Id == old1.Id); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingleAk != null && r.RequiredSingleAk.Id == old1.Id)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.Single) + // .Count(e => e.Id == old2.Id); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk) + .Any(r => r.Single != null && r.Single.Id == old2.Id)); + + //var orphanedCCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.SingleComposite) + // .Count(e => e.Id == old2c.Id); + //Assert.Equal(0, orphanedCCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk) + .Any(r => r.SingleComposite != null && r.SingleComposite.Id == old2c.Id)); } }); } @@ -1711,10 +1764,18 @@ public virtual void Reparent_required_one_to_one_with_alternate_key( AssertKeys(root, loadedRoot); AssertPossiblyNullNavigations(loadedRoot); - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + newRoot = context.Set() + .Include(r => r.RequiredSingleAk.Single) + .Include(r => r.RequiredSingleAk.SingleComposite) + .Single(e => e.Id == newRoot.Id); + + var loaded1 = newRoot.RequiredSingleAk; + var loaded2 = loaded1.Single; + var loaded2c = loaded1.SingleComposite; + + Assert.Equal(old1.Id, loaded1.Id); + Assert.Equal(old2.Id, loaded2.Id); + Assert.Equal(old2c.Id, loaded2c.Id); Assert.Same(newRoot, loaded1.Root); Assert.Same(loaded1, loaded2.Back); @@ -1939,10 +2000,6 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted( Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - Assert.Same(root, removed.Root); Assert.Same(orphaned, removed.Single); } @@ -1956,9 +2013,23 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted( Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingleAk).Count(e => e.Id == removedId); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingleAk != null)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.Single) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk).Any(r => r.Single != null)); + + //var orphanedCCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.SingleComposite) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk).Any(r => r.SingleComposite != null)); } }); } @@ -2112,10 +2183,6 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_i Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - Assert.Same(root, removed.Root); Assert.Same(orphaned, removed.Single); } @@ -2307,9 +2374,23 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_s Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingleAk).Count(e => e.Id == removedId); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingleAk != null)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.Single) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk).Any(r => r.Single != null)); + + //var orphanedCCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.SingleComposite) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk).Any(r => r.SingleComposite != null)); } }); } @@ -2510,9 +2591,23 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_detached_ Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + //TODO: Aggregate on optional dependent #23230 + //var removedCount = context.Set().Select(r => r.RequiredSingleAk).Count(e => e.Id == removedId); + //Assert.Equal(0, removedCount); + + Assert.False(context.Set().Any(r => r.RequiredSingleAk != null)); + + //var orphanedCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.Single) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk).Any(r => r.Single != null)); + + //var orphanedCCount = context.Set().Select(r => r.RequiredSingleAk).Select(r => r.SingleComposite) + // .Count(e => e.Id == orphanedId); + //Assert.Equal(0, orphanedCCount); + + Assert.False(context.Set().Select(r => r.RequiredSingleAk).Any(r => r.SingleComposite != null)); } }); } diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs index b5dde9e5085..3d1529d6b7e 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs @@ -2,7 +2,9 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -192,6 +194,419 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con } } + public class Owned : GraphUpdatesSqlServerTestBase + { + public Owned(SqlServerFixture fixture) + : base(fixture) + { + } + + // Owned dependents are always loaded + public override void Required_one_to_one_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) + { + } + + + public override void Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) + { + } + + public override void Required_one_to_one_relationships_are_one_to_one(CascadeTiming? deleteOrphansTiming) + { + } + + public override void Required_one_to_one_with_AK_relationships_are_one_to_one(CascadeTiming? deleteOrphansTiming) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqlServerFixture : GraphUpdatesSqlServerFixtureBase + { + protected override string StoreName { get; } = "GraphOwnedUpdatesTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + modelBuilder.Entity( + b => + { + b.Property(e => e.AlternateId).ValueGeneratedOnAdd(); + + // TODO: Owned inheritance support #9630 + b.HasMany(e => e.RequiredChildren) + .WithOne(e => e.Parent) + .HasForeignKey(e => e.ParentId); + + modelBuilder.Entity() + .HasMany(e => e.Children) + .WithOne(e => e.Parent) + .HasForeignKey(e => e.ParentId); + + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + + b.HasMany(e => e.OptionalChildren) + .WithOne(e => e.Parent) + .HasForeignKey(e => e.ParentId) + .OnDelete(DeleteBehavior.SetNull); + + b.OwnsOne(e => e.RequiredSingle, r => + { + r.WithOwner(e => e.Root) + .HasForeignKey(e => e.Id); + + r.OwnsOne(e => e.Single) + .WithOwner(e => e.Back) + .HasForeignKey(e => e.Id); + }); + + b.HasOne(e => e.OptionalSingle) + .WithOne(e => e.Root) + .HasForeignKey(e => e.RootId) + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne(e => e.OptionalSingleDerived) + .WithOne(e => e.DerivedRoot) + .HasForeignKey(e => e.DerivedRootId) + .OnDelete(DeleteBehavior.ClientSetNull); + + b.HasOne(e => e.OptionalSingleMoreDerived) + .WithOne(e => e.MoreDerivedRoot) + .HasForeignKey(e => e.MoreDerivedRootId) + .OnDelete(DeleteBehavior.ClientSetNull); + + // TODO: Owned inheritance support #9630 + b.HasOne(e => e.RequiredNonPkSingle) + .WithOne(e => e.Root) + .HasForeignKey(e => e.RootId); + + b.HasOne(e => e.RequiredNonPkSingleDerived) + .WithOne(e => e.DerivedRoot) + .HasForeignKey(e => e.DerivedRootId) + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne(e => e.RequiredNonPkSingleMoreDerived) + .WithOne(e => e.MoreDerivedRoot) + .HasForeignKey(e => e.MoreDerivedRootId) + .OnDelete(DeleteBehavior.Restrict); + + // TODO: Owned inheritance support #9630 + b.HasMany(e => e.RequiredChildrenAk) + .WithOne(e => e.Parent) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.ParentId); + + modelBuilder.Entity( + b => + { + b.Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + b.HasMany(e => e.Children) + .WithOne(e => e.Parent) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.ParentId); + + b.HasMany(e => e.CompositeChildren) + .WithOne(e => e.Parent) + .HasPrincipalKey( + e => new { e.Id, e.AlternateId }) + .HasForeignKey( + e => new { e.ParentId, e.ParentAlternateId }); + }); + + modelBuilder.Entity() + .Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + b.HasMany(e => e.OptionalChildrenAk) + .WithOne(e => e.Parent) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.ParentId) + .OnDelete(DeleteBehavior.SetNull); + + b.OwnsOne(e => e.RequiredSingleAk, r => + { + r.WithOwner(e => e.Root) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.RootId); + + r.HasKey(e => e.Id); + + r.Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + r.OwnsOne(e => e.Single, r2 => + { + r2.WithOwner(e => e.Back) + .HasForeignKey(e => e.BackId) + .HasPrincipalKey(e => e.AlternateId); + + r2.HasKey(e => e.Id); + + r2.Property(e => e.Id) + .ValueGeneratedOnAdd(); + + r2.Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + r2.ToTable("RequiredSingleAk2"); + }); + + r.OwnsOne(e => e.SingleComposite, r2 => + { + r2.WithOwner(e => e.Back) + .HasForeignKey(e => new { e.BackId, e.BackAlternateId }) + .HasPrincipalKey(e => new { e.Id, e.AlternateId }); + + r2.HasKey(e => e.Id); + + r2.ToTable("RequiredSingleComposite2"); + }); + + // Table splitting using AK is not supported #23208 + r.ToTable("RequiredSingleAk1"); + }); + + b.HasOne(e => e.OptionalSingleAk) + .WithOne(e => e.Root) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.RootId) + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne(e => e.OptionalSingleAkDerived) + .WithOne(e => e.DerivedRoot) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.DerivedRootId) + .OnDelete(DeleteBehavior.ClientSetNull); + + b.HasOne(e => e.OptionalSingleAkMoreDerived) + .WithOne(e => e.MoreDerivedRoot) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.MoreDerivedRootId) + .OnDelete(DeleteBehavior.ClientSetNull); + + b.HasOne(e => e.RequiredNonPkSingleAk) + .WithOne(e => e.Root) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.RootId); + + b.HasOne(e => e.RequiredNonPkSingleAkDerived) + .WithOne(e => e.DerivedRoot) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.DerivedRootId) + .OnDelete(DeleteBehavior.Restrict); + + b.HasOne(e => e.RequiredNonPkSingleAkMoreDerived) + .WithOne(e => e.MoreDerivedRoot) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.MoreDerivedRootId) + .OnDelete(DeleteBehavior.Restrict); + + b.HasMany(e => e.RequiredCompositeChildren) + .WithOne(e => e.Parent) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.ParentAlternateId); + }); + + modelBuilder.Entity( + b => + { + b.HasMany(e => e.Children) + .WithOne(e => e.Parent) + .HasForeignKey(e => e.ParentId) + .OnDelete(DeleteBehavior.SetNull); + + b.HasMany(e => e.CompositeChildren) + .WithOne(e => e.Parent2) + .HasForeignKey(e => new { e.Parent2Id }); + }); + + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity() + .HasOne(e => e.Single) + .WithOne(e => e.Back) + .HasForeignKey(e => e.BackId) + .OnDelete(DeleteBehavior.SetNull); + + modelBuilder.Entity( + b => + { + b.HasDiscriminator(e => e.Disc) + .HasValue(new MyDiscriminator(1)) + .HasValue(new MyDiscriminator(2)) + .HasValue(new MyDiscriminator(3)); + + b.Property(e => e.Disc) + .HasConversion( + v => v.Value, + v => new MyDiscriminator(v), + new ValueComparer( + (l, r) => l.Value == r.Value, + v => v.Value.GetHashCode(), + v => new MyDiscriminator(v.Value))) + .Metadata + .SetAfterSaveBehavior(PropertySaveBehavior.Save); + }); + + modelBuilder.Entity() + .HasOne(e => e.Single) + .WithOne(e => e.Back) + .HasForeignKey(e => e.BackId); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity( + b => + { + b.Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + b.HasOne(e => e.Single) + .WithOne(e => e.Back) + .HasForeignKey(e => e.BackId) + .HasPrincipalKey(e => e.AlternateId); + }); + + modelBuilder.Entity( + b => + { + b.Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + b.HasMany(e => e.Children) + .WithOne(e => e.Parent) + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.ParentId) + .OnDelete(DeleteBehavior.SetNull); + + b.HasMany(e => e.CompositeChildren) + .WithOne(e => e.Parent) + .HasPrincipalKey( + e => new { e.Id, e.AlternateId }) + .HasForeignKey( + e => new { e.ParentId, e.ParentAlternateId }); + }); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity() + .Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity() + .Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity( + b => + { + b.Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + b.HasOne(e => e.Single) + .WithOne(e => e.Back) + .HasForeignKey(e => e.BackId) + .HasPrincipalKey(e => e.AlternateId) + .OnDelete(DeleteBehavior.SetNull); + + b.HasOne(e => e.SingleComposite) + .WithOne(e => e.Back) + .HasForeignKey( + e => new { e.BackId, e.ParentAlternateId }) + .HasPrincipalKey( + e => new { e.Id, e.AlternateId }); + }); + + modelBuilder.Entity() + .Property(e => e.AlternateId) + .ValueGeneratedOnAdd(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity( + eb => + { + eb.Property(e => e.Id).ValueGeneratedNever(); + + eb.HasKey( + e => new { e.Id, e.ParentAlternateId }); + + eb.HasMany(e => e.CompositeChildren) + .WithOne(e => e.Parent) + .HasPrincipalKey( + e => new { e.Id, e.ParentAlternateId }) + .HasForeignKey( + e => new { e.ParentId, e.ParentAlternateId }); + }); + + modelBuilder.Entity( + eb => + { + eb.Property(e => e.Id).ValueGeneratedNever(); + + eb.HasKey( + e => new { e.Id, e.ParentAlternateId }); + + eb.HasOne(e => e.Root) + .WithMany() + .HasPrincipalKey(e => e.AlternateId) + .HasForeignKey(e => e.ParentAlternateId); + }); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity(); + + modelBuilder.Entity() + .HasMany(qt => qt.Choices) + .WithOne() + .HasForeignKey(tc => tc.QuestTaskId); + + modelBuilder.Entity() + .HasMany(hat => hat.Choices) + .WithOne() + .HasForeignKey(tc => tc.QuestTaskId); + + modelBuilder.Entity(); + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity(); + modelBuilder.Entity(); + + modelBuilder.Entity() + .HasIndex(e => e.BarCode) + .IsUnique(); + } + } + } + public abstract class GraphUpdatesSqlServerTestBase : GraphUpdatesTestBase where TFixture : GraphUpdatesSqlServerTestBase.GraphUpdatesSqlServerFixtureBase, new() { diff --git a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs index 210bc52b1f4..5644face999 100644 --- a/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs +++ b/test/EFCore.SqlServer.Tests/Infrastructure/SqlServerModelValidatorTest.cs @@ -204,7 +204,7 @@ public virtual void Detects_duplicate_column_names_within_hierarchy_with_differe modelBuilder.Entity( db => { - db.Property(d => d.Identity).ValueGeneratedNever().HasColumnName(nameof(Dog.Identity)); + db.Property(d => d.Identity).UseHiLo().HasColumnName(nameof(Dog.Identity)); }); VerifyError( diff --git a/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs index cbfe5b43024..3d923f4a913 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs @@ -2845,6 +2845,13 @@ public void Parent_and_identity_changed_bidirectional(EntityState entityState) principal2.Child1 = dependent; principal1.Child2 = null; + if (entityState != EntityState.Added) + { + Assert.Equal(CoreStrings.KeyReadOnly("ParentId", "Parent.Child2#Child"), + Assert.Throws(() => context.ChangeTracker.DetectChanges()).Message); + return; + } + context.ChangeTracker.DetectChanges(); Assert.True(context.ChangeTracker.HasChanges()); @@ -3043,6 +3050,13 @@ public void Parent_and_identity_changed_bidirectional_collection(EntityState ent principal2.ChildCollection1 = principal1.ChildCollection2; principal1.ChildCollection2 = null; + if (entityState != EntityState.Added) + { + Assert.Equal(CoreStrings.KeyReadOnly("ParentId", "Parent.ChildCollection2#Child"), + Assert.Throws(() => context.ChangeTracker.DetectChanges()).Message); + return; + } + context.ChangeTracker.DetectChanges(); Assert.True(context.ChangeTracker.HasChanges()); @@ -3212,6 +3226,13 @@ public void Parent_and_identity_swapped_bidirectional(EntityState entityState) principal2.Child1 = dependent1; principal1.Child2 = dependent2; + if (entityState != EntityState.Added) + { + Assert.Equal(CoreStrings.KeyReadOnly("ParentId", "Parent.Child2#Child"), + Assert.Throws(() => context.ChangeTracker.DetectChanges()).Message); + return; + } + context.ChangeTracker.DetectChanges(); Assert.True(context.ChangeTracker.HasChanges()); @@ -3500,6 +3521,13 @@ public void Parent_and_identity_swapped_bidirectional_collection(EntityState ent .FindEntry(subDependent2); newSubDependentEntry2.Property("Id").CurrentValue = subDependentEntry2.Property("Id").CurrentValue; + if (entityState != EntityState.Added) + { + Assert.Equal(CoreStrings.KeyReadOnly("ParentId", "Parent.ChildCollection2#Child"), + Assert.Throws(() => context.ChangeTracker.DetectChanges()).Message); + return; + } + context.ChangeTracker.DetectChanges(); Assert.True(context.ChangeTracker.HasChanges());