From 4eb188289561b7069cc001a1f9c1c4b973c18d9c Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Tue, 26 Sep 2017 12:47:33 -0700 Subject: [PATCH] Add change tracking support for shadow navigations to non-shadow entity types Part of #749 --- src/EFCore/ChangeTracking/CollectionEntry.cs | 3 +- .../EmptyShadowValuesFactoryFactory.cs | 2 +- .../Internal/InternalEntityEntry.cs | 49 +- .../Internal/InternalEntityEntrySubscriber.cs | 10 +- .../Internal/InternalMixedEntityEntry.cs | 73 + .../Internal/InternalShadowEntityEntry.cs | 55 + .../ChangeTracking/Internal/KeyPropagator.cs | 5 +- .../Internal/NavigationFixer.cs | 140 +- .../Internal/OriginalValuesFactoryFactory.cs | 2 +- .../Internal/RelationshipsSnapshot.cs | 3 +- .../Internal/ShadowValuesFactoryFactory.cs | 2 +- .../Internal/SnapshotFactoryFactory.cs | 4 +- .../ChangeTracking/Internal/StateManager.cs | 1 - src/EFCore/Infrastructure/ModelValidator.cs | 4 +- .../Internal/NonCapturingLazyInitializer.cs | 20 + ...onAccessor.cs => ClrCollectionAccessor.cs} | 11 +- .../Internal/ClrCollectionAccessorFactory.cs | 5 +- src/EFCore/Metadata/Internal/EntityType.cs | 11 - .../Metadata/Internal/EntityTypeExtensions.cs | 78 +- .../Internal/IClrCollectionAccessor.cs | 7 +- .../Internal/InternalRelationshipBuilder.cs | 26 +- src/EFCore/Metadata/Internal/Navigation.cs | 36 +- src/EFCore/Metadata/Internal/Property.cs | 29 - src/EFCore/Metadata/Internal/PropertyBase.cs | 31 +- .../Internal/PropertyBaseExtensions.cs | 90 +- .../Metadata/Internal/PropertyExtensions.cs | 10 +- ...RelationalMetadataBuilderExtensionsTest.cs | 6 + .../ChangeTracking/ChangeTrackerTest.cs | 19 +- .../Internal/FixupCompositeTest.cs | 4 + .../ChangeTracking/Internal/FixupTest.cs | 2879 +++++------------ .../Internal/InternalEntityEntryTestBase.cs | 15 +- .../ChangeTracking/Internal/OwnedFixupTest.cs | 8 + .../ChangeTracking/Internal/QueryFixupTest.cs | 1 + .../Internal/ShadowFixupTest.cs | 348 ++ .../Internal/ShadowFkFixupTest.cs | 7 + .../ClrCollectionAccessorFactoryTest.cs | 7 +- .../Metadata/Internal/EntityTypeTest.cs | 127 +- 37 files changed, 1558 insertions(+), 2570 deletions(-) rename src/EFCore/Metadata/Internal/{ClrICollectionAccessor.cs => ClrCollectionAccessor.cs} (95%) create mode 100644 test/EFCore.Tests/ChangeTracking/Internal/ShadowFixupTest.cs diff --git a/src/EFCore/ChangeTracking/CollectionEntry.cs b/src/EFCore/ChangeTracking/CollectionEntry.cs index b23008af667..248930c099b 100644 --- a/src/EFCore/ChangeTracking/CollectionEntry.cs +++ b/src/EFCore/ChangeTracking/CollectionEntry.cs @@ -8,7 +8,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.ChangeTracking { @@ -117,6 +116,6 @@ public override IQueryable Query() /// directly from your code. This API may change or be removed in future releases. /// protected virtual void EnsureInitialized() - => Metadata.GetCollectionAccessor().GetOrCreate(InternalEntry.Entity); + => InternalEntry.GetOrCreateCollection(Metadata); } } diff --git a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs index d5b7b87f62d..46bf613fa1b 100644 --- a/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/EmptyShadowValuesFactoryFactory.cs @@ -18,7 +18,7 @@ public class EmptyShadowValuesFactoryFactory : SnapshotFactoryFactory /// directly from your code. This API may change or be removed in future releases. /// protected override int GetPropertyIndex(IPropertyBase propertyBase) - => (propertyBase as IProperty)?.GetShadowIndex() ?? -1; + => propertyBase.GetShadowIndex(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 93a482cbbf4..e94653e347b 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -1,7 +1,8 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; using System.Collections.Specialized; using System.ComponentModel; @@ -522,6 +523,48 @@ protected virtual void WritePropertyValue([NotNull] IPropertyBase propertyBase, propertyBase.GetSetter().SetClrValue(Entity, value); } + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual IEnumerable GetOrCreateCollection([NotNull] INavigation navigation) + { + return navigation.GetCollectionAccessor().GetOrCreate(Entity); + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual bool CollectionContains([NotNull] INavigation navigation, [NotNull] InternalEntityEntry value) + { + Debug.Assert(!navigation.IsShadowProperty); + + return navigation.GetCollectionAccessor().Contains(Entity, value.Entity); + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual bool AddToCollection([NotNull] INavigation navigation, [NotNull] InternalEntityEntry value) + { + Debug.Assert(!navigation.IsShadowProperty); + + return navigation.GetCollectionAccessor().Add(Entity, value.Entity); + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual void RemoveFromCollection([NotNull] INavigation navigation, [NotNull] InternalEntityEntry value) + { + Debug.Assert(!navigation.IsShadowProperty); + + navigation.GetCollectionAccessor().Remove(Entity, value.Entity); + } + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -673,9 +716,7 @@ public virtual object this[[NotNull] IPropertyBase propertyBase] { get { - return propertyBase is INavigation && propertyBase.IsShadowProperty // Remove when issue #749 is fixed - ? null - : _storeGeneratedValues.TryGetValue(propertyBase, out var value) + return _storeGeneratedValues.TryGetValue(propertyBase, out var value) ? value : ReadPropertyValue(propertyBase); } diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs index 28238935487..e9c8973aa34 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; +// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { /// @@ -90,8 +91,7 @@ private static INotifyCollectionChanged AsINotifyCollectionChanged( IEntityType entityType, ChangeTrackingStrategy changeTrackingStrategy) { - var notifyingCollection = navigation.GetCollectionAccessor().GetOrCreate(entry.Entity) as INotifyCollectionChanged; - if (notifyingCollection == null) + if (!(navigation.GetCollectionAccessor()?.GetOrCreate(entry.Entity) is INotifyCollectionChanged notifyingCollection)) { throw new InvalidOperationException( CoreStrings.NonNotifyingCollection(navigation.Name, entityType.DisplayName(), changeTrackingStrategy)); @@ -105,8 +105,7 @@ private static INotifyPropertyChanged AsINotifyPropertyChanged( IEntityType entityType, ChangeTrackingStrategy changeTrackingStrategy) { - var changed = entry.Entity as INotifyPropertyChanged; - if (changed == null) + if (!(entry.Entity is INotifyPropertyChanged changed)) { throw new InvalidOperationException( CoreStrings.ChangeTrackingInterfaceMissing( @@ -121,8 +120,7 @@ private static INotifyPropertyChanging AsINotifyPropertyChanging( IEntityType entityType, ChangeTrackingStrategy changeTrackingStrategy) { - var changing = entry.Entity as INotifyPropertyChanging; - if (changing == null) + if (!(entry.Entity is INotifyPropertyChanging changing)) { throw new InvalidOperationException( CoreStrings.ChangeTrackingInterfaceMissing( diff --git a/src/EFCore/ChangeTracking/Internal/InternalMixedEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalMixedEntityEntry.cs index 5e0fbc9f26f..f861dce1854 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalMixedEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalMixedEntityEntry.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections; +using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -85,5 +87,76 @@ protected override void WritePropertyValue(IPropertyBase propertyBase, object va _shadowValues[propertyBase.GetShadowIndex()] = value; } } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override IEnumerable GetOrCreateCollection(INavigation navigation) + => navigation.IsShadowProperty + ? GetOrCreateCollectionTyped(navigation) + : base.GetOrCreateCollection(navigation); + + private ICollection GetOrCreateCollectionTyped(INavigation navigation) + { + if (!(_shadowValues[navigation.GetShadowIndex()] is ICollection collection)) + { + collection = new HashSet(); + _shadowValues[navigation.GetShadowIndex()] = collection; + } + + return collection; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override bool CollectionContains(INavigation navigation, InternalEntityEntry value) + => navigation.IsShadowProperty + ? GetOrCreateCollectionTyped(navigation).Contains(value.Entity) + : base.CollectionContains(navigation, value); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override bool AddToCollection(INavigation navigation, InternalEntityEntry value) + { + if (!navigation.IsShadowProperty) + { + return base.AddToCollection(navigation, value); + } + + if (navigation.GetTargetType().ClrType == null) + { + return false; + } + + var collection = GetOrCreateCollectionTyped(navigation); + if (!collection.Contains(value.Entity)) + { + collection.Add(value.Entity); + return true; + } + + return false; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override void RemoveFromCollection(INavigation navigation, InternalEntityEntry value) + { + if (navigation.IsShadowProperty) + { + GetOrCreateCollectionTyped(navigation).Remove(value.Entity); + } + else + { + base.RemoveFromCollection(navigation, value); + } + } } } diff --git a/src/EFCore/ChangeTracking/Internal/InternalShadowEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalShadowEntityEntry.cs index 91fe187f61b..ece1ac4dcee 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalShadowEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalShadowEntityEntry.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections; +using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -70,5 +72,58 @@ protected override object ReadPropertyValue(IPropertyBase propertyBase) /// protected override void WritePropertyValue(IPropertyBase propertyBase, object value) => _propertyValues[propertyBase.GetShadowIndex()] = value; + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override IEnumerable GetOrCreateCollection(INavigation navigation) + => GetOrCreateCollectionTyped(navigation); + + private ICollection GetOrCreateCollectionTyped(INavigation navigation) + { + if (!(_propertyValues[navigation.GetShadowIndex()] is ICollection collection)) + { + collection = new HashSet(); + _propertyValues[navigation.GetShadowIndex()] = collection; + } + + return collection; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override bool CollectionContains(INavigation navigation, InternalEntityEntry value) + => GetOrCreateCollectionTyped(navigation).Contains(value.Entity); + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override bool AddToCollection(INavigation navigation, InternalEntityEntry value) + { + if (navigation.GetTargetType().ClrType == null) + { + return false; + } + + var collection = GetOrCreateCollectionTyped(navigation); + if (!collection.Contains(value.Entity)) + { + collection.Add(value.Entity); + return true; + } + + return false; + } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public override void RemoveFromCollection(INavigation navigation, InternalEntityEntry value) + => GetOrCreateCollectionTyped(navigation).Remove(value.Entity); } } diff --git a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs index fd19925f5db..57e14fa1030 100644 --- a/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs +++ b/src/EFCore/ChangeTracking/Internal/KeyPropagator.cs @@ -100,10 +100,9 @@ private static InternalEntityEntry TryPropagateValue(InternalEntityEntry entry, { if (property == foreignKey.Properties[propertyIndex]) { - // Remove when issue #749 is fixed - var principal = foreignKey.DependentToPrincipal?.IsShadowProperty ?? true + var principal = foreignKey.DependentToPrincipal == null ? null - : foreignKey.DependentToPrincipal.GetGetter().GetClrValue(entry.Entity); + : entry[foreignKey.DependentToPrincipal]; InternalEntityEntry principalEntry = null; if (principal != null) { diff --git a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs index 3292e54bf41..f4c87bfd843 100644 --- a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs +++ b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs @@ -7,7 +7,6 @@ using System.Linq; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { @@ -102,26 +101,18 @@ public virtual void NavigationReferenceChanged(InternalEntityEntry entry, INavig if (inverse != null) { - var collectionAccessor = inverse.IsCollection() ? inverse.GetCollectionAccessor() : null; - // Set the inverse reference or add the entity to the inverse collection if (newTargetEntry != null) { - SetReferenceOrAddToCollection(newTargetEntry, inverse, collectionAccessor, entry.Entity); + SetReferenceOrAddToCollection(newTargetEntry, inverse, entry); } // Remove the entity from the old collection, or null the old inverse unless it was already // changed to point to something else - if (oldTargetEntry != null) + if (oldTargetEntry != null + && oldTargetEntry.EntityState != EntityState.Deleted) { - if (collectionAccessor != null) - { - RemoveFromCollection(oldTargetEntry, inverse, collectionAccessor, entry.Entity); - } - else if (ReferenceEquals(oldTargetEntry[inverse], entry.Entity)) - { - SetNavigation(oldTargetEntry, inverse, null); - } + ResetReferenceOrRemoveCollection(oldTargetEntry, inverse, entry); } } } @@ -144,7 +135,7 @@ public virtual void NavigationReferenceChanged(InternalEntityEntry entry, INavig SetForeignKeyProperties(newTargetEntry, entry, foreignKey, setModified: true); - SetNavigation(newTargetEntry, inverse, entry.Entity); + SetNavigation(newTargetEntry, inverse, entry); } if (oldTargetEntry != null) @@ -205,7 +196,6 @@ public virtual void NavigationCollectionChanged( var foreignKey = navigation.ForeignKey; var stateManager = entry.StateManager; var inverse = navigation.FindInverse(); - var collectionAccessor = navigation.GetCollectionAccessor(); foreach (var oldValue in removed) { @@ -253,14 +243,14 @@ public virtual void NavigationCollectionChanged( if (oldPrincipalEntry != null && oldPrincipalEntry != entry) { - RemoveFromCollection(oldPrincipalEntry, navigation, collectionAccessor, newValue); + RemoveFromCollection(oldPrincipalEntry, navigation, newTargetEntry); } // Set the FK properties on added dependents to match this principal SetForeignKeyProperties(newTargetEntry, entry, foreignKey, setModified: true); // Set the inverse navigation to point to this principal - SetNavigation(newTargetEntry, inverse, entry.Entity); + SetNavigation(newTargetEntry, inverse, entry); } finally { @@ -309,31 +299,19 @@ public virtual void KeyPropertyChanged( var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { - // Remove when issue #749 is fixed - var collectionAccessor = principalToDependent.IsCollection() && !principalToDependent.IsShadowProperty - ? principalToDependent.GetCollectionAccessor() - : null; - if (oldPrincipalEntry != null && oldPrincipalEntry.EntityState != EntityState.Deleted) { // Remove this entity from the principal collection that it was previously part of, // or null the navigation for a 1:1 unless that reference was already changed. - if (collectionAccessor != null) - { - RemoveFromCollection(oldPrincipalEntry, principalToDependent, collectionAccessor, entry.Entity); - } - else if (ReferenceEquals(oldPrincipalEntry[principalToDependent], entry.Entity)) - { - SetNavigation(oldPrincipalEntry, principalToDependent, null); - } + ResetReferenceOrRemoveCollection(oldPrincipalEntry, principalToDependent, entry); } if (newPrincipalEntry != null && !entry.IsConceptualNull(property)) { // Add this entity to the collection of the new principal, or set the navigation for a 1:1 - SetReferenceOrAddToCollection(newPrincipalEntry, principalToDependent, collectionAccessor, entry.Entity); + SetReferenceOrAddToCollection(newPrincipalEntry, principalToDependent, entry); } } @@ -366,7 +344,7 @@ var targetDependentEntry if (!entry.IsConceptualNull(property)) { - SetNavigation(entry, dependentToPrincipal, newPrincipalEntry.Entity); + SetNavigation(entry, dependentToPrincipal, newPrincipalEntry); } } else if (oldPrincipalEntry != null @@ -392,12 +370,10 @@ var targetDependentEntry var principalToDependent = foreignKey.PrincipalToDependent; if (principalToDependent != null) { - var collectionAccessor = principalToDependent.IsCollection() ? principalToDependent.GetCollectionAccessor() : null; - if (!entry.IsConceptualNull(property)) { // Add this entity to the collection of the new principal, or set the navigation for a 1:1 - SetReferenceOrAddToCollection(entry, principalToDependent, collectionAccessor, dependentEntry.Entity); + SetReferenceOrAddToCollection(entry, principalToDependent, dependentEntry); } } @@ -406,7 +382,7 @@ var targetDependentEntry { if (!entry.IsConceptualNull(property)) { - SetNavigation(dependentEntry, dependentToPrincipal, entry.Entity); + SetNavigation(dependentEntry, dependentToPrincipal, entry); } } } @@ -499,20 +475,10 @@ private void DeleteFixup(InternalEntityEntry entry) if (principalToDependent != null) { var principalEntry = stateManager.GetPrincipal(entry, foreignKey); - if (principalEntry != null) + if (principalEntry != null + && principalEntry.EntityState != EntityState.Deleted) { - if (principalToDependent.IsCollection()) - { - RemoveFromCollection( - principalEntry, - principalToDependent, - principalToDependent.GetCollectionAccessor(), - entry.Entity); - } - else if (principalEntry[principalToDependent] == entry.Entity) - { - SetNavigation(principalEntry, principalToDependent, null); - } + ResetReferenceOrRemoveCollection(principalEntry, principalToDependent, entry); } } } @@ -551,7 +517,7 @@ private void InitialFixup( if (principalEntry != null) { // Set navigation to principal based on FK properties - SetNavigation(entry, foreignKey.DependentToPrincipal, principalEntry.Entity); + SetNavigation(entry, foreignKey.DependentToPrincipal, principalEntry); // Add this entity to principal's collection, or set inverse for 1:1 ToDependentFixup(entry, principalEntry, foreignKey); @@ -575,27 +541,17 @@ private void InitialFixup( var dependentEntry = dependents.First(); // Set navigations to and from principal entity that is indicated by FK - SetNavigation(entry, principalToDependent, dependentEntry.Entity); - SetNavigation(dependentEntry, dependentToPrincipal, entry.Entity); + SetNavigation(entry, principalToDependent, dependentEntry); + SetNavigation(dependentEntry, dependentToPrincipal, entry); } else { - // Remove when issue #749 is fixed - var collectionAccessor = principalToDependent?.IsShadowProperty ?? true - ? null - : principalToDependent.GetCollectionAccessor(); - foreach (var dependentEntry in dependents) { - var dependentEntity = dependentEntry.Entity; - // Add to collection on principal indicated by FK and set inverse navigation - if (collectionAccessor != null) - { - AddToCollection(entry, principalToDependent, collectionAccessor, dependentEntity); - } + AddToCollection(entry, principalToDependent, dependentEntry); - SetNavigation(dependentEntry, dependentToPrincipal, entry.Entity); + SetNavigation(dependentEntry, dependentToPrincipal, entry); } } } @@ -703,7 +659,7 @@ private void DelayedFixup(InternalEntityEntry entry, INavigation navigation, Int { if (navigation.IsCollection()) { - if (navigation.GetCollectionAccessor().Contains(entry.Entity, referencedEntry.Entity)) + if (entry.CollectionContains(navigation, referencedEntry)) { FixupToDependent(entry, referencedEntry, navigation.ForeignKey, setModified); } @@ -728,7 +684,7 @@ private void FixupToDependent( { SetForeignKeyProperties(dependentEntry, principalEntry, foreignKey, setModified); - SetNavigation(dependentEntry, foreignKey.DependentToPrincipal, principalEntry.Entity); + SetNavigation(dependentEntry, foreignKey.DependentToPrincipal, principalEntry); } private void FixupToPrincipal( @@ -777,8 +733,7 @@ private void ToDependentFixup( SetReferenceOrAddToCollection( principalEntry, principalToDependent, - principalToDependent.IsCollection() ? principalToDependent.GetCollectionAccessor() : null, - dependentEntry.Entity); + dependentEntry); } } @@ -866,37 +821,34 @@ private void ConditionallyNullForeignKeyProperties( } } - private void SetNavigation(InternalEntityEntry entry, INavigation navigation, object value) + private void SetNavigation(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value) { if (navigation != null) { _changeDetector.Suspend(); + var entity = value?.Entity; try { - entry[navigation] = value; + entry[navigation] = entity; } finally { _changeDetector.Resume(); } - entry.SetRelationshipSnapshotValue(navigation, value); + entry.SetRelationshipSnapshotValue(navigation, entity); } } - private void AddToCollection( - InternalEntityEntry entry, - INavigation navigation, - IClrCollectionAccessor collectionAccessor, - object value) + private void AddToCollection(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value) { if (navigation != null) { _changeDetector.Suspend(); try { - if (collectionAccessor.Add(entry.Entity, value)) + if (entry.AddToCollection(navigation, value)) { - entry.AddToCollectionSnapshot(navigation, value); + entry.AddToCollectionSnapshot(navigation, value.Entity); } } finally @@ -906,38 +858,48 @@ private void AddToCollection( } } - private void RemoveFromCollection( - InternalEntityEntry entry, - INavigation navigation, - IClrCollectionAccessor collectionAccessor, - object value) + private void RemoveFromCollection(InternalEntityEntry entry, INavigation navigation, InternalEntityEntry value) { _changeDetector.Suspend(); try { - collectionAccessor.Remove(entry.Entity, value); + entry.RemoveFromCollection(navigation, value); } finally { _changeDetector.Resume(); } - entry.RemoveFromCollectionSnapshot(navigation, value); + entry.RemoveFromCollectionSnapshot(navigation, value.Entity); } private void SetReferenceOrAddToCollection( InternalEntityEntry entry, INavigation navigation, - IClrCollectionAccessor collectionAccessor, - object value) + InternalEntityEntry value) { - if (collectionAccessor != null) + if (navigation.IsCollection()) { - AddToCollection(entry, navigation, collectionAccessor, value); + AddToCollection(entry, navigation, value); } else { SetNavigation(entry, navigation, value); } } + + private void ResetReferenceOrRemoveCollection( + InternalEntityEntry entry, + INavigation navigation, + InternalEntityEntry value) + { + if (navigation.IsCollection()) + { + RemoveFromCollection(entry, navigation, value); + } + else if (ReferenceEquals(entry[navigation], value.Entity)) + { + SetNavigation(entry, navigation, null); + } + } } } diff --git a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs index 46e73d3e48b..9f6de50c3e3 100644 --- a/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/OriginalValuesFactoryFactory.cs @@ -17,7 +17,7 @@ public class OriginalValuesFactoryFactory : SnapshotFactoryFactory protected override int GetPropertyIndex(IPropertyBase propertyBase) - => (propertyBase as IProperty)?.GetOriginalValueIndex() ?? -1; + => propertyBase.GetOriginalValueIndex(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/ChangeTracking/Internal/RelationshipsSnapshot.cs b/src/EFCore/ChangeTracking/Internal/RelationshipsSnapshot.cs index 06e506a018f..044016d4386 100644 --- a/src/EFCore/ChangeTracking/Internal/RelationshipsSnapshot.cs +++ b/src/EFCore/ChangeTracking/Internal/RelationshipsSnapshot.cs @@ -32,8 +32,7 @@ public void SetValue(IPropertyBase propertyBase, object value) { if (value == null) { - var property = propertyBase as IProperty; - if (property != null + if (propertyBase is IProperty property && !property.IsNullable) { return; diff --git a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs index 1cb1800a9cc..dc5f8b12ac3 100644 --- a/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/ShadowValuesFactoryFactory.cs @@ -19,7 +19,7 @@ public class ShadowValuesFactoryFactory : SnapshotFactoryFactory /// directly from your code. This API may change or be removed in future releases. /// protected override int GetPropertyIndex(IPropertyBase propertyBase) - => (propertyBase as IProperty)?.GetShadowIndex() ?? -1; + => propertyBase.GetShadowIndex(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs index ab7e576984e..2edf1ce73aa 100644 --- a/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs +++ b/src/EFCore/ChangeTracking/Internal/SnapshotFactoryFactory.cs @@ -107,9 +107,7 @@ private Expression CreateSnapshotExpression( var propertyBase = propertyBases[i]; if (propertyBase.IsShadowProperty) { - arguments[i] = propertyBase is INavigation - ? Expression.Constant(null) // Remove when issue #749 is fixed - : CreateReadShadowValueExpression(parameter, propertyBase); + arguments[i] = CreateReadShadowValueExpression(parameter, propertyBase); } else { diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index 855a8474fe5..6dc3904c838 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -178,7 +178,6 @@ public virtual InternalEntityEntry GetOrCreateEntry(IDictionary if (entityType.HasClrType()) { - // Remove when issue #749 is fixed entity = Activator.CreateInstance(entityType.ClrType); entry = _internalEntityEntryFactory.Create(this, entityType, entity); } diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 3d320137098..fa8e189e529 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -399,9 +399,9 @@ protected virtual void ValidateFieldMapping([NotNull] IModel model) { foreach (var propertyBase in entityType .GetDeclaredProperties() - .Where(e => !e.IsShadowProperty) .Cast() - .Concat(entityType.GetDeclaredNavigations())) + .Concat(entityType.GetDeclaredNavigations()) + .Where(e => !e.IsShadowProperty)) { if (!propertyBase.TryGetMemberInfo( forConstruction: true, diff --git a/src/EFCore/Internal/NonCapturingLazyInitializer.cs b/src/EFCore/Internal/NonCapturingLazyInitializer.cs index c9733f1d7fa..691ce218570 100644 --- a/src/EFCore/Internal/NonCapturingLazyInitializer.cs +++ b/src/EFCore/Internal/NonCapturingLazyInitializer.cs @@ -72,5 +72,25 @@ public static TValue EnsureInitialized( return target; } + + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public static TValue EnsureInitialized( + [CanBeNull] ref TValue target, + [CanBeNull] TParam param, + [NotNull] Action valueFactory) + where TValue : class + { + if (Volatile.Read(ref target) != null) + { + return target; + } + + valueFactory(param); + + return Volatile.Read(ref target); + } } } diff --git a/src/EFCore/Metadata/Internal/ClrICollectionAccessor.cs b/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs similarity index 95% rename from src/EFCore/Metadata/Internal/ClrICollectionAccessor.cs rename to src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs index cc838a4484a..2222ac34d46 100644 --- a/src/EFCore/Metadata/Internal/ClrICollectionAccessor.cs +++ b/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Internal; @@ -12,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Metadata.Internal /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public class ClrICollectionAccessor : IClrCollectionAccessor + public class ClrCollectionAccessor : IClrCollectionAccessor where TEntity : class where TCollection : class, IEnumerable { @@ -32,7 +33,7 @@ public class ClrICollectionAccessor : IClrCollec /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public ClrICollectionAccessor( + public ClrCollectionAccessor( [NotNull] string propertyName, [NotNull] Func getCollection, [CanBeNull] Action setCollection, @@ -84,7 +85,7 @@ public virtual void AddRange(object instance, IEnumerable values) /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual object Create() + public virtual IEnumerable Create() { if (_createCollection == null) { @@ -100,7 +101,7 @@ public virtual object Create() /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual object Create(IEnumerable values) + public virtual IEnumerable Create(IEnumerable values) { var collection = (ICollection)Create(); foreach (TElement value in values) @@ -115,7 +116,7 @@ public virtual object Create(IEnumerable values) /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public virtual object GetOrCreate(object instance) => GetOrCreateCollection(instance); + public virtual IEnumerable GetOrCreate(object instance) => GetOrCreateCollection(instance); private ICollection GetOrCreateCollection(object instance) { diff --git a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs index 8eb080c0bf1..69ac5961fc0 100644 --- a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs +++ b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs @@ -32,8 +32,7 @@ private static readonly MethodInfo _create public virtual IClrCollectionAccessor Create([NotNull] INavigation navigation) { // ReSharper disable once SuspiciousTypeConversion.Global - var accessor = navigation as IClrCollectionAccessor; - if (accessor != null) + if (navigation is IClrCollectionAccessor accessor) { return accessor; } @@ -119,7 +118,7 @@ private static IClrCollectionAccessor CreateGeneric( + return new ClrCollectionAccessor( navigation.Name, getterDelegate, setterDelegate, createAndSetDelegate, createDelegate); } diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index a0df4d68e2d..96bf3a7bfa1 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -1165,17 +1165,6 @@ private Navigation AddNavigation(PropertyIdentity propertyIdentity, ForeignKey f (pointsToPrincipal ? foreignKey.DeclaringEntityType : foreignKey.PrincipalEntityType) == this, "EntityType mismatch"); - var navigationProperty = propertyIdentity.Property; - if (ClrType != null) - { - Navigation.IsCompatible( - propertyIdentity.Name, - navigationProperty, - this, - pointsToPrincipal ? foreignKey.PrincipalEntityType : foreignKey.DeclaringEntityType, - !pointsToPrincipal && !foreignKey.IsUnique, - shouldThrow: true); - } var navigation = new Navigation(name, propertyIdentity.Property, null, foreignKey); _navigations.Add(name, navigation); diff --git a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs index 8d510b6686e..2b9a23ae623 100644 --- a/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs +++ b/src/EFCore/Metadata/Internal/EntityTypeExtensions.cs @@ -16,6 +16,8 @@ using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.Utilities; +// ReSharper disable ArgumentsStyleOther +// ReSharper disable ArgumentsStyleNamedExpression namespace Microsoft.EntityFrameworkCore.Metadata.Internal { /// @@ -265,36 +267,57 @@ public static PropertyCounts GetCounts([NotNull] this IEntityType entityType) /// public static PropertyCounts CalculateCounts([NotNull] this IEntityType entityType) { - var properties = entityType.GetDeclaredProperties().ToList(); - var navigations = entityType.GetDeclaredNavigations(); + var index = 0; + var navigationIndex = 0; + var originalValueIndex = 0; + var shadowIndex = 0; + var relationshipIndex = 0; + var storeGenerationIndex = 0; + + var baseCounts = entityType.BaseType?.GetCounts(); + if (baseCounts != null) + { + index = baseCounts.PropertyCount; + navigationIndex = baseCounts.NavigationCount; + originalValueIndex = baseCounts.OriginalValueCount; + shadowIndex = baseCounts.ShadowCount; + relationshipIndex = baseCounts.RelationshipCount; + storeGenerationIndex = baseCounts.StoreGeneratedCount; + } + + foreach (var property in entityType.GetDeclaredProperties()) + { + var indexes = new PropertyIndexes( + index: index++, + originalValueIndex: property.RequiresOriginalValue() ? originalValueIndex++ : -1, + shadowIndex: property.IsShadowProperty ? shadowIndex++ : -1, + relationshipIndex: property.IsKeyOrForeignKey() ? relationshipIndex++ : -1, + storeGenerationIndex: property.MayBeStoreGenerated() ? storeGenerationIndex++ : -1); + + property.SetIndexes(indexes); + } var isNotifying = entityType.GetChangeTrackingStrategy() != ChangeTrackingStrategy.Snapshot; - var propertyCount = properties.Count; - var navigationCount = navigations.Count(); - var originalValueCount = properties.Count(p => p.RequiresOriginalValue()); - var shadowCount = properties.Count(p => p.IsShadowProperty); - var relationshipCount = (isNotifying ? navigations.Count(n => !n.IsCollection()) : navigationCount) - + properties.Count(p => p.IsKeyOrForeignKey()); - var storeGeneratedCount = properties.Count(p => p.MayBeStoreGenerated()); - - var baseCounts = entityType.BaseType?.CalculateCounts(); - - return baseCounts == null - ? new PropertyCounts( - propertyCount, - navigationCount, - originalValueCount, - shadowCount, - relationshipCount, - storeGeneratedCount) - : new PropertyCounts( - baseCounts.PropertyCount + propertyCount, - baseCounts.NavigationCount + navigationCount, - baseCounts.OriginalValueCount + originalValueCount, - baseCounts.ShadowCount + shadowCount, - baseCounts.RelationshipCount + relationshipCount, - baseCounts.StoreGeneratedCount + storeGeneratedCount); + foreach (var navigation in entityType.GetDeclaredNavigations()) + { + var indexes = new PropertyIndexes( + index: navigationIndex++, + originalValueIndex: -1, + shadowIndex: navigation.IsShadowProperty ? shadowIndex++ : -1, + relationshipIndex: navigation.IsCollection() && isNotifying ? -1 : relationshipIndex++, + storeGenerationIndex: -1); + + navigation.SetIndexes(indexes); + } + + return new PropertyCounts( + index, + navigationIndex, + originalValueIndex, + shadowIndex, + relationshipIndex, + storeGenerationIndex); } /// @@ -476,6 +499,7 @@ public static IEnumerable GetNotificationProperties( } else { + // ReSharper disable once AssignNullToNotNullAttribute var property = (IPropertyBase)entityType.FindProperty(propertyName) ?? entityType.FindNavigation(propertyName); if (property != null) diff --git a/src/EFCore/Metadata/Internal/IClrCollectionAccessor.cs b/src/EFCore/Metadata/Internal/IClrCollectionAccessor.cs index d000cab1a29..41cea6b6b5f 100644 --- a/src/EFCore/Metadata/Internal/IClrCollectionAccessor.cs +++ b/src/EFCore/Metadata/Internal/IClrCollectionAccessor.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections; using System.Collections.Generic; using JetBrains.Annotations; @@ -41,19 +42,19 @@ public interface IClrCollectionAccessor /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - object Create(); + IEnumerable Create(); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - object Create([NotNull] IEnumerable values); + IEnumerable Create([NotNull] IEnumerable values); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - object GetOrCreate([NotNull] object instance); + IEnumerable GetOrCreate([NotNull] object instance); /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used diff --git a/src/EFCore/Metadata/Internal/InternalRelationshipBuilder.cs b/src/EFCore/Metadata/Internal/InternalRelationshipBuilder.cs index af650fc14d2..6e732aadad3 100644 --- a/src/EFCore/Metadata/Internal/InternalRelationshipBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalRelationshipBuilder.cs @@ -317,12 +317,23 @@ private InternalRelationshipBuilder Navigations( Metadata.HasPrincipalToDependent((string)null, configurationSource.Value); } + var navigationProperty = navigationToPrincipal.Value.Property; if (navigationToPrincipalName != null) { Metadata.DeclaringEntityType.Unignore(navigationToPrincipalName); + + if (Metadata.DeclaringEntityType.ClrType != null) + { + Navigation.IsCompatible( + navigationToPrincipalName, + navigationProperty, + Metadata.DeclaringEntityType, + Metadata.PrincipalEntityType, + shouldBeCollection: false, + shouldThrow: true); + } } - var navigationProperty = navigationToPrincipal.Value.Property; if (navigationProperty != null) { Metadata.HasDependentToPrincipal(navigationProperty, configurationSource.Value); @@ -335,12 +346,23 @@ private InternalRelationshipBuilder Navigations( if (navigationToDependent != null) { + var navigationProperty = navigationToDependent.Value.Property; if (navigationToDependentName != null) { Metadata.PrincipalEntityType.Unignore(navigationToDependentName); + + if (Metadata.DeclaringEntityType.ClrType != null) + { + Navigation.IsCompatible( + navigationToPrincipalName, + navigationProperty, + Metadata.PrincipalEntityType, + Metadata.DeclaringEntityType, + !Metadata.IsUnique, + shouldThrow: true); + } } - var navigationProperty = navigationToDependent.Value.Property; if (navigationProperty != null) { Metadata.HasPrincipalToDependent(navigationProperty, configurationSource.Value); diff --git a/src/EFCore/Metadata/Internal/Navigation.cs b/src/EFCore/Metadata/Internal/Navigation.cs index ee36e559262..23a8685224c 100644 --- a/src/EFCore/Metadata/Internal/Navigation.cs +++ b/src/EFCore/Metadata/Internal/Navigation.cs @@ -19,8 +19,6 @@ public class Navigation : PropertyBase, IMutableNavigation // Warning: Never access these fields directly as access needs to be thread-safe private IClrCollectionAccessor _collectionAccessor; - private PropertyIndexes _indexes; - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -190,35 +188,6 @@ public static bool IsCompatible( return true; } - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public virtual PropertyIndexes PropertyIndexes - { - get - { - return NonCapturingLazyInitializer.EnsureInitialized( - ref _indexes, this, - property => property.DeclaringType.CalculateIndexes(property)); - } - - [param: CanBeNull] - set - { - if (value == null) - { - // This path should only kick in when the model is still mutable and therefore access does not need - // to be thread-safe. - _indexes = null; - } - else - { - NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, value); - } - } - } - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -238,7 +207,10 @@ public virtual EntityType GetTargetType() /// directly from your code. This API may change or be removed in future releases. /// public virtual IClrCollectionAccessor CollectionAccessor - => NonCapturingLazyInitializer.EnsureInitialized(ref _collectionAccessor, this, n => new ClrCollectionAccessorFactory().Create(n)); + => NonCapturingLazyInitializer.EnsureInitialized(ref _collectionAccessor, this, n => + !n.IsCollection() || n.IsShadowProperty + ? null + : new ClrCollectionAccessorFactory().Create(n)); IForeignKey INavigation.ForeignKey => ForeignKey; IMutableForeignKey IMutableNavigation.ForeignKey => ForeignKey; diff --git a/src/EFCore/Metadata/Internal/Property.cs b/src/EFCore/Metadata/Internal/Property.cs index e07cd43df62..2d20a990e77 100644 --- a/src/EFCore/Metadata/Internal/Property.cs +++ b/src/EFCore/Metadata/Internal/Property.cs @@ -33,9 +33,6 @@ public class Property : PropertyBase, IMutableProperty private ConfigurationSource? _isConcurrencyTokenConfigurationSource; private ConfigurationSource? _valueGeneratedConfigurationSource; - // Warning: Never access these fields directly as access needs to be thread-safe - private PropertyIndexes _indexes; - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -495,32 +492,6 @@ public static bool AreCompatible([NotNull] IReadOnlyList properties, [ && entityType.ClrType.GetFieldInfo(property.Name) != null)))); } - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public virtual PropertyIndexes PropertyIndexes - { - get => NonCapturingLazyInitializer.EnsureInitialized( - ref _indexes, this, - property => property.DeclaringType.CalculateIndexes(property)); - - [param: CanBeNull] - set - { - if (value == null) - { - // This path should only kick in when the model is still mutable and therefore access does not need - // to be thread-safe. - _indexes = null; - } - else - { - NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, value); - } - } - } - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. diff --git a/src/EFCore/Metadata/Internal/PropertyBase.cs b/src/EFCore/Metadata/Internal/PropertyBase.cs index 002d31dcd37..888d4481d1a 100644 --- a/src/EFCore/Metadata/Internal/PropertyBase.cs +++ b/src/EFCore/Metadata/Internal/PropertyBase.cs @@ -21,9 +21,9 @@ public abstract class PropertyBase : ConventionalAnnotatable, IMutablePropertyBa // Warning: Never access these fields directly as access needs to be thread-safe private IClrPropertyGetter _getter; - private IClrPropertySetter _setter; private PropertyAccessors _accessors; + private PropertyIndexes _indexes; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -195,6 +195,35 @@ public static bool IsCompatible( return true; } + /// + /// This API supports the Entity Framework Core infrastructure and is not intended to be used + /// directly from your code. This API may change or be removed in future releases. + /// + public virtual PropertyIndexes PropertyIndexes + { + get => NonCapturingLazyInitializer.EnsureInitialized( + ref _indexes, this, + property => + { + var _ = (property.DeclaringType as EntityType)?.Counts; + }); + + [param: CanBeNull] + set + { + if (value == null) + { + // This path should only kick in when the model is still mutable and therefore access does not need + // to be thread-safe. + _indexes = null; + } + else + { + NonCapturingLazyInitializer.EnsureInitialized(ref _indexes, value); + } + } + } + /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. diff --git a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs index b09e1cb14c7..21ec1718763 100644 --- a/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyBaseExtensions.cs @@ -47,96 +47,22 @@ public static int GetIndex([NotNull] this IPropertyBase property) /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static PropertyIndexes GetPropertyIndexes([NotNull] this IPropertyBase propertyBase) - => (propertyBase as IProperty)?.AsProperty()?.PropertyIndexes - ?? ((INavigation)propertyBase).AsNavigation().PropertyIndexes; + public static int GetOriginalValueIndex([NotNull] this IPropertyBase propertyBase) + => propertyBase.GetPropertyIndexes().OriginalValueIndex; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static PropertyIndexes CalculateIndexes([NotNull] this IEntityType entityType, [NotNull] IPropertyBase propertyBase) - { - var index = 0; - var navigationIndex = 0; - var shadowIndex = 0; - var originalValueIndex = 0; - var relationshipIndex = 0; - var storeGenerationIndex = 0; - - var baseCounts = entityType.BaseType?.GetCounts(); - if (baseCounts != null) - { - index = baseCounts.PropertyCount; - navigationIndex = baseCounts.NavigationCount; - shadowIndex = baseCounts.ShadowCount; - originalValueIndex = baseCounts.OriginalValueCount; - relationshipIndex = baseCounts.RelationshipCount; - storeGenerationIndex = baseCounts.StoreGeneratedCount; - } - - PropertyIndexes callingPropertyIndexes = null; - - foreach (var property in entityType.GetDeclaredProperties()) - { - var indexes = new PropertyIndexes( - index: index++, - originalValueIndex: property.RequiresOriginalValue() ? originalValueIndex++ : -1, - shadowIndex: property.IsShadowProperty ? shadowIndex++ : -1, - relationshipIndex: property.IsKeyOrForeignKey() ? relationshipIndex++ : -1, - storeGenerationIndex: property.MayBeStoreGenerated() ? storeGenerationIndex++ : -1); - - TrySetIndexes(property, indexes); - - if (propertyBase == property) - { - callingPropertyIndexes = indexes; - } - } - - var isNotifying = entityType.GetChangeTrackingStrategy() != ChangeTrackingStrategy.Snapshot; - - foreach (var navigation in entityType.GetDeclaredNavigations()) - { - var indexes = new PropertyIndexes( - index: navigationIndex++, - originalValueIndex: -1, - shadowIndex: -1, - relationshipIndex: navigation.IsCollection() && isNotifying ? -1 : relationshipIndex++, - storeGenerationIndex: -1); - - TrySetIndexes(navigation, indexes); - - if (propertyBase == navigation) - { - callingPropertyIndexes = indexes; - } - } - - foreach (var derivedType in entityType.GetDirectlyDerivedTypes()) - { - derivedType.CalculateIndexes(propertyBase); - } - - return callingPropertyIndexes; - } + public static PropertyIndexes GetPropertyIndexes([NotNull] this IPropertyBase propertyBase) + => propertyBase.AsPropertyBase()?.PropertyIndexes; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. /// - public static void TrySetIndexes([NotNull] this IPropertyBase propertyBase, [CanBeNull] PropertyIndexes indexes) - { - var property = propertyBase as IProperty; - if (property != null) - { - property.AsProperty().PropertyIndexes = indexes; - } - else - { - ((INavigation)propertyBase).AsNavigation().PropertyIndexes = indexes; - } - } + public static void SetIndexes([NotNull] this IPropertyBase propertyBase, [CanBeNull] PropertyIndexes indexes) + => propertyBase.AsPropertyBase().PropertyIndexes = indexes; /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used @@ -168,9 +94,7 @@ public static MemberInfo GetMemberInfo( bool forConstruction, bool forSet) { - MemberInfo memberInfo; - string errorMessage; - if (propertyBase.TryGetMemberInfo(forConstruction, forSet, out memberInfo, out errorMessage)) + if (propertyBase.TryGetMemberInfo(forConstruction, forSet, out var memberInfo, out var errorMessage)) { return memberInfo; } diff --git a/src/EFCore/Metadata/Internal/PropertyExtensions.cs b/src/EFCore/Metadata/Internal/PropertyExtensions.cs index d9b07d8380f..3f32e332de0 100644 --- a/src/EFCore/Metadata/Internal/PropertyExtensions.cs +++ b/src/EFCore/Metadata/Internal/PropertyExtensions.cs @@ -1,6 +1,7 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -101,13 +102,6 @@ public static bool RequiresValueGenerator([NotNull] this IProperty property) && property.IsKey()) || property.GetValueGeneratorFactory() != null; - /// - /// This API supports the Entity Framework Core infrastructure and is not intended to be used - /// directly from your code. This API may change or be removed in future releases. - /// - public static int GetOriginalValueIndex([NotNull] this IProperty property) - => property.GetPropertyIndexes().OriginalValueIndex; - /// /// This API supports the Entity Framework Core infrastructure and is not intended to be used /// directly from your code. This API may change or be removed in future releases. @@ -193,7 +187,7 @@ public static string ToDebugString([NotNull] this IProperty property, bool singl { builder.Append("no field, "); } - else if (!field.EndsWith(">k__BackingField")) + else if (!field.EndsWith(">k__BackingField", StringComparison.Ordinal)) { builder.Append(field).Append(", "); } diff --git a/test/EFCore.Relational.Tests/Metadata/InternalRelationalMetadataBuilderExtensionsTest.cs b/test/EFCore.Relational.Tests/Metadata/InternalRelationalMetadataBuilderExtensionsTest.cs index cc7118d6571..18619972512 100644 --- a/test/EFCore.Relational.Tests/Metadata/InternalRelationalMetadataBuilderExtensionsTest.cs +++ b/test/EFCore.Relational.Tests/Metadata/InternalRelationalMetadataBuilderExtensionsTest.cs @@ -9,6 +9,8 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using Xunit; +// ReSharper disable UnusedMember.Local +// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.Metadata { public class InternalRelationalMetadataBuilderExtensionsTest @@ -315,6 +317,8 @@ public void Changing_discriminator_type_removes_values() discriminatorBuilder = typeBuilder.Relational(ConfigurationSource.Convention) .HasDiscriminator("Splotted", typeof(string)); + + Assert.NotNull(discriminatorBuilder); Assert.Equal("4", typeBuilder.Metadata.Relational().DiscriminatorValue); Assert.Equal( "5", typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention) @@ -324,6 +328,8 @@ public void Changing_discriminator_type_removes_values() .Metadata.Relational().DiscriminatorValue); discriminatorBuilder = typeBuilder.Relational(ConfigurationSource.Convention).HasDiscriminator(typeof(int)); + + Assert.NotNull(discriminatorBuilder); Assert.Null(typeBuilder.Metadata.Relational().DiscriminatorValue); Assert.Null( typeBuilder.ModelBuilder.Entity("Splow", ConfigurationSource.Convention) diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index 5413b3e2ccf..d221dcd5be1 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -12,6 +12,10 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable ParameterOnlyUsedForPreconditionCheck.Local +// ReSharper disable UnusedAutoPropertyAccessor.Local +// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking { public class ChangeTrackerTest @@ -611,9 +615,8 @@ public void Further_graph_traversal_stops_if_an_entity_is_not_attached() category, e => { traversal.Add(NodeString(e)); - var product = e.Entry.Entity as Product; - if ((product == null) - || (product.Id != 2)) + if (!(e.Entry.Entity is Product product) + || product.Id != 2) { e.Entry.State = EntityState.Unchanged; } @@ -691,8 +694,8 @@ public void Can_attach_parent_with_some_new_and_some_existing_entities() e => { traversal.Add(NodeString(e)); - var product = e.Entry.Entity as Product; - e.Entry.State = (product != null) && (product.Id == 0) ? EntityState.Added : EntityState.Unchanged; + e.Entry.State = e.Entry.Entity is Product product && product.Id == 0 + ? EntityState.Added : EntityState.Unchanged; }); Assert.Equal( @@ -1481,6 +1484,8 @@ private class Product public ProductDetails Details { get; set; } + // ReSharper disable once CollectionNeverUpdated.Local + // ReSharper disable once MemberHidesStaticFromOuterClass public List OrderDetails { get; set; } } @@ -1517,6 +1522,8 @@ private class Order { public int Id { get; set; } + // ReSharper disable once CollectionNeverUpdated.Local + // ReSharper disable once MemberHidesStaticFromOuterClass public List OrderDetails { get; set; } } @@ -1537,11 +1544,13 @@ private class NullbileCategory private class NullbileCategoryInfo { + // ReSharper disable once MemberHidesStaticFromOuterClass public NullbileCategory Category { get; set; } } private class NullbileProduct { + // ReSharper disable once MemberHidesStaticFromOuterClass public NullbileCategory Category { get; set; } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/FixupCompositeTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/FixupCompositeTest.cs index c27e2688a10..86e4411a3d9 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/FixupCompositeTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/FixupCompositeTest.cs @@ -5,7 +5,10 @@ using System.Collections.Generic; using System.Linq; using Xunit; +// ReSharper disable MemberHidesStaticFromOuterClass +// ReSharper disable AccessToDisposedClosure +// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { public class FixupCompositeTest @@ -3482,6 +3485,7 @@ private class FixupContext : DbContext { public FixupContext() { + // ReSharper disable once VirtualMemberCallInConstructor ChangeTracker.AutoDetectChangesEnabled = false; } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs index 4b080d8035b..b38bb5087e3 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs @@ -23,27 +23,8 @@ public class FixupTest [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_FK_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, principal.Id); - dependent.SetCategory(principal); - principal.AddProduct(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: true, setToPrincipal: true, setToDependent: true); } [Theory] @@ -52,27 +33,8 @@ public void Add_dependent_then_principal_one_to_many_FK_set_both_navs_set(Entity [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_FK_not_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, 0); - dependent.SetCategory(principal); - principal.AddProduct(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: false, setToPrincipal: true, setToDependent: true); } [Theory] @@ -81,25 +43,8 @@ public void Add_dependent_then_principal_one_to_many_FK_not_set_both_navs_set(En [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, principal.Id); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: true, setToPrincipal: false, setToDependent: false); } [Theory] @@ -108,26 +53,8 @@ public void Add_dependent_then_principal_one_to_many_FK_set_no_navs_set(EntitySt [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_FK_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, principal.Id); - principal.AddProduct(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: true, setToPrincipal: false, setToDependent: true); } [Theory] @@ -136,26 +63,8 @@ public void Add_dependent_then_principal_one_to_many_FK_set_principal_nav_set(En [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, principal.Id); - dependent.SetCategory(principal); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: true, setToPrincipal: true, setToDependent: false); } [Theory] @@ -164,26 +73,8 @@ public void Add_dependent_then_principal_one_to_many_FK_set_dependent_nav_set(En [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_FK_not_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, 0); - principal.AddProduct(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: false, setToPrincipal: false, setToDependent: true); } [Theory] @@ -192,26 +83,8 @@ public void Add_dependent_then_principal_one_to_many_FK_not_set_principal_nav_se [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_FK_not_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, 0); - dependent.SetCategory(principal); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList().ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: false, setToPrincipal: true, setToDependent: false); } [Theory] @@ -220,27 +93,8 @@ public void Add_dependent_then_principal_one_to_many_FK_not_set_dependent_nav_se [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_FK_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, principal.Id); - dependent.SetCategory(principal); - principal.AddProduct(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: true, setFk: true, setToPrincipal: true, setToDependent: true); } [Theory] @@ -249,27 +103,8 @@ public void Add_principal_then_dependent_one_to_many_FK_set_both_navs_set(Entity [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_FK_not_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, 0); - dependent.SetCategory(principal); - principal.AddProduct(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: true, setFk: false, setToPrincipal: true, setToDependent: true); } [Theory] @@ -278,25 +113,8 @@ public void Add_principal_then_dependent_one_to_many_FK_not_set_both_navs_set(En [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, principal.Id); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: true, setFk: true, setToPrincipal: false, setToDependent: false); } [Theory] @@ -305,26 +123,8 @@ public void Add_principal_then_dependent_one_to_many_FK_set_no_navs_set(EntitySt [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_FK_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, principal.Id); - principal.AddProduct(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: true, setFk: true, setToPrincipal: false, setToDependent: true); } [Theory] @@ -333,26 +133,8 @@ public void Add_principal_then_dependent_one_to_many_FK_set_principal_nav_set(En [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, principal.Id); - dependent.SetCategory(principal); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: true, setFk: true, setToPrincipal: true, setToDependent: false); } [Theory] @@ -361,26 +143,8 @@ public void Add_principal_then_dependent_one_to_many_FK_set_dependent_nav_set(En [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_FK_not_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(78, 0); - principal.AddProduct(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: true, setFk: false, setToPrincipal: false, setToDependent: true); } [Theory] @@ -388,15 +152,40 @@ public void Add_principal_then_dependent_one_to_many_FK_not_set_principal_nav_se [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: true, setFk: false, setToPrincipal: true, setToDependent: false); + } + + private void Add_principal_and_dependent_one_to_many( + EntityState entityState, bool principalFirst, bool setFk, bool setToPrincipal, bool setToDependent) { using (var context = new FixupContext()) { var principal = new Category(77); var dependent = new Product(78, 0); - dependent.SetCategory(principal); + if (setFk) + { + dependent.SetCategoryId(principal.Id); + } + if (setToPrincipal) + { + dependent.SetCategory(principal); + } + if (setToDependent) + { + principal.AddProduct(dependent); + } - context.Entry(principal).State = entityState; + if (principalFirst) + { + context.Entry(principal).State = entityState; + } context.Entry(dependent).State = entityState; + if (!principalFirst) + { + context.Entry(principal).State = entityState; + } AssertFixup( context, @@ -404,7 +193,7 @@ public void Add_principal_then_dependent_one_to_many_FK_not_set_dependent_nav_se { Assert.Equal(principal.Id, dependent.CategoryId); Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); + Assert.Equal(new[] { dependent }, principal.Products); Assert.Equal(entityState, context.Entry(principal).State); Assert.Equal(entityState, context.Entry(dependent).State); }); @@ -417,154 +206,86 @@ public void Add_principal_then_dependent_one_to_many_FK_not_set_dependent_nav_se [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_prin_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78, CategoryId = principal.Id }; + Add_principal_and_dependent_one_to_many_prin_uni(entityState, principalFirst: false, setFk: true, setToDependent: false); + } - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_many_prin_uni_FK_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many_prin_uni(entityState, principalFirst: false, setFk: true, setToDependent: true); + } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_then_dependent_one_to_many_prin_uni_FK_set_no_navs_set(EntityState entityState) + public void Add_dependent_then_principal_one_to_many_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78, CategoryId = principal.Id }; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_prin_uni(entityState, principalFirst: false, setFk: false, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_then_principal_one_to_many_prin_uni_FK_set_principal_nav_set(EntityState entityState) + public void Add_principal_then_dependent_one_to_many_prin_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78, CategoryId = principal.Id }; - principal.Products.Add(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_prin_uni(entityState, principalFirst: true, setFk: true, setToDependent: false); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_then_principal_one_to_many_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + public void Add_principal_then_dependent_one_to_many_prin_uni_FK_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78 }; - principal.Products.Add(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_prin_uni(entityState, principalFirst: true, setFk: true, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_then_dependent_one_to_many_prin_uni_FK_set_principal_nav_set(EntityState entityState) + public void Add_principal_then_dependent_one_to_many_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78, CategoryId = principal.Id }; - principal.Products.Add(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_prin_uni(entityState, principalFirst: true, setFk: false, setToDependent: true); } - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_then_dependent_one_to_many_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + private void Add_principal_and_dependent_one_to_many_prin_uni( + EntityState entityState, bool principalFirst, bool setFk, bool setToDependent) { using (var context = new FixupContext()) { var principal = new CategoryPN { Id = 77 }; var dependent = new ProductPN { Id = 78 }; - principal.Products.Add(dependent); + if (setFk) + { + dependent.CategoryId = principal.Id; + } + if (setToDependent) + { + principal.Products.Add(dependent); + } - context.Entry(principal).State = entityState; + if (principalFirst) + { + context.Entry(principal).State = entityState; + } context.Entry(dependent).State = entityState; + if (!principalFirst) + { + context.Entry(principal).State = entityState; + } AssertFixup( context, () => { Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); + Assert.Equal(new[] { dependent }, principal.Products); Assert.Equal(entityState, context.Entry(principal).State); Assert.Equal(entityState, context.Entry(dependent).State); }); @@ -577,24 +298,7 @@ public void Add_principal_then_dependent_one_to_many_prin_uni_FK_not_set_princip [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_dep_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78, CategoryId = principal.Id }; - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_dep_uni(entityState, principalFirst: false, setFk: true, setToPrincipal: false); } [Theory] @@ -603,24 +307,7 @@ public void Add_dependent_then_principal_one_to_many_dep_uni_FK_set_no_navs_set( [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_dep_uni_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78, CategoryId = principal.Id, Category = principal }; - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_dep_uni(entityState, principalFirst: false, setFk: true, setToPrincipal: true); } [Theory] @@ -629,24 +316,7 @@ public void Add_dependent_then_principal_one_to_many_dep_uni_FK_set_dependent_na [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_many_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78, Category = principal }; - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_dep_uni(entityState, principalFirst: false, setFk: false, setToPrincipal: true); } [Theory] @@ -655,24 +325,7 @@ public void Add_dependent_then_principal_one_to_many_dep_uni_FK_not_set_dependen [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_dep_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78, CategoryId = principal.Id }; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_dep_uni(entityState, principalFirst: true, setFk: true, setToPrincipal: false); } [Theory] @@ -681,24 +334,7 @@ public void Add_principal_then_dependent_one_to_many_dep_uni_FK_set_no_navs_set( [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_dep_uni_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78, CategoryId = principal.Id, Category = principal }; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_many_dep_uni(entityState, principalFirst: true, setFk: true, setToPrincipal: true); } [Theory] @@ -706,14 +342,35 @@ public void Add_principal_then_dependent_one_to_many_dep_uni_FK_set_dependent_na [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_many_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many_dep_uni(entityState, principalFirst: true, setFk: false, setToPrincipal: true); + } + + private void Add_principal_and_dependent_one_to_many_dep_uni( + EntityState entityState, bool principalFirst, bool setFk, bool setToPrincipal) { using (var context = new FixupContext()) { var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78, Category = principal }; + var dependent = new ProductDN { Id = 78 }; + if (setFk) + { + dependent.CategoryId = principal.Id; + } + if (setToPrincipal) + { + dependent.Category = principal; + } - context.Entry(principal).State = entityState; + if (principalFirst) + { + context.Entry(principal).State = entityState; + } context.Entry(dependent).State = entityState; + if (!principalFirst) + { + context.Entry(principal).State = entityState; + } AssertFixup( context, @@ -731,7 +388,7 @@ public void Add_principal_then_dependent_one_to_many_dep_uni_FK_not_set_dependen [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_then_principal_one_to_many_no_navs_FK_set_no_navs_set(EntityState entityState) + public void Add_dependent_then_principal_one_to_many_no_navs_FK_set(EntityState entityState) { using (var context = new FixupContext()) { @@ -756,7 +413,7 @@ public void Add_dependent_then_principal_one_to_many_no_navs_FK_set_no_navs_set( [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_then_dependent_one_to_many_no_navs_FK_set_no_navs_set(EntityState entityState) + public void Add_principal_then_dependent_one_to_many_no_navs_FK_set(EntityState entityState) { using (var context = new FixupContext()) { @@ -783,27 +440,8 @@ public void Add_principal_then_dependent_one_to_many_no_navs_FK_set_no_navs_set( [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_FK_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, principal.Id); - dependent.SetParent(principal); - principal.SetChild(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: true, setToPrincipal: true, setToDependent: true); } [Theory] @@ -812,27 +450,8 @@ public void Add_dependent_then_principal_one_to_one_FK_set_both_navs_set(EntityS [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_FK_not_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - dependent.SetParent(principal); - principal.SetChild(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: false, setToPrincipal: true, setToDependent: true); } [Theory] @@ -841,25 +460,8 @@ public void Add_dependent_then_principal_one_to_one_FK_not_set_both_navs_set(Ent [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, principal.Id); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: true, setToPrincipal: false, setToDependent: false); } [Theory] @@ -868,27 +470,9 @@ public void Add_dependent_then_principal_one_to_one_FK_set_no_navs_set(EntitySta [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_FK_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, principal.Id); - principal.SetChild(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: true, setToPrincipal: false, setToDependent: true); + } [Theory] [InlineData(EntityState.Added)] @@ -896,26 +480,8 @@ public void Add_dependent_then_principal_one_to_one_FK_set_principal_nav_set(Ent [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, principal.Id); - dependent.SetParent(principal); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: true, setToPrincipal: true, setToDependent: false); } [Theory] @@ -924,26 +490,8 @@ public void Add_dependent_then_principal_one_to_one_FK_set_dependent_nav_set(Ent [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_FK_not_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - principal.SetChild(dependent); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: false, setToPrincipal: false, setToDependent: true); } [Theory] @@ -952,26 +500,8 @@ public void Add_dependent_then_principal_one_to_one_FK_not_set_principal_nav_set [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_FK_not_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - dependent.SetParent(principal); - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: false, setToPrincipal: true, setToDependent: false); } [Theory] @@ -980,27 +510,8 @@ public void Add_dependent_then_principal_one_to_one_FK_not_set_dependent_nav_set [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_FK_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, principal.Id); - dependent.SetParent(principal); - principal.SetChild(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: true, setFk: true, setToPrincipal: true, setToDependent: true); } [Theory] @@ -1009,27 +520,8 @@ public void Add_principal_then_dependent_one_to_one_FK_set_both_navs_set(EntityS [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_FK_not_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - dependent.SetParent(principal); - principal.SetChild(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: true, setFk: false, setToPrincipal: true, setToDependent: true); } [Theory] @@ -1038,25 +530,8 @@ public void Add_principal_then_dependent_one_to_one_FK_not_set_both_navs_set(Ent [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, principal.Id); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: true, setFk: true, setToPrincipal: false, setToDependent: false); } [Theory] @@ -1065,26 +540,8 @@ public void Add_principal_then_dependent_one_to_one_FK_set_no_navs_set(EntitySta [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_FK_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, principal.Id); - principal.SetChild(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: true, setFk: true, setToPrincipal: false, setToDependent: true); } [Theory] @@ -1093,26 +550,8 @@ public void Add_principal_then_dependent_one_to_one_FK_set_principal_nav_set(Ent [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, principal.Id); - dependent.SetParent(principal); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: true, setFk: true, setToPrincipal: true, setToDependent: false); } [Theory] @@ -1121,26 +560,8 @@ public void Add_principal_then_dependent_one_to_one_FK_set_dependent_nav_set(Ent [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_FK_not_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - principal.SetChild(dependent); - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: true, setFk: false, setToPrincipal: false, setToDependent: true); } [Theory] @@ -1148,15 +569,40 @@ public void Add_principal_then_dependent_one_to_one_FK_not_set_principal_nav_set [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: true, setFk: false, setToPrincipal: true, setToDependent: false); + } + + private void Add_principal_and_dependent_one_to_one( + EntityState entityState, bool principalFirst, bool setFk, bool setToPrincipal, bool setToDependent) { using (var context = new FixupContext()) { var principal = new Parent(77); var dependent = new Child(78, 0); - dependent.SetParent(principal); + if (setFk) + { + dependent.SetParentId(principal.Id); + } + if (setToPrincipal) + { + dependent.SetParent(principal); + } + if (setToDependent) + { + principal.SetChild(dependent); + } - context.Entry(principal).State = entityState; + if (principalFirst) + { + context.Entry(principal).State = entityState; + } context.Entry(dependent).State = entityState; + if (!principalFirst) + { + context.Entry(principal).State = entityState; + } AssertFixup( context, @@ -1177,24 +623,25 @@ public void Add_principal_then_dependent_one_to_one_FK_not_set_dependent_nav_set [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_prin_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78, ParentId = principal.Id }; + Add_principal_and_dependent_one_to_one_prin_uni(entityState, principalFirst: false, setFk: true, setToDependent: false); + } - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_prin_uni_FK_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one_prin_uni(entityState, principalFirst: false, setFk: true, setToDependent: true); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one_prin_uni(entityState, principalFirst: false, setFk: false, setToDependent: true); } [Theory] @@ -1203,121 +650,55 @@ public void Add_dependent_then_principal_one_to_one_prin_uni_FK_set_no_navs_set( [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_prin_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78, ParentId = principal.Id }; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; + Add_principal_and_dependent_one_to_one_prin_uni(entityState, principalFirst: true, setFk: true, setToDependent: false); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_then_dependent_one_to_one_prin_uni_FK_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one_prin_uni(entityState, principalFirst: true, setFk: true, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_then_principal_one_to_one_prin_uni_FK_set_principal_nav_set(EntityState entityState) + public void Add_principal_then_dependent_one_to_one_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one_prin_uni(entityState, principalFirst: true, setFk: false, setToDependent: true); + } + + private void Add_principal_and_dependent_one_to_one_prin_uni( + EntityState entityState, bool principalFirst, bool setFk, bool setToDependent) { using (var context = new FixupContext()) { var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78, ParentId = principal.Id }; - principal.Child = dependent; + var dependent = new ChildPN { Id = 78 }; - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_then_principal_one_to_one_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78 }; - principal.Child = dependent; - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } + if (setFk) + { + dependent.ParentId = principal.Id; + } + if (setToDependent) + { + principal.Child = dependent; + } - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_then_dependent_one_to_one_prin_uni_FK_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78, ParentId = principal.Id }; - principal.Child = dependent; + if (principalFirst) + { + context.Entry(principal).State = entityState; + } - context.Entry(principal).State = entityState; context.Entry(dependent).State = entityState; - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_then_dependent_one_to_one_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78 }; - principal.Child = dependent; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; + if (!principalFirst) + { + context.Entry(principal).State = entityState; + } AssertFixup( context, @@ -1337,24 +718,8 @@ public void Add_principal_then_dependent_one_to_one_prin_uni_FK_not_set_principa [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_dep_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78, ParentId = principal.Id }; - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one_dep_uni( + entityState, principalFirst: false, setFk: true, setToPrincipal: false); } [Theory] @@ -1363,24 +728,8 @@ public void Add_dependent_then_principal_one_to_one_dep_uni_FK_set_no_navs_set(E [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_dep_uni_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78, ParentId = principal.Id, Parent = principal }; - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one_dep_uni( + entityState, principalFirst: false, setFk: true, setToPrincipal: true); } [Theory] @@ -1389,24 +738,8 @@ public void Add_dependent_then_principal_one_to_one_dep_uni_FK_set_dependent_nav [InlineData(EntityState.Unchanged)] public void Add_dependent_then_principal_one_to_one_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78, Parent = principal }; - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one_dep_uni( + entityState, principalFirst: false, setFk: false, setToPrincipal: true); } [Theory] @@ -1415,24 +748,8 @@ public void Add_dependent_then_principal_one_to_one_dep_uni_FK_not_set_dependent [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_dep_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78, ParentId = principal.Id }; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one_dep_uni( + entityState, principalFirst: true, setFk: true, setToPrincipal: false); } [Theory] @@ -1441,24 +758,8 @@ public void Add_principal_then_dependent_one_to_one_dep_uni_FK_set_no_navs_set(E [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_dep_uni_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78, ParentId = principal.Id, Parent = principal }; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one_dep_uni( + entityState, principalFirst: true, setFk: true, setToPrincipal: true); } [Theory] @@ -1467,708 +768,47 @@ public void Add_principal_then_dependent_one_to_one_dep_uni_FK_set_dependent_nav [InlineData(EntityState.Unchanged)] public void Add_principal_then_dependent_one_to_one_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78, Parent = principal }; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_then_principal_one_to_one_no_navs_FK_set_no_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new ParentNN { Id = 77 }; - var dependent = new ChildNN { Id = 78, ParentId = principal.Id }; - - context.Entry(dependent).State = entityState; - context.Entry(principal).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_then_dependent_one_to_one_no_navs_FK_set_no_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new ParentNN { Id = 77 }; - var dependent = new ChildNN { Id = 78, ParentId = principal.Id }; - - context.Entry(principal).State = entityState; - context.Entry(dependent).State = entityState; - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_and_dependent_one_to_one_dep_uni( + entityState, principalFirst: true, setFk: false, setToPrincipal: true); } - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_FK_set_both_navs_set(EntityState entityState) + private void Add_principal_and_dependent_one_to_one_dep_uni( + EntityState entityState, bool principalFirst, bool setFk, bool setToPrincipal) { using (var context = new FixupContext()) { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(dependent).State = entityState; - - dependent.SetCategoryId(principal.Id); - dependent.SetCategory(principal); - principal.AddProduct(dependent); + var principal = new ParentDN { Id = 77 }; + var dependent = new ChildDN { Id = 78 }; - context.ChangeTracker.DetectChanges(); + if (setFk) + { + dependent.ParentId = principal.Id; + } + if (setToPrincipal) + { + dependent.Parent = principal; + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_FK_not_set_both_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(dependent).State = entityState; - - dependent.SetCategory(principal); - principal.AddProduct(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_FK_set_no_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(dependent).State = entityState; - - dependent.SetCategoryId(principal.Id); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Null(dependent.Category); - Assert.Null(principal.Products); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_FK_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(dependent).State = entityState; - - dependent.SetCategoryId(principal.Id); - principal.AddProduct(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Null(dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_FK_set_dependent_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(dependent).State = entityState; - - dependent.SetCategoryId(principal.Id); - dependent.SetCategory(principal); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_FK_not_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(dependent).State = entityState; - - principal.AddProduct(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(0, dependent.CategoryId); - Assert.Null(dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_FK_not_set_dependent_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(dependent).State = entityState; - - dependent.SetCategory(principal); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_FK_set_both_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(principal).State = entityState; - - dependent.SetCategoryId(principal.Id); - dependent.SetCategory(principal); - principal.AddProduct(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_FK_not_set_both_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(principal).State = entityState; - - dependent.SetCategory(principal); - principal.AddProduct(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_FK_set_no_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(principal).State = entityState; - - dependent.SetCategoryId(principal.Id); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Null(dependent.Category); - Assert.Null(principal.Products); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_FK_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(principal).State = entityState; - - dependent.SetCategoryId(principal.Id); - principal.AddProduct(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_FK_set_dependent_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(principal).State = entityState; - - dependent.SetCategoryId(principal.Id); - dependent.SetCategory(principal); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Null(principal.Products); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_FK_not_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(principal).State = entityState; - - dependent.SetCategoryId(principal.Id); - principal.AddProduct(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_FK_not_set_dependent_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new Category(77); - var dependent = new Product(77, 0); - - context.Entry(principal).State = entityState; - - dependent.SetCategory(principal); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(0, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Null(principal.Products); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_prin_uni_FK_set_no_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78 }; - - context.Entry(dependent).State = entityState; - - dependent.CategoryId = principal.Id; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Empty(principal.Products); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_prin_uni_FK_set_no_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78 }; - - context.Entry(principal).State = entityState; - - dependent.CategoryId = principal.Id; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Empty(principal.Products); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_prin_uni_FK_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78 }; - - context.Entry(dependent).State = entityState; - - dependent.CategoryId = principal.Id; - principal.Products.Add(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78 }; - - context.Entry(dependent).State = entityState; - - principal.Products.Add(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(0, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_prin_uni_FK_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78 }; - - context.Entry(principal).State = entityState; - - dependent.CategoryId = principal.Id; - principal.Products.Add(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new CategoryPN { Id = 77 }; - var dependent = new ProductPN { Id = 78 }; - - context.Entry(principal).State = entityState; - - principal.Products.Add(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(new[] { dependent }.ToList(), principal.Products.ToList()); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } - } - - [Theory] - [InlineData(EntityState.Added)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_set_no_navs_set(EntityState entityState) - { - using (var context = new FixupContext()) - { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78 }; + if (principalFirst) + { + context.Entry(principal).State = entityState; + } context.Entry(dependent).State = entityState; - dependent.CategoryId = principal.Id; - - context.ChangeTracker.DetectChanges(); + if (!principalFirst) + { + context.Entry(principal).State = entityState; + } AssertFixup( context, () => { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Null(dependent.Category); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); + Assert.Equal(principal.Id, dependent.ParentId); + Assert.Same(principal, dependent.Parent); + Assert.Equal(entityState, context.Entry(principal).State); + Assert.Equal(entityState, context.Entry(dependent).State); }); } } @@ -2177,28 +817,23 @@ public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_set_no_navs_s [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_set_dependent_nav_set(EntityState entityState) + public void Add_dependent_then_principal_one_to_one_no_navs_FK_set(EntityState entityState) { using (var context = new FixupContext()) { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78 }; + var principal = new ParentNN { Id = 77 }; + var dependent = new ChildNN { Id = 78, ParentId = principal.Id }; context.Entry(dependent).State = entityState; - - dependent.CategoryId = principal.Id; - dependent.Category = principal; - - context.ChangeTracker.DetectChanges(); + context.Entry(principal).State = entityState; AssertFixup( context, () => { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); + Assert.Equal(principal.Id, dependent.ParentId); + Assert.Equal(entityState, context.Entry(principal).State); + Assert.Equal(entityState, context.Entry(dependent).State); }); } } @@ -2207,27 +842,23 @@ public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_set_dependent [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) + public void Add_principal_then_dependent_one_to_one_no_navs_FK_set(EntityState entityState) { using (var context = new FixupContext()) { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78 }; + var principal = new ParentNN { Id = 77 }; + var dependent = new ChildNN { Id = 78, ParentId = principal.Id }; + context.Entry(principal).State = entityState; context.Entry(dependent).State = entityState; - dependent.Category = principal; - - context.ChangeTracker.DetectChanges(); - AssertFixup( context, () => { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); + Assert.Equal(principal.Id, dependent.ParentId); + Assert.Equal(entityState, context.Entry(principal).State); + Assert.Equal(entityState, context.Entry(dependent).State); }); } } @@ -2236,75 +867,87 @@ public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_not_set_depen [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_dep_uni_FK_set_no_navs_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_many_FK_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78 }; - - context.Entry(principal).State = entityState; - - dependent.CategoryId = principal.Id; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Null(dependent.Category); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } + Add_dependent_but_not_principal_one_to_many(entityState, setFk: true, setToPrincipal: true, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_dep_uni_FK_set_dependent_nav_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_many_FK_not_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78 }; + Add_dependent_but_not_principal_one_to_many(entityState, setFk: false, setToPrincipal: true, setToDependent: true); + } - context.Entry(principal).State = entityState; + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_many_FK_set_no_navs_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many(entityState, setFk: true, setToPrincipal: false, setToDependent: false); + } - dependent.CategoryId = principal.Id; - dependent.Category = principal; + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_many_FK_set_principal_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many(entityState, setFk: true, setToPrincipal: false, setToDependent: true); + } - context.ChangeTracker.DetectChanges(); + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_many_FK_set_dependent_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many(entityState, setFk: true, setToPrincipal: true, setToDependent: false); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_many_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many(entityState, setFk: false, setToPrincipal: false, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_many_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many(entityState, setFk: false, setToPrincipal: true, setToDependent: false); + } + + private void Add_dependent_but_not_principal_one_to_many( + EntityState entityState, bool setFk, bool setToPrincipal, bool setToDependent) { using (var context = new FixupContext()) { - var principal = new CategoryDN { Id = 77 }; - var dependent = new ProductDN { Id = 78 }; + var principal = new Category(77); + var dependent = new Product(77, 0); - context.Entry(principal).State = entityState; + context.Entry(dependent).State = entityState; - dependent.Category = principal; + if (setFk) + { + dependent.SetCategoryId(principal.Id); + } + if (setToPrincipal) + { + dependent.SetCategory(principal); + } + if (setToDependent) + { + principal.AddProduct(dependent); + } context.ChangeTracker.DetectChanges(); @@ -2312,10 +955,12 @@ public void Add_principal_but_not_dependent_one_to_many_dep_uni_FK_not_set_depen context, () => { - Assert.Equal(0, dependent.CategoryId); - Assert.Same(principal, dependent.Category); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); + Assert.Equal(setToPrincipal || setFk ? principal.Id : 0, dependent.CategoryId); + Assert.Same(setToPrincipal ? principal : null, dependent.Category); + Assert.Equal(setToPrincipal || setToDependent ? new[] { dependent } : null, principal.Products); + Assert.Equal(setToPrincipal ? EntityState.Added : EntityState.Detached, context.Entry(principal).State); + Assert.Equal(entityState == EntityState.Unchanged && (setToPrincipal || setFk) ? EntityState.Modified : entityState, + context.Entry(dependent).State); }); } } @@ -2324,44 +969,87 @@ public void Add_principal_but_not_dependent_one_to_many_dep_uni_FK_not_set_depen [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_many_no_navs_FK_set_no_navs_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_many_FK_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new CategoryNN { Id = 77 }; - var dependent = new ProductNN { Id = 78 }; + Add_principal_but_not_dependent_one_to_many(entityState, setFk: true, setToPrincipal: true, setToDependent: true); + } - context.Entry(dependent).State = entityState; + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_many_FK_not_set_both_navs_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many(entityState, setFk: false, setToPrincipal: true, setToDependent: true); + } - dependent.CategoryId = principal.Id; + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_many_FK_set_no_navs_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many(entityState, setFk: true, setToPrincipal: false, setToDependent: false); + } - context.ChangeTracker.DetectChanges(); + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_many_FK_set_principal_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many(entityState, setFk: true, setToPrincipal: false, setToDependent: true); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.CategoryId); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_many_FK_set_dependent_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many(entityState, setFk: true, setToPrincipal: true, setToDependent: false); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_many_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many(entityState, setFk: false, setToPrincipal: false, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_many_no_navs_FK_set_no_navs_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_many_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many(entityState, setFk: false, setToPrincipal: true, setToDependent: false); + } + + private void Add_principal_but_not_dependent_one_to_many( + EntityState entityState, bool setFk, bool setToPrincipal, bool setToDependent) { using (var context = new FixupContext()) { - var principal = new CategoryNN { Id = 77 }; - var dependent = new ProductNN { Id = 78 }; + var principal = new Category(77); + var dependent = new Product(77, 0); context.Entry(principal).State = entityState; - dependent.CategoryId = principal.Id; + if (setFk) + { + dependent.SetCategoryId(principal.Id); + } + if (setToPrincipal) + { + dependent.SetCategory(principal); + } + if (setToDependent) + { + principal.AddProduct(dependent); + } context.ChangeTracker.DetectChanges(); @@ -2369,9 +1057,11 @@ public void Add_principal_but_not_dependent_one_to_many_no_navs_FK_set_no_navs_s context, () => { - Assert.Equal(principal.Id, dependent.CategoryId); + Assert.Equal(setToDependent || setFk ? principal.Id : 0, dependent.CategoryId); + Assert.Same(setToDependent || setToPrincipal ? principal : null, dependent.Category); + Assert.Equal(setToDependent ? new[] { dependent } : null, principal.Products); Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); + Assert.Equal(setToDependent ? EntityState.Added : EntityState.Detached, context.Entry(dependent).State); }); } } @@ -2380,18 +1070,46 @@ public void Add_principal_but_not_dependent_one_to_many_no_navs_FK_set_no_navs_s [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_FK_set_both_navs_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_many_prin_uni_FK_set_no_navs_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many_prin_uni(entityState, setFk: true, setToDependent: false); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_many_prin_uni_FK_set_principal_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many_prin_uni(entityState, setFk: true, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_many_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many_prin_uni(entityState, setFk: false, setToDependent: true); + } + + private void Add_dependent_but_not_principal_one_to_many_prin_uni(EntityState entityState, bool setFk, bool setToDependent) { using (var context = new FixupContext()) { - var principal = new Parent(77); - var dependent = new Child(78, 0); + var principal = new CategoryPN { Id = 77 }; + var dependent = new ProductPN { Id = 78 }; context.Entry(dependent).State = entityState; - dependent.SetParentId(principal.Id); - dependent.SetParent(principal); - principal.SetChild(dependent); + if (setFk) + { + dependent.CategoryId = principal.Id; + } + if (setToDependent) + { + principal.Products.Add(dependent); + } context.ChangeTracker.DetectChanges(); @@ -2399,11 +1117,11 @@ public void Add_dependent_but_not_principal_one_to_one_FK_set_both_navs_set(Enti context, () => { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); + Assert.Equal(setFk ? principal.Id : 0, dependent.CategoryId); + Assert.Equal(setToDependent ? new[] { dependent } : new ProductPN[0], principal.Products); + Assert.Equal(EntityState.Detached, context.Entry(principal).State); + Assert.Equal(entityState == EntityState.Unchanged && setFk ? EntityState.Modified : entityState, + context.Entry(dependent).State); }); } } @@ -2412,17 +1130,46 @@ public void Add_dependent_but_not_principal_one_to_one_FK_set_both_navs_set(Enti [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_FK_not_set_both_navs_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_many_prin_uni_FK_set_no_navs_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many_prin_uni(entityState, setFk: true, setToDependent: false); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_many_prin_uni_FK_set_principal_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many_prin_uni(entityState, setFk: true, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_many_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many_prin_uni(entityState, setFk: false, setToDependent: true); + } + + private void Add_principal_but_not_dependent_one_to_many_prin_uni(EntityState entityState, bool setFk, bool setToDependent) { using (var context = new FixupContext()) { - var principal = new Parent(77); - var dependent = new Child(78, 0); + var principal = new CategoryPN { Id = 77 }; + var dependent = new ProductPN { Id = 78 }; - context.Entry(dependent).State = entityState; + context.Entry(principal).State = entityState; - dependent.SetParent(principal); - principal.SetChild(dependent); + if (setFk) + { + dependent.CategoryId = principal.Id; + } + if (setToDependent) + { + principal.Products.Add(dependent); + } context.ChangeTracker.DetectChanges(); @@ -2430,11 +1177,10 @@ public void Add_dependent_but_not_principal_one_to_one_FK_not_set_both_navs_set( context, () => { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); + Assert.Equal(principal.Id, dependent.CategoryId); + Assert.Equal(setToDependent ? new[] { dependent } : new ProductPN[0], principal.Products); + Assert.Equal(entityState, context.Entry(principal).State); + Assert.Equal(setToDependent ? EntityState.Added : EntityState.Detached, context.Entry(dependent).State); }); } } @@ -2443,47 +1189,46 @@ public void Add_dependent_but_not_principal_one_to_one_FK_not_set_both_navs_set( [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_FK_set_no_navs_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - - context.Entry(dependent).State = entityState; - - dependent.SetParentId(principal.Id); - - context.ChangeTracker.DetectChanges(); + Add_dependent_but_not_principal_one_to_many_dep_uni(entityState, setFk: true, setToPrincipal: false); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Null(dependent.Parent); - Assert.Null(principal.Child); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_set_dependent_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many_dep_uni(entityState, setFk: true, setToPrincipal: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_FK_set_principal_nav_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_many_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_many_dep_uni(entityState, setFk: false, setToPrincipal: true); + } + + private void Add_dependent_but_not_principal_one_to_many_dep_uni(EntityState entityState, bool setFk, bool setToPrincipal) { using (var context = new FixupContext()) { - var principal = new Parent(77); - var dependent = new Child(78, 0); + var principal = new CategoryDN { Id = 77 }; + var dependent = new ProductDN { Id = 78 }; context.Entry(dependent).State = entityState; - dependent.SetParentId(principal.Id); - principal.SetChild(dependent); + if (setFk) + { + dependent.CategoryId = principal.Id; + } + if (setToPrincipal) + { + dependent.Category = principal; + } context.ChangeTracker.DetectChanges(); @@ -2491,11 +1236,11 @@ public void Add_dependent_but_not_principal_one_to_one_FK_set_principal_nav_set( context, () => { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Null(dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); + Assert.Equal(principal.Id, dependent.CategoryId); + Assert.Same(setToPrincipal ? principal : null, dependent.Category); + Assert.Equal(setToPrincipal ? EntityState.Added : EntityState.Detached, context.Entry(principal).State); + Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, + context.Entry(dependent).State); }); } } @@ -2504,47 +1249,46 @@ public void Add_dependent_but_not_principal_one_to_one_FK_set_principal_nav_set( [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_FK_set_dependent_nav_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_many_dep_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - - context.Entry(dependent).State = entityState; - - dependent.SetParentId(principal.Id); - dependent.SetParent(principal); - - context.ChangeTracker.DetectChanges(); + Add_principal_but_not_dependent_one_to_many_dep_uni(entityState, setFk: true, setToPrincipal: false); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_many_dep_uni_FK_set_dependent_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many_dep_uni(entityState, setFk: true, setToPrincipal: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_FK_not_set_principal_nav_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_many_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_many_dep_uni(entityState, setFk: false, setToPrincipal: true); + } + + private void Add_principal_but_not_dependent_one_to_many_dep_uni(EntityState entityState, bool setFk, bool setToPrincipal) { using (var context = new FixupContext()) { - var principal = new Parent(77); - var dependent = new Child(78, 0); + var principal = new CategoryDN { Id = 77 }; + var dependent = new ProductDN { Id = 78 }; - context.Entry(dependent).State = entityState; + context.Entry(principal).State = entityState; - principal.SetChild(dependent); + if (setFk) + { + dependent.CategoryId = principal.Id; + } + if (setToPrincipal) + { + dependent.Category = principal; + } context.ChangeTracker.DetectChanges(); @@ -2552,11 +1296,10 @@ public void Add_dependent_but_not_principal_one_to_one_FK_not_set_principal_nav_ context, () => { - Assert.Equal(0, dependent.ParentId); - Assert.Null(dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); + Assert.Equal(setFk ? principal.Id : 0, dependent.CategoryId); + Assert.Same(setToPrincipal ? principal : null, dependent.Category); + Assert.Equal(entityState, context.Entry(principal).State); + Assert.Equal(EntityState.Detached, context.Entry(dependent).State); }); } } @@ -2565,16 +1308,16 @@ public void Add_dependent_but_not_principal_one_to_one_FK_not_set_principal_nav_ [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_FK_not_set_dependent_nav_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_many_no_navs_FK_set(EntityState entityState) { using (var context = new FixupContext()) { - var principal = new Parent(77); - var dependent = new Child(78, 0); + var principal = new CategoryNN { Id = 77 }; + var dependent = new ProductNN { Id = 78 }; context.Entry(dependent).State = entityState; - dependent.SetParent(principal); + dependent.CategoryId = principal.Id; context.ChangeTracker.DetectChanges(); @@ -2582,10 +1325,8 @@ public void Add_dependent_but_not_principal_one_to_one_FK_not_set_dependent_nav_ context, () => { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(EntityState.Added, context.Entry(principal).State); + Assert.Equal(principal.Id, dependent.CategoryId); + Assert.Equal(EntityState.Detached, context.Entry(principal).State); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); }); } @@ -2595,18 +1336,16 @@ public void Add_dependent_but_not_principal_one_to_one_FK_not_set_dependent_nav_ [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_one_FK_set_both_navs_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_many_no_navs_FK_set(EntityState entityState) { using (var context = new FixupContext()) { - var principal = new Parent(77); - var dependent = new Child(78, 0); + var principal = new CategoryNN { Id = 77 }; + var dependent = new ProductNN { Id = 78 }; context.Entry(principal).State = entityState; - dependent.SetParentId(principal.Id); - dependent.SetParent(principal); - principal.SetChild(dependent); + dependent.CategoryId = principal.Id; context.ChangeTracker.DetectChanges(); @@ -2614,11 +1353,9 @@ public void Add_principal_but_not_dependent_one_to_one_FK_set_both_navs_set(Enti context, () => { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); + Assert.Equal(principal.Id, dependent.CategoryId); Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); + Assert.Equal(EntityState.Detached, context.Entry(dependent).State); }); } } @@ -2627,78 +1364,87 @@ public void Add_principal_but_not_dependent_one_to_one_FK_set_both_navs_set(Enti [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_one_FK_not_set_both_navs_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_one_FK_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - - context.Entry(principal).State = entityState; - - dependent.SetParent(principal); - principal.SetChild(dependent); - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } + Add_dependent_but_not_principal_one_to_one(entityState, setFk: true, setToPrincipal: true, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_one_FK_set_no_navs_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_one_FK_not_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); + Add_dependent_but_not_principal_one_to_one(entityState, setFk: false, setToPrincipal: true, setToDependent: true); + } - context.Entry(principal).State = entityState; + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_one_FK_set_no_navs_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_one(entityState, setFk: true, setToPrincipal: false, setToDependent: false); + } - dependent.SetParentId(principal.Id); + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_one_FK_set_principal_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_one(entityState, setFk: true, setToPrincipal: false, setToDependent: true); + } - context.ChangeTracker.DetectChanges(); + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_one_FK_set_dependent_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_one(entityState, setFk: true, setToPrincipal: true, setToDependent: false); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Null(dependent.Parent); - Assert.Null(principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_but_not_principal_one_to_one_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_one(entityState, setFk: false, setToPrincipal: false, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_one_FK_set_principal_nav_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_one_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_one(entityState, setFk: false, setToPrincipal: true, setToDependent: false); + } + + private void Add_dependent_but_not_principal_one_to_one( + EntityState entityState, bool setFk, bool setToPrincipal, bool setToDependent) { using (var context = new FixupContext()) { var principal = new Parent(77); var dependent = new Child(78, 0); - context.Entry(principal).State = entityState; + context.Entry(dependent).State = entityState; - dependent.SetParentId(principal.Id); - principal.SetChild(dependent); + if (setFk) + { + dependent.SetParentId(principal.Id); + } + if (setToPrincipal) + { + dependent.SetParent(principal); + } + if (setToDependent) + { + principal.SetChild(dependent); + } context.ChangeTracker.DetectChanges(); @@ -2706,11 +1452,13 @@ public void Add_principal_but_not_dependent_one_to_one_FK_set_principal_nav_set( context, () => { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); + Assert.Equal(setToPrincipal || setFk ? principal.Id : 0, dependent.ParentId); + Assert.Same(setToPrincipal ? principal : null, dependent.Parent); + Assert.Same(setToPrincipal || setToDependent ? dependent : null, principal.Child); + Assert.Equal(setToPrincipal ? EntityState.Added : EntityState.Detached, context.Entry(principal).State); + Assert.Equal(entityState == EntityState.Unchanged && (setFk || setToPrincipal) + ? EntityState.Modified : entityState, + context.Entry(dependent).State); }); } } @@ -2719,61 +1467,60 @@ public void Add_principal_but_not_dependent_one_to_one_FK_set_principal_nav_set( [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_one_FK_set_dependent_nav_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_one_FK_set_both_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - - context.Entry(principal).State = entityState; - - dependent.SetParentId(principal.Id); - dependent.SetParent(principal); + Add_principal_but_not_dependent_one_to_one( + entityState, setFk: true, setToPrincipal: true, setToDependent: true); + } - context.ChangeTracker.DetectChanges(); + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_one_FK_not_set_both_navs_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_one( + entityState, setFk: false, setToPrincipal: true, setToDependent: true); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Null(principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_one_FK_set_no_navs_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_one( + entityState, setFk: true, setToPrincipal: false, setToDependent: false); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_one_FK_not_set_principal_nav_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_one_FK_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new Parent(77); - var dependent = new Child(78, 0); - - context.Entry(principal).State = entityState; - - principal.SetChild(dependent); + Add_principal_but_not_dependent_one_to_one( + entityState, setFk: true, setToPrincipal: false, setToDependent: true); + } - context.ChangeTracker.DetectChanges(); + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_one_FK_set_dependent_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_one( + entityState, setFk: true, setToPrincipal: true, setToDependent: false); + } - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_principal_but_not_dependent_one_to_one_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_one( + entityState, setFk: false, setToPrincipal: false, setToDependent: true); } [Theory] @@ -2781,6 +1528,13 @@ public void Add_principal_but_not_dependent_one_to_one_FK_not_set_principal_nav_ [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] public void Add_principal_but_not_dependent_one_to_one_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_one( + entityState, setFk: false, setToPrincipal: true, setToDependent: false); + } + + private void Add_principal_but_not_dependent_one_to_one( + EntityState entityState, bool setFk, bool setToPrincipal, bool setToDependent) { using (var context = new FixupContext()) { @@ -2789,7 +1543,18 @@ public void Add_principal_but_not_dependent_one_to_one_FK_not_set_dependent_nav_ context.Entry(principal).State = entityState; - dependent.SetParent(principal); + if (setFk) + { + dependent.SetParentId(principal.Id); + } + if (setToPrincipal) + { + dependent.SetParent(principal); + } + if (setToDependent) + { + principal.SetChild(dependent); + } context.ChangeTracker.DetectChanges(); @@ -2797,11 +1562,11 @@ public void Add_principal_but_not_dependent_one_to_one_FK_not_set_dependent_nav_ context, () => { - Assert.Equal(0, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Null(principal.Child); + Assert.Equal(setToDependent || setFk ? principal.Id : 0, dependent.ParentId); + Assert.Same(setToDependent || setToPrincipal ? principal : null, dependent.Parent); + Assert.Same(setToDependent ? dependent : null, principal.Child); Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); + Assert.Equal(setToDependent ? EntityState.Added : EntityState.Detached, context.Entry(dependent).State); }); } } @@ -2812,63 +1577,28 @@ public void Add_principal_but_not_dependent_one_to_one_FK_not_set_dependent_nav_ [InlineData(EntityState.Unchanged)] public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78 }; - - context.Entry(dependent).State = entityState; - - dependent.ParentId = principal.Id; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Null(principal.Child); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } + Add_dependent_but_not_principal_one_to_one_prin_uni(entityState, setFk: true, setToDependent: false); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_one_prin_uni_FK_set_no_navs_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78 }; - - context.Entry(principal).State = entityState; - - dependent.ParentId = principal.Id; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Null(principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } + Add_dependent_but_not_principal_one_to_one_prin_uni(entityState, setFk: true, setToDependent: true); } [Theory] [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_set_principal_nav_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_one_prin_uni(entityState, setFk: false, setToDependent: true); + } + + private void Add_dependent_but_not_principal_one_to_one_prin_uni(EntityState entityState, bool setFk, bool setToDependent) { using (var context = new FixupContext()) { @@ -2877,8 +1607,14 @@ public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_set_principal context.Entry(dependent).State = entityState; - dependent.ParentId = principal.Id; - principal.Child = dependent; + if (setFk) + { + dependent.ParentId = principal.Id; + } + if (setToDependent) + { + principal.Child = dependent; + } context.ChangeTracker.DetectChanges(); @@ -2886,10 +1622,11 @@ public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_set_principal context, () => { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(dependent, principal.Child); + Assert.Equal(setFk ? principal.Id : 0, dependent.ParentId); + Assert.Same(setToDependent ? dependent : null, principal.Child); Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); + Assert.Equal(entityState == EntityState.Unchanged && setFk ? EntityState.Modified : entityState, + context.Entry(dependent).State); }); } } @@ -2898,29 +1635,9 @@ public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_set_principal [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_one_prin_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78 }; - - context.Entry(dependent).State = entityState; - - principal.Child = dependent; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(0, dependent.ParentId); - Assert.Same(dependent, principal.Child); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState, context.Entry(dependent).State); - }); - } + Add_principal_but_not_dependent_one_to_one_prin_uni(entityState, setFk: true, setToDependent: false); } [Theory] @@ -2929,28 +1646,7 @@ public void Add_dependent_but_not_principal_one_to_one_prin_uni_FK_not_set_princ [InlineData(EntityState.Unchanged)] public void Add_principal_but_not_dependent_one_to_one_prin_uni_FK_set_principal_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentPN { Id = 77 }; - var dependent = new ChildPN { Id = 78 }; - - context.Entry(principal).State = entityState; - - dependent.ParentId = principal.Id; - principal.Child = dependent; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(dependent, principal.Child); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); - }); - } + Add_principal_but_not_dependent_one_to_one_prin_uni(entityState, setFk: true, setToDependent: true); } [Theory] @@ -2958,6 +1654,11 @@ public void Add_principal_but_not_dependent_one_to_one_prin_uni_FK_set_principal [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] public void Add_principal_but_not_dependent_one_to_one_prin_uni_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_one_prin_uni(entityState, setFk: false, setToDependent: true); + } + + private void Add_principal_but_not_dependent_one_to_one_prin_uni(EntityState entityState, bool setFk, bool setToDependent) { using (var context = new FixupContext()) { @@ -2966,7 +1667,14 @@ public void Add_principal_but_not_dependent_one_to_one_prin_uni_FK_not_set_princ context.Entry(principal).State = entityState; - principal.Child = dependent; + if (setFk) + { + dependent.ParentId = principal.Id; + } + if (setToDependent) + { + principal.Child = dependent; + } context.ChangeTracker.DetectChanges(); @@ -2975,9 +1683,9 @@ public void Add_principal_but_not_dependent_one_to_one_prin_uni_FK_not_set_princ () => { Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(dependent, principal.Child); + Assert.Same(setToDependent ? dependent : null, principal.Child); Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Added, context.Entry(dependent).State); + Assert.Equal(setToDependent ? EntityState.Added : EntityState.Detached, context.Entry(dependent).State); }); } } @@ -2988,27 +1696,7 @@ public void Add_principal_but_not_dependent_one_to_one_prin_uni_FK_not_set_princ [InlineData(EntityState.Unchanged)] public void Add_dependent_but_not_principal_one_to_one_dep_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78 }; - - context.Entry(dependent).State = entityState; - - dependent.ParentId = principal.Id; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Null(dependent.Parent); - Assert.Equal(EntityState.Detached, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } + Add_dependent_but_not_principal_one_to_one_dep_uni(entityState, setFk: true, setToPrincipal: false); } [Theory] @@ -3017,28 +1705,7 @@ public void Add_dependent_but_not_principal_one_to_one_dep_uni_FK_set_no_navs_se [InlineData(EntityState.Unchanged)] public void Add_dependent_but_not_principal_one_to_one_dep_uni_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78 }; - - context.Entry(dependent).State = entityState; - - dependent.ParentId = principal.Id; - dependent.Parent = principal; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(EntityState.Added, context.Entry(principal).State); - Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); - }); - } + Add_dependent_but_not_principal_one_to_one_dep_uni(entityState, setFk: true, setToPrincipal: true); } [Theory] @@ -3046,6 +1713,11 @@ public void Add_dependent_but_not_principal_one_to_one_dep_uni_FK_set_dependent_ [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] public void Add_dependent_but_not_principal_one_to_one_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_dependent_but_not_principal_one_to_one_dep_uni(entityState, setFk: false, setToPrincipal: true); + } + + private void Add_dependent_but_not_principal_one_to_one_dep_uni(EntityState entityState, bool setFk, bool setToPrincipal) { using (var context = new FixupContext()) { @@ -3054,7 +1726,14 @@ public void Add_dependent_but_not_principal_one_to_one_dep_uni_FK_not_set_depend context.Entry(dependent).State = entityState; - dependent.Parent = principal; + if (setFk) + { + dependent.ParentId = principal.Id; + } + if (setToPrincipal) + { + dependent.Parent = principal; + } context.ChangeTracker.DetectChanges(); @@ -3063,8 +1742,8 @@ public void Add_dependent_but_not_principal_one_to_one_dep_uni_FK_not_set_depend () => { Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(EntityState.Added, context.Entry(principal).State); + Assert.Same(setToPrincipal ? principal : null, dependent.Parent); + Assert.Equal(setToPrincipal ? EntityState.Added : EntityState.Detached, context.Entry(principal).State); Assert.Equal(entityState == EntityState.Added ? EntityState.Added : EntityState.Modified, context.Entry(dependent).State); }); } @@ -3076,27 +1755,7 @@ public void Add_dependent_but_not_principal_one_to_one_dep_uni_FK_not_set_depend [InlineData(EntityState.Unchanged)] public void Add_principal_but_not_dependent_one_to_one_dep_uni_FK_set_no_navs_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78 }; - - context.Entry(principal).State = entityState; - - dependent.ParentId = principal.Id; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Null(dependent.Parent); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } + Add_principal_but_not_dependent_one_to_one_dep_uni(entityState, setFk: true, setToPrincipal: false); } [Theory] @@ -3105,28 +1764,7 @@ public void Add_principal_but_not_dependent_one_to_one_dep_uni_FK_set_no_navs_se [InlineData(EntityState.Unchanged)] public void Add_principal_but_not_dependent_one_to_one_dep_uni_FK_set_dependent_nav_set(EntityState entityState) { - using (var context = new FixupContext()) - { - var principal = new ParentDN { Id = 77 }; - var dependent = new ChildDN { Id = 78 }; - - context.Entry(principal).State = entityState; - - dependent.ParentId = principal.Id; - dependent.Parent = principal; - - context.ChangeTracker.DetectChanges(); - - AssertFixup( - context, - () => - { - Assert.Equal(principal.Id, dependent.ParentId); - Assert.Same(principal, dependent.Parent); - Assert.Equal(entityState, context.Entry(principal).State); - Assert.Equal(EntityState.Detached, context.Entry(dependent).State); - }); - } + Add_principal_but_not_dependent_one_to_one_dep_uni(entityState, setFk: true, setToPrincipal: true); } [Theory] @@ -3134,6 +1772,11 @@ public void Add_principal_but_not_dependent_one_to_one_dep_uni_FK_set_dependent_ [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] public void Add_principal_but_not_dependent_one_to_one_dep_uni_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_but_not_dependent_one_to_one_dep_uni(entityState, setFk: false, setToPrincipal: true); + } + + private void Add_principal_but_not_dependent_one_to_one_dep_uni(EntityState entityState, bool setFk, bool setToPrincipal) { using (var context = new FixupContext()) { @@ -3142,7 +1785,14 @@ public void Add_principal_but_not_dependent_one_to_one_dep_uni_FK_not_set_depend context.Entry(principal).State = entityState; - dependent.Parent = principal; + if (setFk) + { + dependent.ParentId = principal.Id; + } + if (setToPrincipal) + { + dependent.Parent = principal; + } context.ChangeTracker.DetectChanges(); @@ -3150,8 +1800,8 @@ public void Add_principal_but_not_dependent_one_to_one_dep_uni_FK_not_set_depend context, () => { - Assert.Equal(0, dependent.ParentId); - Assert.Same(principal, dependent.Parent); + Assert.Equal(setFk ? principal.Id : 0, dependent.ParentId); + Assert.Same(setToPrincipal ? principal : null, dependent.Parent); Assert.Equal(entityState, context.Entry(principal).State); Assert.Equal(EntityState.Detached, context.Entry(dependent).State); }); @@ -3162,7 +1812,7 @@ public void Add_principal_but_not_dependent_one_to_one_dep_uni_FK_not_set_depend [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_dependent_but_not_principal_one_to_one_no_navs_FK_set_no_navs_set(EntityState entityState) + public void Add_dependent_but_not_principal_one_to_one_no_navs_FK_set(EntityState entityState) { using (var context = new FixupContext()) { @@ -3190,7 +1840,7 @@ public void Add_dependent_but_not_principal_one_to_one_no_navs_FK_set_no_navs_se [InlineData(EntityState.Added)] [InlineData(EntityState.Modified)] [InlineData(EntityState.Unchanged)] - public void Add_principal_but_not_dependent_one_to_one_no_navs_FK_set_no_navs_set(EntityState entityState) + public void Add_principal_but_not_dependent_one_to_one_no_navs_FK_set(EntityState entityState) { using (var context = new FixupContext()) { @@ -3524,7 +2174,7 @@ private void Replace_dependent_one_to_one_dep_uni( [InlineData(EntityState.Modified, EntityState.Unchanged)] [InlineData(EntityState.Unchanged, EntityState.Added)] [InlineData(EntityState.Unchanged, EntityState.Modified)] - public void Replace_dependent_one_to_one_no_navs_FK_set_no_navs_set(EntityState oldEntityState, EntityState newEntityState) + public void Replace_dependent_one_to_one_no_navs_FK_set(EntityState oldEntityState, EntityState newEntityState) { using (var context = new FixupContext()) { @@ -3772,7 +2422,7 @@ public void Navigation_fixup_is_non_destructive_to_existing_graphs() } } - private void AssertAllFixedUp(DbContext context) + protected virtual void AssertAllFixedUp(DbContext context) { foreach (var entry in context.ChangeTracker.Entries()) { @@ -4148,6 +2798,7 @@ private class TestAssembly [Key] public string Name { get; set; } + // ReSharper disable once CollectionNeverUpdated.Local public ICollection Classes { get; } = new List(); } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs index c9d1c844096..3137852c0b3 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs @@ -15,6 +15,8 @@ using Microsoft.Extensions.DependencyInjection; using Xunit; +// ReSharper disable UnusedAutoPropertyAccessor.Local +// ReSharper disable AssignNullToNotNullAttribute // ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { @@ -1296,6 +1298,7 @@ public void Unchanged_entity_with_conceptually_null_non_FK_property_throws() Assert.Throws(() => entry.HandleConceptualNulls()).Message); } + // ReSharper disable once ClassNeverInstantiated.Local private class Root { public int Id { get; set; } @@ -1303,6 +1306,7 @@ private class Root public FirstDependent First { get; set; } } + // ReSharper disable once ClassNeverInstantiated.Local private class FirstDependent { public int Id { get; set; } @@ -1319,6 +1323,7 @@ private class SecondDependent public FirstDependent First { get; set; } } + // ReSharper disable once ClassNeverInstantiated.Local private class CompositeRoot { public int Id1 { get; set; } @@ -1327,6 +1332,7 @@ private class CompositeRoot public CompositeFirstDependent First { get; set; } } + // ReSharper disable once ClassNeverInstantiated.Local private class CompositeFirstDependent { public int Id1 { get; set; } @@ -1334,6 +1340,7 @@ private class CompositeFirstDependent public int RootId1 { get; set; } public string RootId2 { get; set; } + // ReSharper disable once MemberHidesStaticFromOuterClass public CompositeRoot Root { get; set; } public CompositeSecondDependent Second { get; set; } @@ -1471,7 +1478,7 @@ protected class FullNotificationEntity : INotifyPropertyChanging, INotifyPropert public int Id { - get { return _id; } + get => _id; set { if (_id != value) @@ -1485,7 +1492,7 @@ public int Id public string Name { - get { return _name; } + get => _name; set { if (_name != value) @@ -1514,7 +1521,7 @@ protected class ChangedOnlyEntity : INotifyPropertyChanged, ISomeEntity public int Id { - get { return _id; } + get => _id; set { if (_id != value) @@ -1527,7 +1534,7 @@ public int Id public string Name { - get { return _name; } + get => _name; set { if (_name != value) diff --git a/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs index 5b189c6e3fd..e56dfa11224 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/OwnedFixupTest.cs @@ -10,6 +10,9 @@ using Microsoft.EntityFrameworkCore.Metadata.Internal; using Xunit; +// ReSharper disable UnusedAutoPropertyAccessor.Local +// ReSharper disable AccessToDisposedClosure +// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { public class OwnedFixupTest @@ -36,6 +39,7 @@ public void Can_get_owned_entity_entry() var dependentEntry2 = context.Entry(principal).Reference(p => p.Child2).TargetEntry; + Assert.NotNull(dependentEntry2); Assert.Equal( CoreStrings.AmbiguousDependentEntity( typeof(ChildPN).ShortDisplayName(), @@ -878,8 +882,10 @@ private class Child public SubChild SubChild { get; set; } } + // ReSharper disable once ClassNeverInstantiated.Local private class SubChild { + // ReSharper disable once UnusedMember.Local public string Name { get; set; } public Child Child { get; set; } @@ -897,6 +903,7 @@ private class ChildPN { public string Name { get; set; } + // ReSharper disable once MemberHidesStaticFromOuterClass public SubChildPN SubChild { get; set; } } @@ -913,6 +920,7 @@ public FixupContext(bool ignoreDuplicates = true) { _ignoreDuplicates = ignoreDuplicates; + // ReSharper disable once VirtualMemberCallInConstructor ChangeTracker.AutoDetectChangesEnabled = false; } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs index b2eb296f265..638aa6cc92e 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/QueryFixupTest.cs @@ -6,6 +6,7 @@ using System.Linq; using Xunit; +// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { public class QueryFixupTest diff --git a/test/EFCore.Tests/ChangeTracking/Internal/ShadowFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/ShadowFixupTest.cs new file mode 100644 index 00000000000..dedd41d0ad6 --- /dev/null +++ b/test/EFCore.Tests/ChangeTracking/Internal/ShadowFixupTest.cs @@ -0,0 +1,348 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using Xunit; + +// ReSharper disable AutoPropertyCanBeMadeGetOnly.Local +// ReSharper disable MemberCanBePrivate.Local +// ReSharper disable UnusedMember.Local +// ReSharper disable MemberHidesStaticFromOuterClass +// ReSharper disable UnusedAutoPropertyAccessor.Local +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToDisposedClosure +namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal +{ + public class ShadowFixupTest + { + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_many_FK_set_both_navs_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: true, setToPrincipal: true, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_many_FK_not_set_both_navs_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: false, setToPrincipal: true, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_many_FK_set_no_navs_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: true, setToPrincipal: false, setToDependent: false); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_many_FK_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: true, setToPrincipal: false, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_many_FK_set_dependent_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: true, setToPrincipal: true, setToDependent: false); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_many_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: false, setToPrincipal: false, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_many_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_many( + entityState, principalFirst: false, setFk: false, setToPrincipal: true, setToDependent: false); + } + + private void Add_principal_and_dependent_one_to_many( + EntityState entityState, bool principalFirst, bool setFk, bool setToPrincipal, bool setToDependent) + { + using (var context = new FixupContext()) + { + var principal = new Category(77); + var dependent = new Product(78); + var principalEntry = context.Entry(principal); + var dependentEntry = context.Entry(dependent); + if (setFk) + { + dependentEntry.Property("CategoryId").CurrentValue = principal.Id; + } + if (setToPrincipal) + { + dependentEntry.Navigation("Category").CurrentValue = principal; + } + if (setToDependent) + { + var collection = new HashSet { dependent }; + principalEntry.Collection("Products").CurrentValue = collection; + } + + if (principalFirst) + { + principalEntry.State = entityState; + } + dependentEntry.State = entityState; + if (!principalFirst) + { + principalEntry.State = entityState; + } + + AssertFixup( + context, + () => + { + Assert.Equal(principal.Id, dependentEntry.Property("CategoryId").CurrentValue); + Assert.Same(principal, dependentEntry.Navigation("Category").CurrentValue); + Assert.Equal(new[] { dependent }, principalEntry.Collection("Products").CurrentValue); + Assert.Equal(entityState, context.Entry(principal).State); + Assert.Equal(entityState, context.Entry(dependent).State); + }); + } + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_FK_set_both_navs_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: true, setToPrincipal: true, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_FK_not_set_both_navs_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: false, setToPrincipal: true, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_FK_set_no_navs_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: true, setToPrincipal: false, setToDependent: false); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_FK_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: true, setToPrincipal: false, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_FK_set_dependent_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: true, setToPrincipal: true, setToDependent: false); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_FK_not_set_principal_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: false, setToPrincipal: false, setToDependent: true); + } + + [Theory] + [InlineData(EntityState.Added)] + [InlineData(EntityState.Modified)] + [InlineData(EntityState.Unchanged)] + public void Add_dependent_then_principal_one_to_one_FK_not_set_dependent_nav_set(EntityState entityState) + { + Add_principal_and_dependent_one_to_one( + entityState, principalFirst: false, setFk: false, setToPrincipal: true, setToDependent: false); + } + + private void Add_principal_and_dependent_one_to_one( + EntityState entityState, bool principalFirst, bool setFk, bool setToPrincipal, bool setToDependent) + { + using (var context = new FixupContext()) + { + var principal = new Parent(77); + var dependent = new Child(78); + var principalEntry = context.Entry(principal); + var dependentEntry = context.Entry(dependent); + if (setFk) + { + dependentEntry.Property("ParentId").CurrentValue = principal.Id; + } + if (setToPrincipal) + { + dependentEntry.Navigation("Parent").CurrentValue = principal; + } + if (setToDependent) + { + principalEntry.Navigation("Child").CurrentValue = dependent; + } + + if (principalFirst) + { + principalEntry.State = entityState; + } + dependentEntry.State = entityState; + if (!principalFirst) + { + principalEntry.State = entityState; + } + + AssertFixup( + context, + () => + { + Assert.Equal(principal.Id, dependentEntry.Property("ParentId").CurrentValue); + Assert.Same(principal, dependentEntry.Navigation("Parent").CurrentValue); + Assert.Same(dependent, principalEntry.Navigation("Child").CurrentValue); + Assert.Equal(entityState, context.Entry(principal).State); + Assert.Equal(entityState, context.Entry(dependent).State); + }); + } + } + + private class Parent + { + public Parent(int id) + { + Id = id; + } + + public int Id { get; set; } + } + + private class Child + { + public Child(int id) + { + Id = id; + } + + public int Id { get; set; } + } + + private class Category + { + public Category() + { + } + + public Category(int id) + { + Id = id; + } + + public int Id { get; set; } + } + + private class Product + { + public Product() + { + } + + public Product(int id) + { + Id = id; + } + + public int Id { get; set; } + } + + private sealed class FixupContext : DbContext + { + public FixupContext() + { + ChangeTracker.AutoDetectChangesEnabled = false; + } + + protected internal override void OnModelCreating(ModelBuilder modelBuilder) + { + var category = modelBuilder.Entity().Metadata; + + modelBuilder.Entity(b => + { + var fk = b.Metadata.AddForeignKey( + new[] { b.Property("CategoryId").Metadata }, + category.FindPrimaryKey(), + category); + fk.HasDependentToPrincipal("Category"); + fk.HasPrincipalToDependent("Products"); + }); + + var parent = modelBuilder.Entity().Metadata; + + modelBuilder.Entity(b => + { + var fk = b.Metadata.AddForeignKey( + new[] { b.Property("ParentId").Metadata }, + parent.FindPrimaryKey(), + parent); + fk.IsUnique = true; + fk.HasDependentToPrincipal("Parent"); + fk.HasPrincipalToDependent("Child"); + }); + } + + protected internal override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseInMemoryDatabase(nameof(FixupContext)); + } + + private void AssertFixup(DbContext context, Action asserts) + { + asserts(); + context.ChangeTracker.DetectChanges(); + asserts(); + context.ChangeTracker.DetectChanges(); + asserts(); + context.ChangeTracker.DetectChanges(); + asserts(); + } + } +} diff --git a/test/EFCore.Tests/ChangeTracking/Internal/ShadowFkFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/ShadowFkFixupTest.cs index ff27212cdaa..4fe261fb5a0 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/ShadowFkFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/ShadowFkFixupTest.cs @@ -6,6 +6,11 @@ using System.Linq; using Xunit; +// ReSharper disable ClassNeverInstantiated.Local +// ReSharper disable MemberHidesStaticFromOuterClass +// ReSharper disable UnusedAutoPropertyAccessor.Local +// ReSharper disable AccessToDisposedClosure +// ReSharper disable InconsistentNaming namespace Microsoft.EntityFrameworkCore.ChangeTracking.Internal { public class ShadowFkFixupTest @@ -1694,6 +1699,7 @@ public Product() public int Id { get; set; } public Category Category { get; set; } + // ReSharper disable once CollectionNeverUpdated.Local public ICollection SpecialOffers { get; } } @@ -1708,6 +1714,7 @@ private class FixupContext : DbContext { public FixupContext() { + // ReSharper disable once VirtualMemberCallInConstructor ChangeTracker.AutoDetectChangesEnabled = false; } diff --git a/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs b/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs index a8edf39592b..e0f09e0a56f 100644 --- a/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/ClrCollectionAccessorFactoryTest.cs @@ -11,6 +11,7 @@ using Microsoft.EntityFrameworkCore.Metadata.Conventions.Internal; using Xunit; +// ReSharper disable InconsistentNaming // ReSharper disable FieldCanBeMadeReadOnly.Local // ReSharper disable AutoPropertyCanBeMadeGetOnly.Local // ReSharper disable EmptyConstructor @@ -49,9 +50,9 @@ private class FakeNavigation : INavigation, IClrCollectionAccessor public void AddRange(object instance, IEnumerable values) => throw new NotImplementedException(); public bool Contains(object instance, object value) => throw new NotImplementedException(); public void Remove(object instance, object value) => throw new NotImplementedException(); - public object Create() => throw new NotImplementedException(); - public object Create(IEnumerable values) => throw new NotImplementedException(); - public object GetOrCreate(object instance) => throw new NotImplementedException(); + public IEnumerable Create() => throw new NotImplementedException(); + public IEnumerable Create(IEnumerable values) => throw new NotImplementedException(); + public IEnumerable GetOrCreate(object instance) => throw new NotImplementedException(); public Type CollectionType { get; } } diff --git a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs index 787e619b1fd..927fb6e600b 100644 --- a/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs +++ b/test/EFCore.Tests/Metadata/Internal/EntityTypeTest.cs @@ -11,6 +11,8 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; +// ReSharper disable CollectionNeverUpdated.Local +// ReSharper disable MemberCanBePrivate.Local // ReSharper disable InconsistentNaming // ReSharper disable UnusedMember.Local @@ -2192,96 +2194,6 @@ public void Can_add_a_navigation_to_shadow_entity() Assert.NotNull(customerForeignKey.HasDependentToPrincipal("Customer")); } - [Fact] - public void Adding_a_navigation_on_non_shadow_entity_type_pointing_to_a_shadow_entity_type_throws() - { - var model = new Model(); - var customerType = model.AddEntityType("Customer"); - var customerKey = customerType.GetOrAddKey(customerType.AddProperty("Id", typeof(int))); - - var orderType = model.AddEntityType(typeof(Order)); - var foreignKeyProperty = orderType.AddProperty("CustomerId", typeof(int)); - var customerForeignKey = orderType.GetOrAddForeignKey(foreignKeyProperty, customerKey, customerType); - - Assert.Equal( - CoreStrings.NavigationToShadowEntity(nameof(Order.Customer), typeof(Order).Name, "Customer"), - Assert.Throws( - () => customerForeignKey.HasDependentToPrincipal(Order.CustomerProperty)).Message); - } - - [Fact] - public void Adding_a_shadow_navigation_on_a_non_shadow_entity_type_throws() - { - var model = new Model(); - var customerType = model.AddEntityType(typeof(Customer)); - var customerKey = customerType.GetOrAddKey(customerType.AddProperty("Id", typeof(int))); - - var orderType = model.AddEntityType(typeof(Order)); - var foreignKeyProperty = orderType.AddProperty("CustomerId", typeof(int)); - var customerForeignKey = orderType.GetOrAddForeignKey(foreignKeyProperty, customerKey, customerType); - - Assert.Equal( - CoreStrings.NoClrNavigation("Navigation", typeof(Order).Name), - Assert.Throws( - () => customerForeignKey.HasDependentToPrincipal("Navigation")).Message); - } - - [Fact] - public void Adding_a_navigation_that_doesnt_match_a_CLR_property_throws() - { - var model = new Model(); - var customerType = model.AddEntityType(typeof(Customer)); - var customerKey = customerType.GetOrAddKey(customerType.GetOrAddProperty(Customer.IdProperty)); - - var orderType = model.AddEntityType(typeof(Order)); - var foreignKeyProperty = orderType.GetOrAddProperty(Order.CustomerIdProperty); - var customerForeignKey = orderType.GetOrAddForeignKey(foreignKeyProperty, customerKey, customerType); - - Assert.Equal( - CoreStrings.NoClrNavigation("Snook", typeof(Order).Name), - Assert.Throws( - () => customerForeignKey.HasDependentToPrincipal("Snook")).Message); - } - - [Fact] - public void Collection_navigation_properties_must_be_IEnumerables_of_the_target_type() - { - var model = new Model(); - var customerType = model.AddEntityType(typeof(Customer)); - var customerKey = customerType.GetOrAddKey(customerType.GetOrAddProperty(Customer.IdProperty)); - - var orderType = model.AddEntityType(typeof(Order)); - var foreignKeyProperty = orderType.GetOrAddProperty(Order.CustomerIdProperty); - var customerForeignKey = orderType.GetOrAddForeignKey(foreignKeyProperty, customerKey, customerType); - - Assert.Equal( - CoreStrings.NavigationCollectionWrongClrType( - nameof(Customer.NotCollectionOrders), typeof(Customer).Name, typeof(Order).Name, typeof(Order).Name), - Assert.Throws( - () => customerForeignKey.HasPrincipalToDependent(Customer.NotCollectionOrdersProperty)).Message); - } - - [Fact] - public void Collection_navigation_properties_cannot_be_IEnumerables_of_derived_target_type() - { - var model = new Model(); - var customerType = model.AddEntityType(typeof(SpecialCustomer)); - var customerKey = customerType.GetOrAddKey(customerType.GetOrAddProperty(Customer.IdProperty)); - - var orderType = model.AddEntityType(typeof(Order)); - var foreignKeyProperty = orderType.GetOrAddProperty(Order.CustomerIdProperty); - var customerForeignKey = orderType.GetOrAddForeignKey(foreignKeyProperty, customerKey, customerType); - - Assert.Equal( - CoreStrings.NavigationCollectionWrongClrType( - nameof(SpecialCustomer.DerivedOrders), - typeof(SpecialCustomer).Name, - typeof(IEnumerable).ShortDisplayName(), - typeof(Order).Name), - Assert.Throws( - () => customerForeignKey.HasPrincipalToDependent(SpecialCustomer.DerivedOrdersProperty)).Message); - } - [Fact] public void Collection_navigation_properties_can_be_IEnumerables_of_base_target_type() { @@ -2304,41 +2216,6 @@ public void Collection_navigation_properties_can_be_IEnumerables_of_base_target_ Assert.Same(ordersNavigation, customerForeignKey.PrincipalToDependent); } - [Fact] - public void Reference_navigation_properties_must_be_of_the_target_type() - { - var model = new Model(); - var customerType = model.AddEntityType(typeof(Customer)); - var customerKey = customerType.GetOrAddKey(customerType.GetOrAddProperty(Customer.IdProperty)); - - var orderType = model.AddEntityType(typeof(Order)); - var foreignKeyProperty = orderType.GetOrAddProperty(Order.CustomerIdProperty); - var customerForeignKey = orderType.GetOrAddForeignKey(foreignKeyProperty, customerKey, customerType); - - Assert.Equal( - CoreStrings.NavigationSingleWrongClrType("OrderCustomer", typeof(Order).Name, typeof(Order).Name, typeof(Customer).Name), - Assert.Throws( - () => customerForeignKey.HasDependentToPrincipal(Order.OrderCustomerProperty)).Message); - } - - [Fact] - public void Reference_navigation_properties_cannot_be_of_derived_type() - { - var model = new Model(); - var customerType = model.AddEntityType(typeof(Customer)); - var customerKey = customerType.GetOrAddKey(customerType.GetOrAddProperty(Customer.IdProperty)); - - var orderType = model.AddEntityType(typeof(SpecialOrder)); - var foreignKeyProperty = orderType.GetOrAddProperty(Order.CustomerIdProperty); - var customerForeignKey = orderType.GetOrAddForeignKey(foreignKeyProperty, customerKey, customerType); - - Assert.Equal( - CoreStrings.NavigationSingleWrongClrType( - nameof(SpecialOrder.DerivedCustomer), typeof(SpecialOrder).Name, typeof(SpecialCustomer).Name, typeof(Customer).Name), - Assert.Throws( - () => customerForeignKey.HasDependentToPrincipal(SpecialOrder.DerivedCustomerProperty)).Message); - } - [Fact] public void Reference_navigation_properties_can_be_of_base_type() {