From e90bd07d73cc00824fa0c82e6c52b5c3ea75256b Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sun, 13 Jan 2019 14:50:10 -0800 Subject: [PATCH] Allow cascade delete timing to be configured Fixes #10114 Allows timing cascade delete (i.e. delete of dependent on principal deletion) and delete orphans (i.e. delete of dependent on severing its relationship to the principal) to be configured as: * Immediate: Dependents changed to Deleted immediately, or at least at the next DetectChanges * OnSaveChanges: Dependents are not changed to Deleted until SaveChanges is called * Never: Cascades don't happen automatically. They can be triggered by a new method: `context.ChangeTracker.CascadeChanges()` The default for both has been changed to `Immediate` which is a breaking change, but likely a better default because: * Auditing in overridden SaveChanges will now report anything that will be deleted * Data binding will reflect the changes immediately However, deleting orphans can be more difficult. This doesn't appear to be a major problem in EF Core because we don't aggressively graph shred, we don't typically have entities that handle their own fixup, and conceptual nulls still exist to allow transitionary states. Note that EF6 does not support deleting orphans, which means the behavior here with both set to Immediate is not quite the same as the default behavior of EF6. --- src/EFCore/ChangeTracking/CascadeTiming.cs | 29 + src/EFCore/ChangeTracking/ChangeTracker.cs | 65 +- .../ChangeTracking/Internal/IStateManager.cs | 12 + .../Internal/InternalEntityEntry.cs | 42 +- .../ChangeTracking/Internal/StateManager.cs | 47 +- src/EFCore/DbContext.cs | 8 +- src/EFCore/DeleteBehavior.cs | 3 +- .../DbContextPoolConfigurationSnapshot.cs | 20 +- .../GraphUpdatesInMemoryTest.cs | 106 +- .../ProxyGraphUpdatesInMemoryTest.cs | 37 +- .../DatabindingTestBase.cs | 20 +- .../GraphUpdatesTestBase.cs | 3407 ++++++++++++++--- .../LazyLoadProxyTestBase.cs | 70 +- .../LoadTestBase.cs | 299 +- .../PropertyValuesTestBase.cs | 55 +- .../ProxyGraphUpdatesTestBase.cs | 1891 ++++++--- .../StoreGeneratedFixupTestBase.cs | 3 + .../DbContextPoolingTest.cs | 11 + .../GraphUpdatesSqlServerTest.cs | 321 +- .../LazyLoadProxySqlServerTest.cs | 13 +- .../LoadSqlServerTest.cs | 45 +- .../ChangeTracking/ChangeTrackerTest.cs | 241 +- .../ChangeTracking/Internal/FixupTest.cs | 8 + .../Internal/InternalEntityEntryTestBase.cs | 83 +- .../Internal/NavigationFixerTest.cs | 6 +- test/EFCore.Tests/DbContextTrackingTest.cs | 24 +- .../TestUtilities/FakeStateManager.cs | 2 + 27 files changed, 5380 insertions(+), 1488 deletions(-) create mode 100644 src/EFCore/ChangeTracking/CascadeTiming.cs diff --git a/src/EFCore/ChangeTracking/CascadeTiming.cs b/src/EFCore/ChangeTracking/CascadeTiming.cs new file mode 100644 index 00000000000..26e7e0b81ef --- /dev/null +++ b/src/EFCore/ChangeTracking/CascadeTiming.cs @@ -0,0 +1,29 @@ +// 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. + +namespace Microsoft.EntityFrameworkCore.ChangeTracking +{ + /// + /// Defines different strategies for when cascading actions will be performed. + /// See and . + /// + public enum CascadeTiming + { + /// + /// Cascading actions are made to dependent/child entities as soon as the principal/parent + /// entity changes. + /// + Immediate, + + /// + /// Cascading actions are made to dependent/child entities as part of . + /// + OnSaveChanges, + + /// + /// Cascading actions are never made automatically to dependent/child entities, but must instead + /// be triggered by an explicit call. + /// + Never + } +} diff --git a/src/EFCore/ChangeTracking/ChangeTracker.cs b/src/EFCore/ChangeTracking/ChangeTracker.cs index 79d2aa4fcc9..06b6dfdc8bb 100644 --- a/src/EFCore/ChangeTracking/ChangeTracker.cs +++ b/src/EFCore/ChangeTracking/ChangeTracker.cs @@ -109,6 +109,35 @@ public virtual QueryTrackingBehavior QueryTrackingBehavior set => _queryTrackingBehavior = value; } + /// + /// + /// Gets or sets a value indicating when a dependent/child entity will have its state + /// set to once severed from a parent/principal entity + /// through either a navigation or foreign key property being set to null. The default + /// value is . + /// + /// + /// Dependent/child entities are only deleted automatically when the relationship + /// is configured with . This is set by default + /// for required relationships. + /// + /// + public virtual CascadeTiming DeleteOrphansTiming { get; set; } = CascadeTiming.Immediate; + + /// + /// + /// Gets or sets a value indicating when a dependent/child entity will have its state + /// set to once its parent/principal entity has been marked + /// as . The default value is. + /// + /// + /// Dependent/child entities are only deleted automatically when the relationship + /// is configured with . This is set by default + /// for required relationships. + /// + /// + public virtual CascadeTiming CascadeDeleteTiming { get; set; } = CascadeTiming.Immediate; + /// /// Gets an for each entity being tracked by the context. /// The entries provide access to change tracking information and operations for each entity. @@ -313,22 +342,46 @@ public virtual void DetectChanges() remove => StateManager.StateChanged -= value; } - #region Hidden System.Object members - /// - /// Returns a string that represents the current object. + /// + /// Forces immediate cascading deletion of child/dependent entities when they are either + /// severed from a required parent/principal entity, or the required parent/principal entity + /// is itself deleted. See . + /// + /// + /// This method is usually used when and/or + /// have been set to + /// to manually force the deletes to have at a time controlled by the application. + /// /// - /// A string that represents the current object. - [EditorBrowsable(EditorBrowsableState.Never)] - public override string ToString() => base.ToString(); + public virtual void CascadeChanges() + { + if (AutoDetectChangesEnabled) + { + DetectChanges(); + } + + StateManager.CascadeChanges(force: true); + } void IResettableService.ResetState() { _queryTrackingBehavior = _defaultQueryTrackingBehavior; AutoDetectChangesEnabled = true; LazyLoadingEnabled = true; + CascadeDeleteTiming = CascadeTiming.Immediate; + DeleteOrphansTiming = CascadeTiming.Immediate; } + #region Hidden System.Object members + + /// + /// Returns a string that represents the current object. + /// + /// A string that represents the current object. + [EditorBrowsable(EditorBrowsableState.Never)] + public override string ToString() => base.ToString(); + /// /// Determines whether the specified object is equal to the current object. /// diff --git a/src/EFCore/ChangeTracking/Internal/IStateManager.cs b/src/EFCore/ChangeTracking/Internal/IStateManager.cs index 2eb82a9cf9c..17b5d705cd2 100644 --- a/src/EFCore/ChangeTracking/Internal/IStateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/IStateManager.cs @@ -267,6 +267,18 @@ public interface IStateManager : IResettableService /// bool SensitiveLoggingEnabled { get; } + /// + /// 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. + /// + void CascadeChanges(bool force); + + /// + /// 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. + /// + void CascadeDelete([NotNull] InternalEntityEntry entry, bool force); + /// /// 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/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 1dc7cc00990..791207a4f74 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -273,6 +273,12 @@ private void SetEntityState(EntityState oldState, EntityState newState, bool acc } FireStateChanged(oldState); + + if (newState == EntityState.Deleted + && StateManager.Context.ChangeTracker.CascadeDeleteTiming == CascadeTiming.Immediate) + { + StateManager.CascadeDelete(this, force: false); + } } private void FireStateChanged(EntityState oldState) @@ -519,7 +525,7 @@ public virtual void SetTemporaryValue([NotNull] IProperty property, object value CoreStrings.TempValue(property.Name, EntityType.DisplayName())); } - SetProperty(property, value, setModified, CurrentValueType.Temporary); + SetProperty(property, value, setModified, isCascadeDelete: false, CurrentValueType.Temporary); } /// @@ -534,7 +540,7 @@ public virtual void SetStoreGeneratedValue(IProperty property, object value) CoreStrings.StoreGenValue(property.Name, EntityType.DisplayName())); } - SetProperty(property, value, setModified: true, CurrentValueType.StoreGenerated); + SetProperty(property, value, setModified: true, isCascadeDelete: false, CurrentValueType.StoreGenerated); } /// @@ -909,13 +915,15 @@ public virtual void AddToCollectionSnapshot([NotNull] IPropertyBase propertyBase public virtual void SetProperty( [NotNull] IPropertyBase propertyBase, [CanBeNull] object value, - bool setModified = true) - => SetProperty(propertyBase, value, setModified, CurrentValueType.Normal); + bool setModified = true, + bool isCascadeDelete = false) + => SetProperty(propertyBase, value, setModified, isCascadeDelete, CurrentValueType.Normal); private void SetProperty( [NotNull] IPropertyBase propertyBase, [CanBeNull] object value, bool setModified, + bool isCascadeDelete, CurrentValueType valueType) { var currentValue = this[propertyBase]; @@ -968,6 +976,15 @@ public virtual void AddToCollectionSnapshot([NotNull] IPropertyBase propertyBase asProperty, changeState: true, isModified: true, isConceptualNull: true); } + + if (!isCascadeDelete + && StateManager.Context.ChangeTracker.DeleteOrphansTiming == CascadeTiming.Immediate) + { + HandleConceptualNulls( + StateManager.SensitiveLoggingEnabled, + force: false, + isCascadeDelete: false); + } } writeValue = false; @@ -1134,7 +1151,7 @@ public virtual InternalEntityEntry PrepareToSave() /// 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 HandleConceptualNulls(bool sensitiveLoggingEnabled) + public virtual void HandleConceptualNulls(bool sensitiveLoggingEnabled, bool force, bool isCascadeDelete) { var fks = new List(); foreach (var foreignKey in EntityType.GetForeignKeys()) @@ -1172,7 +1189,10 @@ public virtual void HandleConceptualNulls(bool sensitiveLoggingEnabled) } var cascadeFk = fks.FirstOrDefault(fk => fk.DeleteBehavior == DeleteBehavior.Cascade); - if (cascadeFk != null) + if (cascadeFk != null + && (force + || (!isCascadeDelete + && StateManager.Context.ChangeTracker.DeleteOrphansTiming != CascadeTiming.Never))) { var cascadeState = EntityState == EntityState.Added ? EntityState.Detached @@ -1193,18 +1213,20 @@ public virtual void HandleConceptualNulls(bool sensitiveLoggingEnabled) } else if (fks.Count > 0) { + var foreignKey = fks.First(); + if (sensitiveLoggingEnabled) { throw new InvalidOperationException( CoreStrings.RelationshipConceptualNullSensitive( - fks.First().PrincipalEntityType.DisplayName(), + foreignKey.PrincipalEntityType.DisplayName(), EntityType.DisplayName(), - this.BuildOriginalValuesString(EntityType.FindPrimaryKey().Properties))); + this.BuildOriginalValuesString(foreignKey.Properties))); } throw new InvalidOperationException( CoreStrings.RelationshipConceptualNull( - fks.First().PrincipalEntityType.DisplayName(), + foreignKey.PrincipalEntityType.DisplayName(), EntityType.DisplayName())); } else @@ -1222,7 +1244,7 @@ public virtual void HandleConceptualNulls(bool sensitiveLoggingEnabled) CoreStrings.PropertyConceptualNullSensitive( property.Name, EntityType.DisplayName(), - this.BuildOriginalValuesString(EntityType.FindPrimaryKey().Properties))); + this.BuildOriginalValuesString(new[] { property }))); } throw new InvalidOperationException( diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index c2672ffd073..b6b2690d76b 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -811,31 +811,46 @@ public virtual IReadOnlyList GetEntriesToSave() /// directly from your code. This API may change or be removed in future releases. /// public virtual IReadOnlyList GetInternalEntriesToSave() + { + CascadeChanges(force: false); + + return Entries + .Where( + e => e.EntityState == EntityState.Added + || e.EntityState == EntityState.Modified + || e.EntityState == EntityState.Deleted) + .Select(e => e.PrepareToSave()) + .ToList(); + } + + /// + /// 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 CascadeChanges(bool force) { foreach (var entry in Entries.Where( e => (e.EntityState == EntityState.Modified || e.EntityState == EntityState.Added) && e.HasConceptualNull).ToList()) { - entry.HandleConceptualNulls(SensitiveLoggingEnabled); + entry.HandleConceptualNulls(SensitiveLoggingEnabled, force, isCascadeDelete: false); } foreach (var entry in Entries.Where(e => e.EntityState == EntityState.Deleted).ToList()) { - CascadeDelete(entry); + CascadeDelete(entry, force); } - - return Entries - .Where( - e => e.EntityState == EntityState.Added - || e.EntityState == EntityState.Modified - || e.EntityState == EntityState.Deleted) - .Select(e => e.PrepareToSave()) - .ToList(); } - private void CascadeDelete(InternalEntityEntry entry) + /// + /// 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 CascadeDelete(InternalEntityEntry entry, bool force) { + var doCascadeDelete = force || Context.ChangeTracker.CascadeDeleteTiming != CascadeTiming.Never; + foreach (var fk in entry.EntityType.GetReferencingForeignKeys()) { foreach (var dependent in (GetDependentsFromNavigation(entry, fk) @@ -847,7 +862,8 @@ private void CascadeDelete(InternalEntityEntry entry) && (dependent.EntityState == EntityState.Added || KeysEqual(entry, fk, dependent))) { - if (fk.DeleteBehavior == DeleteBehavior.Cascade) + if (fk.DeleteBehavior == DeleteBehavior.Cascade + && doCascadeDelete) { var cascadeState = dependent.EntityState == EntityState.Added ? EntityState.Detached @@ -864,18 +880,18 @@ private void CascadeDelete(InternalEntityEntry entry) dependent.SetEntityState(cascadeState); - CascadeDelete(dependent); + CascadeDelete(dependent, force); } else { foreach (var dependentProperty in fk.Properties) { - dependent[dependentProperty] = null; + dependent.SetProperty(dependentProperty, null, setModified: true, isCascadeDelete: true); } if (dependent.HasConceptualNull) { - dependent.HandleConceptualNulls(SensitiveLoggingEnabled); + dependent.HandleConceptualNulls(SensitiveLoggingEnabled, force, isCascadeDelete: true); } } } @@ -895,7 +911,6 @@ private static bool KeysEqual(InternalEntityEntry entry, IForeignKey fk, Interna entry[principalProperty], dependent[dependentProperty])) { - //dependent[dependentProperty] = null; return false; } } diff --git a/src/EFCore/DbContext.cs b/src/EFCore/DbContext.cs index cb41a4a6af8..6b521326881 100644 --- a/src/EFCore/DbContext.cs +++ b/src/EFCore/DbContext.cs @@ -575,7 +575,9 @@ DbContextPoolConfigurationSnapshot IDbContextPoolable.SnapshotConfiguration() _changeTracker?.AutoDetectChangesEnabled, _changeTracker?.QueryTrackingBehavior, _database?.AutoTransactionsEnabled, - _changeTracker?.LazyLoadingEnabled); + _changeTracker?.LazyLoadingEnabled, + _changeTracker?.CascadeDeleteTiming, + _changeTracker?.DeleteOrphansTiming); void IDbContextPoolable.Resurrect(DbContextPoolConfigurationSnapshot configurationSnapshot) { @@ -585,10 +587,14 @@ void IDbContextPoolable.Resurrect(DbContextPoolConfigurationSnapshot configurati { Debug.Assert(configurationSnapshot.QueryTrackingBehavior.HasValue); Debug.Assert(configurationSnapshot.LazyLoadingEnabled.HasValue); + Debug.Assert(configurationSnapshot.CascadeDeleteTiming.HasValue); + Debug.Assert(configurationSnapshot.DeleteOrphansTiming.HasValue); ChangeTracker.AutoDetectChangesEnabled = configurationSnapshot.AutoDetectChangesEnabled.Value; ChangeTracker.QueryTrackingBehavior = configurationSnapshot.QueryTrackingBehavior.Value; ChangeTracker.LazyLoadingEnabled = configurationSnapshot.LazyLoadingEnabled.Value; + ChangeTracker.CascadeDeleteTiming = configurationSnapshot.CascadeDeleteTiming.Value; + ChangeTracker.DeleteOrphansTiming = configurationSnapshot.DeleteOrphansTiming.Value; } else { diff --git a/src/EFCore/DeleteBehavior.cs b/src/EFCore/DeleteBehavior.cs index 730e0e7ba3e..94f6ceb29c6 100644 --- a/src/EFCore/DeleteBehavior.cs +++ b/src/EFCore/DeleteBehavior.cs @@ -50,8 +50,7 @@ public enum DeleteBehavior /// For entities being tracked by the , the values of foreign key properties in /// dependent entities are not changed. This can result in an inconsistent graph of entities /// where the values of foreign key properties do not match the relationships in the - /// graph. If a property remains in this state when - /// is called, then an exception will be thrown. + /// graph. /// /// /// If the database has been created from the model using Entity Framework Migrations or the diff --git a/src/EFCore/Internal/DbContextPoolConfigurationSnapshot.cs b/src/EFCore/Internal/DbContextPoolConfigurationSnapshot.cs index a7cc9cf48cc..0cd193aaafc 100644 --- a/src/EFCore/Internal/DbContextPoolConfigurationSnapshot.cs +++ b/src/EFCore/Internal/DbContextPoolConfigurationSnapshot.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 Microsoft.EntityFrameworkCore.ChangeTracking; + namespace Microsoft.EntityFrameworkCore.Internal { /// @@ -17,12 +19,16 @@ public class DbContextPoolConfigurationSnapshot bool? autoDetectChangesEnabled, QueryTrackingBehavior? queryTrackingBehavior, bool? autoTransactionsEnabled, - bool? lazyLoadingEnabled) + bool? lazyLoadingEnabled, + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { AutoDetectChangesEnabled = autoDetectChangesEnabled; QueryTrackingBehavior = queryTrackingBehavior; AutoTransactionsEnabled = autoTransactionsEnabled; LazyLoadingEnabled = lazyLoadingEnabled; + CascadeDeleteTiming = cascadeDeleteTiming; + DeleteOrphansTiming = deleteOrphansTiming; } /// @@ -37,6 +43,18 @@ public class DbContextPoolConfigurationSnapshot /// public virtual bool? LazyLoadingEnabled { get; } + /// + /// 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 CascadeTiming? CascadeDeleteTiming { get; } + + /// + /// 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 CascadeTiming? DeleteOrphansTiming { get; } + /// /// 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/test/EFCore.InMemory.FunctionalTests/GraphUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/GraphUpdatesInMemoryTest.cs index 8b1f6f5255b..047a61a4876 100644 --- a/test/EFCore.InMemory.FunctionalTests/GraphUpdatesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/GraphUpdatesInMemoryTest.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 Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -15,158 +16,209 @@ public GraphUpdatesInMemoryTest(GraphUpdatesInMemoryFixture fixture) { } - public override DbUpdateException Optional_One_to_one_relationships_are_one_to_one() + public override DbUpdateException Optional_One_to_one_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database return null; } - public override DbUpdateException Required_One_to_one_relationships_are_one_to_one() + public override DbUpdateException Required_One_to_one_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database return null; } - public override DbUpdateException Optional_One_to_one_with_AK_relationships_are_one_to_one() + public override DbUpdateException Optional_One_to_one_with_AK_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database return null; } - public override DbUpdateException Required_One_to_one_with_AK_relationships_are_one_to_one() + public override DbUpdateException Required_One_to_one_with_AK_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database return null; } public override void Save_required_one_to_one_changed_by_reference_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database } public override void Save_required_non_PK_one_to_one_changed_by_reference_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database } - public override DbUpdateException Save_required_one_to_one_changed_by_reference(ChangeMechanism changeMechanism) + public override DbUpdateException Save_required_one_to_one_changed_by_reference( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database return null; } - public override void Save_removed_required_many_to_one_dependents(ChangeMechanism changeMechanism) + public override void Save_removed_required_many_to_one_dependents( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database } - public override void Save_required_non_PK_one_to_one_changed_by_reference(ChangeMechanism changeMechanism, bool useExistingEntities) + public override void Save_required_non_PK_one_to_one_changed_by_reference( + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database } - public override void Sever_required_one_to_one_with_alternate_key(ChangeMechanism changeMechanism) + public override void Sever_required_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database } - public override DbUpdateException Sever_required_one_to_one(ChangeMechanism changeMechanism) + public override DbUpdateException Sever_required_one_to_one( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database return null; } - public override void Sever_required_non_PK_one_to_one(ChangeMechanism changeMechanism) + public override void Sever_required_non_PK_one_to_one( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database } - public override void Sever_required_non_PK_one_to_one_with_alternate_key(ChangeMechanism changeMechanism) + public override void Sever_required_non_PK_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database } - public override DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_in_store() + public override DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database return null; } - public override DbUpdateException Required_one_to_one_are_cascade_deleted_in_store() + public override DbUpdateException Required_one_to_one_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database return null; } - public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_in_store() + public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database return null; } - public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store() + public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database return null; } - public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database return null; } - public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade delete not supported by in-memory database return null; } - public override DbUpdateException Optional_many_to_one_dependents_are_orphaned_in_store() + public override DbUpdateException Optional_many_to_one_dependents_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade nulls not supported by in-memory database return null; } - public override DbUpdateException Optional_one_to_one_are_orphaned_in_store() + public override DbUpdateException Optional_one_to_one_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade nulls not supported by in-memory database return null; } - public override DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store() + public override DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade nulls not supported by in-memory database return null; } - public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_in_store() + public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade nulls not supported by in-memory database return null; } - public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade nulls not supported by in-memory database return null; } - public override DbUpdateException Required_one_to_one_are_cascade_detached_when_Added() + public override DbUpdateException Required_one_to_one_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade nulls not supported by in-memory database return null; } - public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade nulls not supported by in-memory database return null; } - public override DbUpdateException Required_non_PK_one_to_one_are_cascade_detached_when_Added() + public override DbUpdateException Required_non_PK_one_to_one_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { // Cascade nulls not supported by in-memory database return null; diff --git a/test/EFCore.InMemory.FunctionalTests/ProxyGraphUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/ProxyGraphUpdatesInMemoryTest.cs index cd2d6ae44cc..159d3f98250 100644 --- a/test/EFCore.InMemory.FunctionalTests/ProxyGraphUpdatesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/ProxyGraphUpdatesInMemoryTest.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 Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; using Microsoft.Extensions.DependencyInjection; @@ -32,15 +33,21 @@ public override void Optional_one_to_one_with_AK_relationships_are_one_to_one() { } - public override void Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store() + public override void Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } - public override void Optional_many_to_one_dependents_are_orphaned_in_store() + public override void Optional_many_to_one_dependents_are_orphaned_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } - public override void Required_one_to_one_are_cascade_detached_when_Added() + public override void Required_one_to_one_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } @@ -52,27 +59,39 @@ public override void Required_one_to_one_with_AK_relationships_are_one_to_one() { } - public override void Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + public override void Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } - public override void Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + public override void Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } - public override void Required_many_to_one_dependents_are_cascade_deleted_in_store() + public override void Required_many_to_one_dependents_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } - public override void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store() + public override void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } - public override void Required_non_PK_one_to_one_are_cascade_detached_when_Added() + public override void Required_non_PK_one_to_one_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } - public override void Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + public override void Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { } diff --git a/test/EFCore.Specification.Tests/DatabindingTestBase.cs b/test/EFCore.Specification.Tests/DatabindingTestBase.cs index d26edf6f710..4c6e6dc39ac 100644 --- a/test/EFCore.Specification.Tests/DatabindingTestBase.cs +++ b/test/EFCore.Specification.Tests/DatabindingTestBase.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.ComponentModel; using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.TestModels.ConcurrencyModel; using Xunit; @@ -930,11 +931,17 @@ public virtual void Entity_added_to_navigation_property_binding_list_is_added_to } } - [Fact] - public virtual void Entity_removed_from_navigation_property_binding_list_is_removed_from_nav_property_but_not_marked_Deleted() + [Theory] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never)] + public virtual void Entity_removed_from_navigation_property_binding_list_is_removed_from_nav_property_but_not_marked_Deleted( + CascadeTiming deleteOrphansTiming) { using (var context = CreateF1Context()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var ferrari = context.Teams.Single(t => t.Id == Team.Ferrari); var navBindingList = ((IListSource)ferrari.Drivers).GetList(); @@ -947,7 +954,14 @@ public virtual void Entity_removed_from_navigation_property_binding_list_is_remo context.ChangeTracker.DetectChanges(); - Assert.True(localDrivers.Contains(alonso)); // Because it is not marked as Deleted + if (deleteOrphansTiming == CascadeTiming.Immediate) + { + Assert.False(localDrivers.Contains(alonso)); + } + else + { + Assert.True(localDrivers.Contains(alonso)); // Because it is not marked as Deleted + } Assert.False(ferrari.Drivers.Contains(alonso)); // But has been removed from nav prop } diff --git a/test/EFCore.Specification.Tests/GraphUpdatesTestBase.cs b/test/EFCore.Specification.Tests/GraphUpdatesTestBase.cs index 261a2645804..cb51b19137c 100644 --- a/test/EFCore.Specification.Tests/GraphUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/GraphUpdatesTestBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Security.Authentication.ExtendedProtection; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; @@ -24,9 +25,17 @@ public abstract partial class GraphUpdatesTestBase : IClassFixture { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = context.Set().OrderBy(e => e.Id).First(); var child = context.Set().OrderBy(e => e.Id).First(e => e.ParentId == removed.Id); @@ -99,14 +110,21 @@ public virtual DbUpdateException New_FK_is_not_cleared_on_old_dependent_delete(b return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_One_to_one_relationships_are_one_to_one() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual DbUpdateException Optional_One_to_one_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Single(IsTheRoot); root.OptionalSingle = new OptionalSingle1(); @@ -117,14 +135,21 @@ public virtual DbUpdateException Optional_One_to_one_relationships_are_one_to_on return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_One_to_one_relationships_are_one_to_one() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual DbUpdateException Required_One_to_one_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Single(IsTheRoot); root.RequiredSingle = new RequiredSingle1(); @@ -135,14 +160,21 @@ public virtual DbUpdateException Required_One_to_one_relationships_are_one_to_on return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_One_to_one_with_AK_relationships_are_one_to_one() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual DbUpdateException Optional_One_to_one_with_AK_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Single(IsTheRoot); root.OptionalSingleAk = new OptionalSingleAk1(); @@ -153,14 +185,21 @@ public virtual DbUpdateException Optional_One_to_one_with_AK_relationships_are_o return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_One_to_one_with_AK_relationships_are_one_to_one() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual DbUpdateException Required_One_to_one_with_AK_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Single(IsTheRoot); root.RequiredSingleAk = new RequiredSingleAk1(); @@ -171,11 +210,18 @@ public virtual DbUpdateException Required_One_to_one_with_AK_relationships_are_o return updateException; } - [Fact] - public virtual void No_fixup_to_Deleted_entities() + [Theory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual void No_fixup_to_Deleted_entities( + CascadeTiming? deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadOptionalGraph(context); var existing = root.OptionalChildren.OrderBy(e => e.Id).First(); @@ -198,21 +244,66 @@ public virtual void No_fixup_to_Deleted_entities() } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Save_optional_many_to_one_dependents(ChangeMechanism changeMechanism, bool useExistingEntities) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Save_optional_many_to_one_dependents( + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var new1 = new Optional1(); var new1d = new Optional1Derived(); @@ -235,6 +326,8 @@ public virtual void Save_optional_many_to_one_dependents(ChangeMechanism changeM }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalGraph(context); var existing = root.OptionalChildren.OrderBy(e => e.Id).First(); @@ -329,21 +422,66 @@ public virtual void Save_optional_many_to_one_dependents(ChangeMechanism changeM } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Save_required_many_to_one_dependents(ChangeMechanism changeMechanism, bool useExistingEntities) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Save_required_many_to_one_dependents( + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var newRoot = new Root(); var new1 = new Required1 @@ -388,6 +526,8 @@ public virtual void Save_required_many_to_one_dependents(ChangeMechanism changeM }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredGraph(context); var existing = root.RequiredChildren.OrderBy(e => e.Id).First(); @@ -483,19 +623,44 @@ public virtual void Save_required_many_to_one_dependents(ChangeMechanism changeM } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Fk)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk))] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk))] - public virtual void Save_removed_optional_many_to_one_dependents(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)ChangeMechanism.Fk, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), null)] + public virtual void Save_removed_optional_many_to_one_dependents( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { Root root = null; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalGraph(context); var childCollection = root.OptionalChildren.First().Children; @@ -550,14 +715,37 @@ public virtual void Save_removed_optional_many_to_one_dependents(ChangeMechanism } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Fk)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk))] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk))] - public virtual void Save_removed_required_many_to_one_dependents(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)ChangeMechanism.Fk, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), null)] + public virtual void Save_removed_required_many_to_one_dependents( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { var removed1Id = 0; var removed2Id = 0; @@ -566,6 +754,8 @@ public virtual void Save_removed_required_many_to_one_dependents(ChangeMechanism ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); var childCollection = root.RequiredChildren.First().Children; @@ -588,22 +778,52 @@ public virtual void Save_removed_required_many_to_one_dependents(ChangeMechanism removed1.Parent = null; } - if ((changeMechanism & ChangeMechanism.Fk) != 0) + if (Fixture.ForceRestrict + || deleteOrphansTiming == CascadeTiming.Never) { - context.Entry(removed2).GetInfrastructure()[context.Entry(removed2).Property(e => e.ParentId).Metadata] = null; - context.Entry(removed1).GetInfrastructure()[context.Entry(removed1).Property(e => e.ParentId).Metadata] = null; - } + Action testCode; - Assert.True(context.ChangeTracker.HasChanges()); + if ((changeMechanism & ChangeMechanism.Fk) != 0 + && deleteOrphansTiming == CascadeTiming.Immediate) + { + testCode = () => context.Entry(removed2).GetInfrastructure()[context.Entry(removed2).Property(e => e.ParentId).Metadata] = null; + } + else + { + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + context.Entry(removed2).GetInfrastructure()[context.Entry(removed2).Property(e => e.ParentId).Metadata] = null; + context.Entry(removed1).GetInfrastructure()[context.Entry(removed1).Property(e => e.ParentId).Metadata] = null; + } - if (Fixture.ForceRestrict) - { - Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive(nameof(Required1), nameof(Required2), "{Id: 2}"), - Assert.Throws(() => context.SaveChanges()).Message); + testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? (Action)(() => context.ChangeTracker.DetectChanges()) + : deleteOrphansTiming == null + ? (Action)(() => context.ChangeTracker.CascadeChanges()) + : (Action)(() => context.SaveChanges()); + } + + var message = Assert.Throws(testCode).Message; + + Assert.True( + message == CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(Required1), "{ParentId: " + removed1.ParentId + "}") + || message == CoreStrings.RelationshipConceptualNullSensitive(nameof(Required1), nameof(Required2), "{ParentId: " + removed2.ParentId + "}")); } else { + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + context.Entry(removed2).GetInfrastructure()[context.Entry(removed2).Property(e => e.ParentId).Metadata] = null; + context.Entry(removed1).GetInfrastructure()[context.Entry(removed1).Property(e => e.ParentId).Metadata] = null; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + if (deleteOrphansTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -611,7 +831,8 @@ public virtual void Save_removed_required_many_to_one_dependents(ChangeMechanism }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && deleteOrphansTiming != CascadeTiming.Never) { var root = LoadRequiredGraph(context); @@ -628,21 +849,66 @@ public virtual void Save_removed_required_many_to_one_dependents(ChangeMechanism } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Save_changed_optional_one_to_one(ChangeMechanism changeMechanism, bool useExistingEntities) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Save_changed_optional_one_to_one( + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var new2 = new OptionalSingle2(); var new2d = new OptionalSingle2Derived(); @@ -679,6 +945,8 @@ public virtual void Save_changed_optional_one_to_one(ChangeMechanism changeMecha }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalGraph(context); old1 = root.OptionalSingle; @@ -789,14 +1057,37 @@ public virtual void Save_changed_optional_one_to_one(ChangeMechanism changeMecha } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)ChangeMechanism.Fk)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk))] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk))] - public virtual DbUpdateException Save_required_one_to_one_changed_by_reference(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)ChangeMechanism.Fk, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), null)] + public virtual DbUpdateException Save_required_one_to_one_changed_by_reference( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -828,6 +1119,8 @@ public virtual DbUpdateException Save_required_one_to_one_changed_by_reference(C ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); root.RequiredSingle = null; @@ -898,21 +1191,66 @@ public virtual DbUpdateException Save_required_one_to_one_changed_by_reference(C } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Save_required_non_PK_one_to_one_changed_by_reference(ChangeMechanism changeMechanism, bool useExistingEntities) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Save_required_non_PK_one_to_one_changed_by_reference( + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var new2 = new RequiredNonPkSingle2(); var new2d = new RequiredNonPkSingle2Derived(); @@ -958,6 +1296,8 @@ public virtual void Save_required_non_PK_one_to_one_changed_by_reference(ChangeM }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredNonPkGraph(context); old1 = root.RequiredNonPkSingle; @@ -1012,16 +1352,30 @@ public virtual void Save_required_non_PK_one_to_one_changed_by_reference(ChangeM new1dd.MoreDerivedRootId = root.Id; } - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceRestrict) + if (Fixture.ForceRestrict + || deleteOrphansTiming == CascadeTiming.Never) { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? (Action)(() => context.ChangeTracker.DetectChanges()) + : deleteOrphansTiming == null + ? (Action)(() => context.ChangeTracker.CascadeChanges()) + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredNonPkSingle1), "{Id: 1}"), - Assert.Throws(() => context.SaveChanges()).Message); + message, + CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredNonPkSingle1), "{RootId: " + old1.RootId + "}")); } else { + Assert.True(context.ChangeTracker.HasChanges()); + + if (deleteOrphansTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -1054,7 +1408,8 @@ public virtual void Save_required_non_PK_one_to_one_changed_by_reference(ChangeM }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && deleteOrphansTiming != CascadeTiming.Never) { var loadedRoot = LoadRequiredNonPkGraph(context); @@ -1073,14 +1428,37 @@ public virtual void Save_required_non_PK_one_to_one_changed_by_reference(ChangeM } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)ChangeMechanism.Fk)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk))] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk))] - public virtual void Sever_optional_one_to_one(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)ChangeMechanism.Fk, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), null)] + public virtual void Sever_optional_one_to_one( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { Root root = null; OptionalSingle1 old1 = null; @@ -1088,6 +1466,8 @@ public virtual void Sever_optional_one_to_one(ChangeMechanism changeMechanism) ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalGraph(context); old1 = root.OptionalSingle; @@ -1142,10 +1522,21 @@ public virtual void Sever_optional_one_to_one(ChangeMechanism changeMechanism) } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - public virtual DbUpdateException Sever_required_one_to_one(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + public virtual DbUpdateException Sever_required_one_to_one( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -1155,6 +1546,8 @@ public virtual DbUpdateException Sever_required_one_to_one(ChangeMechanism chang ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredGraph(context); old1 = root.RequiredSingle; @@ -1212,10 +1605,21 @@ public virtual DbUpdateException Sever_required_one_to_one(ChangeMechanism chang } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - public virtual void Sever_required_non_PK_one_to_one(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + public virtual void Sever_required_non_PK_one_to_one( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { Root root = null; RequiredNonPkSingle1 old1 = null; @@ -1223,6 +1627,8 @@ public virtual void Sever_required_non_PK_one_to_one(ChangeMechanism changeMecha ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredNonPkGraph(context); old1 = root.RequiredNonPkSingle; @@ -1243,18 +1649,32 @@ public virtual void Sever_required_non_PK_one_to_one(ChangeMechanism changeMecha throw new ArgumentOutOfRangeException(nameof(changeMechanism)); } - Assert.False(context.Entry(root).Reference(e => e.RequiredNonPkSingle).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceRestrict) + if (Fixture.ForceRestrict + || deleteOrphansTiming == CascadeTiming.Never) { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? (Action)(() => context.ChangeTracker.DetectChanges()) + : deleteOrphansTiming == null + ? (Action)(() => context.ChangeTracker.CascadeChanges()) + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredNonPkSingle1), "{Id: 1}"), - Assert.Throws(() => context.SaveChanges()).Message); + message, + CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredNonPkSingle1), "{RootId: " + old1.RootId + "}")); } else { + Assert.False(context.Entry(root).Reference(e => e.RequiredNonPkSingle).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + if (deleteOrphansTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -1266,7 +1686,8 @@ public virtual void Sever_required_non_PK_one_to_one(ChangeMechanism changeMecha }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && deleteOrphansTiming != CascadeTiming.Never) { var loadedRoot = LoadRequiredNonPkGraph(context); @@ -1280,21 +1701,66 @@ public virtual void Sever_required_non_PK_one_to_one(ChangeMechanism changeMecha } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Reparent_optional_one_to_one(ChangeMechanism changeMechanism, bool useExistingRoot) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Reparent_optional_one_to_one( + ChangeMechanism changeMechanism, + bool useExistingRoot, + CascadeTiming? deleteOrphansTiming) { var newRoot = new Root(); Root root = null; @@ -1312,6 +1778,8 @@ public virtual void Reparent_optional_one_to_one(ChangeMechanism changeMechanism }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalGraph(context); context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; @@ -1366,21 +1834,66 @@ public virtual void Reparent_optional_one_to_one(ChangeMechanism changeMechanism } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Reparent_required_one_to_one(ChangeMechanism changeMechanism, bool useExistingRoot) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Reparent_required_one_to_one( + ChangeMechanism changeMechanism, + bool useExistingRoot, + CascadeTiming? deleteOrphansTiming) { var newRoot = new Root(); @@ -1395,6 +1908,8 @@ public virtual void Reparent_required_one_to_one(ChangeMechanism changeMechanism }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; @@ -1427,21 +1942,66 @@ public virtual void Reparent_required_one_to_one(ChangeMechanism changeMechanism } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Reparent_required_non_PK_one_to_one(ChangeMechanism changeMechanism, bool useExistingRoot) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Reparent_required_non_PK_one_to_one( + ChangeMechanism changeMechanism, + bool useExistingRoot, + CascadeTiming? deleteOrphansTiming) { var newRoot = new Root(); Root root = null; @@ -1459,6 +2019,8 @@ public virtual void Reparent_required_non_PK_one_to_one(ChangeMechanism changeMe }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredNonPkGraph(context); context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; @@ -1513,21 +2075,66 @@ public virtual void Reparent_required_non_PK_one_to_one(ChangeMechanism changeMe } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Reparent_to_different_one_to_many(ChangeMechanism changeMechanism, bool useExistingParent) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Reparent_to_different_one_to_many( + ChangeMechanism changeMechanism, + bool useExistingParent, + CascadeTiming? deleteOrphansTiming) { Root root = null; IReadOnlyList entries = null; @@ -1553,6 +2160,8 @@ public virtual void Reparent_to_different_one_to_many(ChangeMechanism changeMech }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalOneToManyGraph(context); compositeCount = context.Set().Count(); @@ -1647,21 +2256,66 @@ public virtual void Reparent_to_different_one_to_many(ChangeMechanism changeMech } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Reparent_one_to_many_overlapping(ChangeMechanism changeMechanism, bool useExistingParent) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Reparent_one_to_many_overlapping( + ChangeMechanism changeMechanism, + bool useExistingParent, + CascadeTiming? deleteOrphansTiming) { Root root = null; IReadOnlyList entries = null; @@ -1699,6 +2353,8 @@ public virtual void Reparent_one_to_many_overlapping(ChangeMechanism changeMecha }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredCompositeGraph(context); childCount = context.Set().Count(); @@ -1793,14 +2449,27 @@ public virtual void Reparent_one_to_many_overlapping(ChangeMechanism changeMecha } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)ChangeMechanism.Fk)] - public virtual void Mark_modified_one_to_many_overlapping(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)ChangeMechanism.Fk, null)] + public virtual void Mark_modified_one_to_many_overlapping( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredCompositeGraph(context); var parent = root.RequiredCompositeChildren.OrderBy(e => e.Id).First(); var child = parent.CompositeChildren.OrderBy(e => e.Id).First(); @@ -1840,22 +2509,66 @@ public virtual void Mark_modified_one_to_many_overlapping(ChangeMechanism change } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] public virtual void Save_optional_many_to_one_dependents_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var new1 = new OptionalAk1 { @@ -1901,6 +2614,8 @@ public virtual void Mark_modified_one_to_many_overlapping(ChangeMechanism change }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalAkGraph(context); var existing = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); @@ -2013,22 +2728,66 @@ public virtual void Mark_modified_one_to_many_overlapping(ChangeMechanism change } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] public virtual void Save_required_many_to_one_dependents_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var newRoot = new Root { @@ -2091,6 +2850,8 @@ public virtual void Mark_modified_one_to_many_overlapping(ChangeMechanism change }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredAkGraph(context); var existing = root.RequiredChildrenAk.OrderBy(e => e.Id).First(); @@ -2204,19 +2965,44 @@ public virtual void Mark_modified_one_to_many_overlapping(ChangeMechanism change } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Fk)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk))] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk))] - public virtual void Save_removed_optional_many_to_one_dependents_with_alternate_key(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)ChangeMechanism.Fk, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), null)] + public virtual void Save_removed_optional_many_to_one_dependents_with_alternate_key( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { Root root = null; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalAkGraph(context); var firstChild = root.OptionalChildrenAk.OrderByDescending(c => c.Id).First(); @@ -2281,10 +3067,21 @@ public virtual void Save_removed_optional_many_to_one_dependents_with_alternate_ } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - public virtual void Save_removed_required_many_to_one_dependents_with_alternate_key(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + public virtual void Save_removed_required_many_to_one_dependents_with_alternate_key( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { Root root = null; RequiredAk2 removed2 = null; @@ -2294,6 +3091,8 @@ public virtual void Save_removed_required_many_to_one_dependents_with_alternate_ ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredAkGraph(context); var firstChild = root.RequiredChildrenAk.OrderByDescending(c => c.Id).First(); @@ -2322,17 +3121,30 @@ public virtual void Save_removed_required_many_to_one_dependents_with_alternate_ throw new ArgumentOutOfRangeException(nameof(changeMechanism)); } - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceRestrict) + if (Fixture.ForceRestrict + || deleteOrphansTiming == CascadeTiming.Never) { - Add(root.RequiredChildrenAk, removed1); - Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive(nameof(RequiredAk1), nameof(RequiredAk2), "{Id: 1}"), - Assert.Throws(() => context.SaveChanges()).Message); + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? (Action)(() => context.ChangeTracker.DetectChanges()) + : deleteOrphansTiming == null + ? (Action)(() => context.ChangeTracker.CascadeChanges()) + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + + Assert.True( + message == CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredAk1), "{ParentId: " + removed1.ParentId + "}") + || message == CoreStrings.RelationshipConceptualNullSensitive(nameof(RequiredAk1), nameof(RequiredAk2), "{ParentId: " + removed2.ParentId + "}")); } else { + Assert.True(context.ChangeTracker.HasChanges()); + + if (deleteOrphansTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -2348,7 +3160,8 @@ public virtual void Save_removed_required_many_to_one_dependents_with_alternate_ }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && deleteOrphansTiming != CascadeTiming.Never) { var loadedRoot = LoadRequiredAkGraph(context); @@ -2367,21 +3180,66 @@ public virtual void Save_removed_required_many_to_one_dependents_with_alternate_ } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Save_changed_optional_one_to_one_with_alternate_key(ChangeMechanism changeMechanism, bool useExistingEntities) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Save_changed_optional_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var new2 = new OptionalSingleAk2 { @@ -2433,6 +3291,8 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key(ChangeMe }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalAkGraph(context); old1 = root.OptionalSingleAk; @@ -2734,14 +3594,34 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] public virtual void Save_required_one_to_one_changed_by_reference_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var new2 = new RequiredSingleAk2 { @@ -2776,6 +3656,8 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredAkGraph(context); old1 = root.RequiredSingleAk; @@ -2808,16 +3690,30 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store throw new ArgumentOutOfRangeException(nameof(changeMechanism)); } - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceRestrict) + if (Fixture.ForceRestrict + || deleteOrphansTiming == CascadeTiming.Never) { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? (Action)(() => context.ChangeTracker.DetectChanges()) + : deleteOrphansTiming == null + ? (Action)(() => context.ChangeTracker.CascadeChanges()) + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredSingleAk1), "{Id: 1}"), - Assert.Throws(() => context.SaveChanges()).Message); + message, + CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredSingleAk1), "{RootId: " + old1.RootId + "}")); } else { + Assert.True(context.ChangeTracker.HasChanges()); + + if (deleteOrphansTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -2842,7 +3738,8 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && deleteOrphansTiming != CascadeTiming.Never) { var loadedRoot = LoadRequiredAkGraph(context); @@ -2858,22 +3755,66 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] public virtual void Save_required_non_PK_one_to_one_changed_by_reference_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) + ChangeMechanism changeMechanism, + bool useExistingEntities, + CascadeTiming? deleteOrphansTiming) { var new2 = new RequiredNonPkSingleAk2 { @@ -2932,6 +3873,8 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredNonPkAkGraph(context); old1 = root.RequiredNonPkSingleAk; @@ -2986,16 +3929,30 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store new1dd.MoreDerivedRootId = root.AlternateId; } - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceRestrict) + if (Fixture.ForceRestrict + || deleteOrphansTiming == CascadeTiming.Never) { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? (Action)(() => context.ChangeTracker.DetectChanges()) + : deleteOrphansTiming == null + ? (Action)(() => context.ChangeTracker.CascadeChanges()) + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredNonPkSingleAk1), "{Id: 1}"), - Assert.Throws(() => context.SaveChanges()).Message); + message, + CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredNonPkSingleAk1), "{RootId: " + old1.RootId + "}")); } else { + Assert.True(context.ChangeTracker.HasChanges()); + + if (deleteOrphansTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -3028,7 +3985,8 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && deleteOrphansTiming != CascadeTiming.Never) { var loadedRoot = LoadRequiredNonPkAkGraph(context); @@ -3047,14 +4005,37 @@ public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)ChangeMechanism.Fk)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk))] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent))] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk))] - public virtual void Sever_optional_one_to_one_with_alternate_key(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)ChangeMechanism.Fk, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), null)] + public virtual void Sever_optional_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { Root root = null; OptionalSingleAk1 old1 = null; @@ -3063,6 +4044,8 @@ public virtual void Sever_optional_one_to_one_with_alternate_key(ChangeMechanism ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalAkGraph(context); old1 = root.OptionalSingleAk; @@ -3125,10 +4108,21 @@ public virtual void Sever_optional_one_to_one_with_alternate_key(ChangeMechanism } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - public virtual void Sever_required_one_to_one_with_alternate_key(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + public virtual void Sever_required_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { Root root = null; RequiredSingleAk1 old1 = null; @@ -3137,6 +4131,8 @@ public virtual void Sever_required_one_to_one_with_alternate_key(ChangeMechanism ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredAkGraph(context); old1 = root.RequiredSingleAk; @@ -3158,18 +4154,32 @@ public virtual void Sever_required_one_to_one_with_alternate_key(ChangeMechanism throw new ArgumentOutOfRangeException(nameof(changeMechanism)); } - Assert.False(context.Entry(root).Reference(e => e.RequiredSingleAk).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceRestrict) + if (Fixture.ForceRestrict + || deleteOrphansTiming == CascadeTiming.Never) { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? (Action)(() => context.ChangeTracker.DetectChanges()) + : deleteOrphansTiming == null + ? (Action)(() => context.ChangeTracker.CascadeChanges()) + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredSingleAk1), "{Id: 1}"), - Assert.Throws(() => context.SaveChanges()).Message); + message, + CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredSingleAk1), "{RootId: " + old1.RootId + "}")); } else { + Assert.False(context.Entry(root).Reference(e => e.RequiredSingleAk).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + if (deleteOrphansTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -3184,7 +4194,8 @@ public virtual void Sever_required_one_to_one_with_alternate_key(ChangeMechanism }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && deleteOrphansTiming != CascadeTiming.Never) { var loadedRoot = LoadRequiredAkGraph(context); @@ -3199,10 +4210,21 @@ public virtual void Sever_required_one_to_one_with_alternate_key(ChangeMechanism } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - public virtual void Sever_required_non_PK_one_to_one_with_alternate_key(ChangeMechanism changeMechanism) + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, null)] + [InlineData((int)ChangeMechanism.Dependent, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), null)] + public virtual void Sever_required_non_PK_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { Root root = null; RequiredNonPkSingleAk1 old1 = null; @@ -3210,6 +4232,8 @@ public virtual void Sever_required_non_PK_one_to_one_with_alternate_key(ChangeMe ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredNonPkAkGraph(context); old1 = root.RequiredNonPkSingleAk; @@ -3230,20 +4254,34 @@ public virtual void Sever_required_non_PK_one_to_one_with_alternate_key(ChangeMe throw new ArgumentOutOfRangeException(nameof(changeMechanism)); } - context.ChangeTracker.DetectChanges(); - context.ChangeTracker.DetectChanges(); - Assert.False(context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceRestrict) + if (Fixture.ForceRestrict + || deleteOrphansTiming == CascadeTiming.Never) { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? (Action)(() => context.ChangeTracker.DetectChanges()) + : deleteOrphansTiming == null + ? (Action)(() => context.ChangeTracker.CascadeChanges()) + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredNonPkSingleAk1), "{Id: 1}"), - Assert.Throws(() => context.SaveChanges()).Message); + message, + CoreStrings.RelationshipConceptualNullSensitive(nameof(Root), nameof(RequiredNonPkSingleAk1), "{RootId: " + old1.RootId + "}")); } else { + context.ChangeTracker.DetectChanges(); + context.ChangeTracker.DetectChanges(); + Assert.False(context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + if (deleteOrphansTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -3255,7 +4293,8 @@ public virtual void Sever_required_non_PK_one_to_one_with_alternate_key(ChangeMe }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && deleteOrphansTiming != CascadeTiming.Never) { var loadedRoot = LoadRequiredNonPkAkGraph(context); @@ -3269,21 +4308,66 @@ public virtual void Sever_required_non_PK_one_to_one_with_alternate_key(ChangeMe } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Reparent_optional_one_to_one_with_alternate_key(ChangeMechanism changeMechanism, bool useExistingRoot) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Reparent_optional_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + bool useExistingRoot, + CascadeTiming? deleteOrphansTiming) { var newRoot = new Root { @@ -3305,6 +4389,8 @@ public virtual void Reparent_optional_one_to_one_with_alternate_key(ChangeMechan }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadOptionalAkGraph(context); context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; @@ -3367,21 +4453,66 @@ public virtual void Reparent_optional_one_to_one_with_alternate_key(ChangeMechan } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Reparent_required_one_to_one_with_alternate_key(ChangeMechanism changeMechanism, bool useExistingRoot) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Reparent_required_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + bool useExistingRoot, + CascadeTiming? deleteOrphansTiming) { var newRoot = new Root { @@ -3403,6 +4534,8 @@ public virtual void Reparent_required_one_to_one_with_alternate_key(ChangeMechan }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredAkGraph(context); context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; @@ -3465,21 +4598,66 @@ public virtual void Reparent_required_one_to_one_with_alternate_key(ChangeMechan } [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent, false)] - [InlineData((int)ChangeMechanism.Dependent, true)] - [InlineData((int)ChangeMechanism.Principal, false)] - [InlineData((int)ChangeMechanism.Principal, true)] - [InlineData((int)ChangeMechanism.Fk, false)] - [InlineData((int)ChangeMechanism.Fk, true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false)] - [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true)] - public virtual void Reparent_required_non_PK_one_to_one_with_alternate_key(ChangeMechanism changeMechanism, bool useExistingRoot) + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.OnSaveChanges)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.OnSaveChanges)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Immediate)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Immediate)] + [InlineData((int)ChangeMechanism.Principal, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Dependent, true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, false, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Fk, true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, CascadeTiming.Never)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, CascadeTiming.Never)] + [InlineData((int)ChangeMechanism.Principal, false, null)] + [InlineData((int)ChangeMechanism.Principal, true, null)] + [InlineData((int)ChangeMechanism.Dependent, false, null)] + [InlineData((int)ChangeMechanism.Dependent, true, null)] + [InlineData((int)ChangeMechanism.Fk, false, null)] + [InlineData((int)ChangeMechanism.Fk, true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Fk), true, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), false, null)] + [InlineData((int)(ChangeMechanism.Fk | ChangeMechanism.Dependent), true, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), false, null)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent | ChangeMechanism.Fk), true, null)] + public virtual void Reparent_required_non_PK_one_to_one_with_alternate_key( + ChangeMechanism changeMechanism, + bool useExistingRoot, + CascadeTiming? deleteOrphansTiming) { var newRoot = new Root { @@ -3500,6 +4678,8 @@ public virtual void Reparent_required_non_PK_one_to_one_with_alternate_key(Chang }, context => { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + root = LoadRequiredNonPkAkGraph(context); context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; @@ -3553,8 +4733,20 @@ public virtual void Reparent_required_non_PK_one_to_one_with_alternate_key(Chang }); } - [ConditionalFact] - public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -3564,6 +4756,9 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); Assert.Equal(2, root.RequiredChildren.Count()); @@ -3580,10 +4775,23 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == (Fixture.ForceRestrict ? EntityState.Unchanged : EntityState.Deleted))); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -3605,7 +4813,8 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredGraph(context); @@ -3620,14 +4829,29 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del return updateException; } - [ConditionalFact] - public virtual void Required_many_to_one_dependent_leaves_can_be_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual void Required_many_to_one_dependent_leaves_can_be_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { var removedId = 0; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); var parent = root.RequiredChildren.First(); @@ -3640,6 +4864,11 @@ public virtual void Required_many_to_one_dependent_leaves_can_be_deleted() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -3664,8 +4893,20 @@ public virtual void Required_many_to_one_dependent_leaves_can_be_deleted() }); } - [ConditionalFact] - public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -3675,6 +4916,9 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned() ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadOptionalGraph(context); Assert.Equal(2, root.OptionalChildren.Count()); @@ -3691,6 +4935,15 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + + Assert.True(orphaned.All(e => context.Entry(e).State == (Fixture.ForceRestrict ? EntityState.Unchanged : EntityState.Modified))); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -3731,13 +4984,28 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned() return updateException; } - [ConditionalFact] - public virtual void Optional_many_to_one_dependent_leaves_can_be_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual void Optional_many_to_one_dependent_leaves_can_be_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { var removedId = 0; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadOptionalGraph(context); var parent = root.OptionalChildren.First(); @@ -3750,6 +5018,11 @@ public virtual void Optional_many_to_one_dependent_leaves_can_be_deleted() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -3775,8 +5048,20 @@ public virtual void Optional_many_to_one_dependent_leaves_can_be_deleted() }); } - [ConditionalFact] - public virtual DbUpdateException Optional_one_to_one_are_orphaned() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_one_to_one_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -3786,6 +5071,9 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned() ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadOptionalGraph(context); var removed = root.OptionalSingle; @@ -3798,6 +5086,15 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + + Assert.Equal(Fixture.ForceRestrict ? EntityState.Unchanged : EntityState.Modified, context.Entry(orphaned).State); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -3836,14 +5133,29 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned() return updateException; } - [ConditionalFact] - public virtual void Optional_one_to_one_leaf_can_be_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual void Optional_one_to_one_leaf_can_be_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { var removedId = 0; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadOptionalGraph(context); var parent = root.OptionalSingle; @@ -3855,6 +5167,11 @@ public virtual void Optional_one_to_one_leaf_can_be_deleted() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -3875,8 +5192,20 @@ public virtual void Optional_one_to_one_leaf_can_be_deleted() }); } - [ConditionalFact] - public virtual DbUpdateException Required_one_to_one_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_one_to_one_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -3886,6 +5215,9 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted() ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); var removed = root.RequiredSingle; @@ -3898,10 +5230,23 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + + Assert.Equal(Fixture.ForceRestrict ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -3922,7 +5267,8 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted() }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredGraph(context); @@ -3936,14 +5282,29 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted() return updateException; } - [ConditionalFact] - public virtual void Required_one_to_one_leaf_can_be_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual void Required_one_to_one_leaf_can_be_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { var removedId = 0; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); var parent = root.RequiredSingle; @@ -3955,6 +5316,11 @@ public virtual void Required_one_to_one_leaf_can_be_deleted() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -3975,8 +5341,20 @@ public virtual void Required_one_to_one_leaf_can_be_deleted() }); } - [ConditionalFact] - public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -3986,6 +5364,9 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted( ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredNonPkGraph(context); var removed = root.RequiredNonPkSingle; @@ -3998,10 +5379,23 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted( Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + + Assert.Equal(Fixture.ForceRestrict ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -4022,7 +5416,8 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted( }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredNonPkGraph(context); @@ -4036,14 +5431,29 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted( return updateException; } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_leaf_can_be_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual void Required_non_PK_one_to_one_leaf_can_be_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { var removedId = 0; ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredNonPkGraph(context); var parent = root.RequiredNonPkSingle; @@ -4055,6 +5465,11 @@ public virtual void Required_non_PK_one_to_one_leaf_can_be_deleted() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + context.SaveChanges(); Assert.False(context.ChangeTracker.HasChanges()); @@ -4075,8 +5490,20 @@ public virtual void Required_non_PK_one_to_one_leaf_can_be_deleted() }); } - [ConditionalFact] - public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4086,6 +5513,9 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadOptionalAkGraph(context); Assert.Equal(2, root.OptionalChildrenAk.Count()); @@ -4100,6 +5530,15 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ context.Remove(removed); + if (cascadeDeleteTiming == null) + { + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + + Assert.True(orphaned.All(e => context.Entry(e).State == (Fixture.ForceRestrict ? EntityState.Unchanged : EntityState.Modified))); + } + Assert.True(context.ChangeTracker.HasChanges()); if (Fixture.ForceRestrict) @@ -4142,8 +5581,20 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4154,6 +5605,9 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredAkGraph(context); Assert.Equal(2, root.RequiredChildrenAk.Count()); @@ -4173,19 +5627,43 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ Assert.True(context.ChangeTracker.HasChanges()); - if (Fixture.ForceRestrict) - { - updateException = Assert.Throws(() => context.SaveChanges()); - } - else + if (cascadeDeleteTiming == null) { - context.SaveChanges(); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); - Assert.False(context.ChangeTracker.HasChanges()); + context.ChangeTracker.CascadeChanges(); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Detached)); + if (Fixture.ForceRestrict) + { + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + } + else + { + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Deleted)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Deleted)); + } + + } + + if (Fixture.ForceRestrict) + { + updateException = Assert.Throws(() => context.SaveChanges()); + } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Detached)); Assert.Equal(1, root.RequiredChildrenAk.Count()); Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); @@ -4199,7 +5677,8 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredAkGraph(context); @@ -4215,8 +5694,20 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4227,6 +5718,9 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadOptionalAkGraph(context); var removed = root.OptionalSingleAk; @@ -4241,6 +5735,25 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + context.ChangeTracker.CascadeChanges(); + + if (Fixture.ForceRestrict) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + } + else + { + Assert.Equal(EntityState.Modified, context.Entry(orphaned).State); + Assert.Equal(EntityState.Modified, context.Entry(orphanedC).State); + } + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4282,8 +5795,20 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4294,6 +5819,9 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredAkGraph(context); var removed = root.RequiredSingleAk; @@ -4308,10 +5836,33 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + context.ChangeTracker.CascadeChanges(); + + if (Fixture.ForceRestrict) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + } + else + { + Assert.Equal(EntityState.Deleted, context.Entry(orphaned).State); + Assert.Equal(EntityState.Deleted, context.Entry(orphanedC).State); + } + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -4334,7 +5885,8 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredAkGraph(context); @@ -4349,8 +5901,20 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4360,6 +5924,9 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredNonPkAkGraph(context); var removed = root.RequiredNonPkSingleAk; @@ -4372,10 +5939,23 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + + Assert.Equal(Fixture.ForceRestrict ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -4396,7 +5976,8 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredNonPkAkGraph(context); @@ -4410,8 +5991,20 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4430,6 +6023,9 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.RequiredChildren).Single(IsTheRoot); var removed = root.RequiredChildren.Single(e => e.Id == removedId); @@ -4440,6 +6036,11 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4479,8 +6080,20 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4497,6 +6110,9 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_in_stor }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.RequiredSingle).Single(IsTheRoot); var removed = root.RequiredSingle; @@ -4506,6 +6122,11 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_in_stor Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4543,8 +6164,20 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_in_stor return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4561,6 +6194,9 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_ }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.RequiredNonPkSingle).Single(IsTheRoot); var removed = root.RequiredNonPkSingle; @@ -4570,6 +6206,11 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_ Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4607,8 +6248,20 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4630,6 +6283,9 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.RequiredChildrenAk).Single(IsTheRoot); var removed = root.RequiredChildrenAk.Single(e => e.Id == removedId); @@ -4638,6 +6294,11 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4679,8 +6340,20 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4699,6 +6372,9 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.RequiredSingleAk).Single(IsTheRoot); var removed = root.RequiredSingleAk; @@ -4708,6 +6384,11 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4747,8 +6428,20 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4765,6 +6458,9 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.RequiredNonPkSingleAk).Single(IsTheRoot); var removed = root.RequiredNonPkSingleAk; @@ -4774,6 +6470,11 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4811,8 +6512,20 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4831,6 +6544,9 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_in }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.OptionalChildren).Single(IsTheRoot); var removed = root.OptionalChildren.First(e => e.Id == removedId); @@ -4841,6 +6557,11 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_in Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4886,8 +6607,20 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_in return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_one_to_one_are_orphaned_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_one_to_one_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4904,6 +6637,9 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned_in_store() }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.OptionalSingle).Single(IsTheRoot); var removed = root.OptionalSingle; @@ -4913,6 +6649,11 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned_in_store() Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -4950,8 +6691,20 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned_in_store() return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -4973,6 +6726,9 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.OptionalChildrenAk).Single(IsTheRoot); var removed = root.OptionalChildrenAk.First(e => e.Id == removedId); @@ -4986,6 +6742,11 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -5039,8 +6800,20 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5059,6 +6832,9 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = context.Set().Include(e => e.OptionalSingleAk).Single(IsTheRoot); var removed = root.OptionalSingleAk; @@ -5072,6 +6848,11 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph Assert.True(context.ChangeTracker.HasChanges()); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + if (Fixture.ForceRestrict) { updateException = Assert.Throws(() => context.SaveChanges()); @@ -5111,8 +6892,20 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5129,6 +6922,9 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.RequiredChildren.First(); removedId = removed.Id; @@ -5140,7 +6936,21 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + + if (cascadeDeleteTiming == null) + { + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == expectedState)); Assert.True(context.ChangeTracker.HasChanges()); @@ -5148,6 +6958,10 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5163,7 +6977,8 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { root = LoadRequiredGraph(context); @@ -5178,8 +6993,20 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_del return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5196,6 +7023,9 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_st }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.OptionalChildren.First(); removedId = removed.Id; @@ -5207,8 +7037,21 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_st context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + if (cascadeDeleteTiming == null) + { + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.True(orphaned.All(e => context.Entry(e).State == expectedState)); Assert.True(context.ChangeTracker.HasChanges()); if (Fixture.ForceRestrict) @@ -5245,8 +7088,20 @@ public virtual DbUpdateException Optional_many_to_one_dependents_are_orphaned_st return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_one_to_one_are_orphaned_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_one_to_one_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5258,6 +7113,9 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned_starting_detac context => root = LoadOptionalGraph(context), context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.OptionalSingle; removedId = removed.Id; @@ -5267,7 +7125,21 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned_starting_detac context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -5304,8 +7176,20 @@ public virtual DbUpdateException Optional_one_to_one_are_orphaned_starting_detac return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5317,6 +7201,9 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_startin context => root = LoadRequiredGraph(context), context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.RequiredSingle; removedId = removed.Id; @@ -5326,7 +7213,21 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_startin context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -5334,6 +7235,10 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_startin { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5350,7 +7255,8 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_startin context => root = LoadRequiredGraph(context), context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { Assert.Null(root.RequiredSingle); @@ -5362,8 +7268,20 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_deleted_startin return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5375,6 +7293,9 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_ context => root = LoadRequiredNonPkGraph(context), context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.RequiredNonPkSingle; removedId = removed.Id; @@ -5384,7 +7305,21 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_ context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -5392,6 +7327,10 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_ { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5407,7 +7346,8 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_ }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { root = LoadRequiredNonPkGraph(context); @@ -5421,8 +7361,20 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5440,6 +7392,9 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.OptionalChildrenAk.OrderBy(c => c.Id).First(); removedId = removed.Id; @@ -5454,8 +7409,23 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); - Assert.True(orphanedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + + if (cascadeDeleteTiming == null) + { + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.True(orphanedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.True(orphaned.All(e => context.Entry(e).State == expectedState)); + Assert.True(orphanedC.All(e => context.Entry(e).State == expectedState)); Assert.True(context.ChangeTracker.HasChanges()); @@ -5495,8 +7465,20 @@ public virtual DbUpdateException Optional_many_to_one_dependents_with_alternate_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5514,6 +7496,9 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ }, context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.RequiredChildrenAk.OrderBy(c => c.Id).First(); removedId = removed.Id; @@ -5527,8 +7512,23 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); - Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + + if (cascadeDeleteTiming == null) + { + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == expectedState)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == expectedState)); Assert.True(context.ChangeTracker.HasChanges()); @@ -5536,6 +7536,10 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5552,7 +7556,8 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { root = LoadRequiredAkGraph(context); @@ -5568,8 +7573,20 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5582,6 +7599,9 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph context => root = LoadOptionalAkGraph(context), context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.OptionalSingleAk; removedId = removed.Id; @@ -5593,8 +7613,23 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -5633,8 +7668,20 @@ public virtual DbUpdateException Optional_one_to_one_with_alternate_key_are_orph return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5647,6 +7694,9 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc context => root = LoadRequiredAkGraph(context), context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.RequiredSingleAk; removedId = removed.Id; @@ -5658,8 +7708,23 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -5667,6 +7732,10 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5683,7 +7752,8 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { root = LoadRequiredAkGraph(context); @@ -5698,8 +7768,20 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5711,6 +7793,9 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a context => root = LoadRequiredNonPkAkGraph(context), context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var removed = root.RequiredNonPkSingleAk; removedId = removed.Id; @@ -5720,7 +7805,21 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -5728,6 +7827,10 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5743,7 +7846,8 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { root = LoadRequiredNonPkAkGraph(context); @@ -5757,8 +7861,20 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5768,6 +7884,9 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_det ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); Assert.Equal(2, root.RequiredChildren.Count()); @@ -5795,8 +7914,27 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_det context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(added).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(added).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + } + + if ((cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict) + { + Assert.Equal(EntityState.Detached, context.Entry(added).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Deleted)); + } + else + { + Assert.Equal(EntityState.Added, context.Entry(added).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + } Assert.True(context.ChangeTracker.HasChanges()); @@ -5804,6 +7942,10 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_det { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5820,7 +7962,8 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_det }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredGraph(context); @@ -5835,8 +7978,20 @@ public virtual DbUpdateException Required_many_to_one_dependents_are_cascade_det return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_one_to_one_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_one_to_one_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5846,6 +8001,9 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_detached_when_A ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredGraph(context); var removed = root.RequiredSingle; @@ -5862,7 +8020,21 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_detached_when_A context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -5870,6 +8042,10 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_detached_when_A { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5885,7 +8061,8 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_detached_when_A }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredGraph(context); @@ -5899,8 +8076,20 @@ public virtual DbUpdateException Required_one_to_one_are_cascade_detached_when_A return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5910,6 +8099,9 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_detached ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredNonPkGraph(context); var removed = root.RequiredNonPkSingle; @@ -5926,7 +8118,21 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_detached context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -5934,6 +8140,10 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_detached { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -5949,7 +8159,8 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_detached }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredNonPkGraph(context); @@ -5963,8 +8174,20 @@ public virtual DbUpdateException Required_non_PK_one_to_one_are_cascade_detached return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -5975,6 +8198,9 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredAkGraph(context); Assert.Equal(2, root.RequiredChildrenAk.Count()); @@ -6009,10 +8235,33 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(added).State); - Assert.Equal(EntityState.Added, context.Entry(addedC).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); - Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(added).State); + Assert.Equal(EntityState.Added, context.Entry(addedC).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + + context.ChangeTracker.CascadeChanges(); + } + + if ((cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict) + { + Assert.Equal(EntityState.Detached, context.Entry(added).State); + Assert.Equal(EntityState.Detached, context.Entry(addedC).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Deleted)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Deleted)); + } + else + { + Assert.Equal(EntityState.Added, context.Entry(added).State); + Assert.Equal(EntityState.Added, context.Entry(addedC).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + } Assert.True(context.ChangeTracker.HasChanges()); @@ -6020,6 +8269,10 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -6038,7 +8291,8 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredAkGraph(context); @@ -6054,8 +8308,20 @@ public virtual DbUpdateException Required_many_to_one_dependents_with_alternate_ return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -6066,6 +8332,9 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredAkGraph(context); var removed = root.RequiredSingleAk; @@ -6086,8 +8355,23 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - Assert.Equal(EntityState.Added, context.Entry(orphanedC).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + Assert.Equal(EntityState.Added, context.Entry(orphanedC).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -6095,6 +8379,10 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -6111,7 +8399,8 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredAkGraph(context); @@ -6126,8 +8415,20 @@ public virtual DbUpdateException Required_one_to_one_with_alternate_key_are_casc return updateException; } - [ConditionalFact] - public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { DbUpdateException updateException = null; @@ -6137,6 +8438,9 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var root = LoadRequiredNonPkAkGraph(context); var removed = root.RequiredNonPkSingleAk; @@ -6153,7 +8457,21 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a context.Remove(removed); Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceRestrict + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -6161,6 +8479,10 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a { updateException = Assert.Throws(() => context.SaveChanges()); } + else if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } else { context.SaveChanges(); @@ -6176,7 +8498,8 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a }, context => { - if (!Fixture.ForceRestrict) + if (!Fixture.ForceRestrict + && cascadeDeleteTiming != CascadeTiming.Never) { var root = LoadRequiredNonPkAkGraph(context); @@ -6190,8 +8513,20 @@ public virtual DbUpdateException Required_non_PK_one_to_one_with_alternate_key_a return updateException; } - [ConditionalFact] - public virtual void Re_childing_parent_to_new_child_with_delete() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(null, null)] + public virtual void Re_childing_parent_to_new_child_with_delete( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { var oldId = 0; var newId = 0; @@ -6199,6 +8534,9 @@ public virtual void Re_childing_parent_to_new_child_with_delete() ExecuteWithStrategyInTransaction( context => { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + var parent = context.Set().Include(p => p.ChildAsAParent).Single(); var oldChild = parent.ChildAsAParent; @@ -6211,6 +8549,11 @@ public virtual void Re_childing_parent_to_new_child_with_delete() context.SaveChanges(); + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + newId = newChild.Id; Assert.NotEqual(newId, oldId); diff --git a/test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs b/test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs index 75292cc9739..988f79c119f 100644 --- a/test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs +++ b/test/EFCore.Specification.Tests/LazyLoadProxyTestBase.cs @@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -754,13 +755,18 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_not_found(Entity } [Theory] - [InlineData(EntityState.Unchanged)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Deleted)] - public virtual void Lazy_load_collection_already_loaded(EntityState state) + [InlineData(EntityState.Unchanged, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, CascadeTiming.Immediate)] + public virtual void Lazy_load_collection_already_loaded(EntityState state, CascadeTiming cascadeDeleteTiming) { using (var context = CreateContext(lazyLoadingEnabled: true)) { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + var changeDetector = (ChangeDetectorProxy)context.GetService(); var parent = context.Set().Include(e => e.Children).Single(); @@ -785,20 +791,37 @@ public virtual void Lazy_load_collection_already_loaded(EntityState state) context.ChangeTracker.LazyLoadingEnabled = false; Assert.Equal(2, parent.Children.Count()); - Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + + if (state == EntityState.Deleted + && cascadeDeleteTiming == CascadeTiming.Immediate) + { + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Null(c)); + } + else + { + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + } Assert.Equal(3, context.ChangeTracker.Entries().Count()); } } [Theory] - [InlineData(EntityState.Unchanged)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Deleted)] - public virtual void Lazy_load_many_to_one_reference_to_principal_already_loaded(EntityState state) + [InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, CascadeTiming.Never)] + [InlineData(EntityState.Modified, CascadeTiming.Never)] + [InlineData(EntityState.Deleted, CascadeTiming.Never)] + public virtual void Lazy_load_many_to_one_reference_to_principal_already_loaded(EntityState state, CascadeTiming cascadeDeleteTiming) { using (var context = CreateContext(lazyLoadingEnabled: true)) { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + var changeDetector = (ChangeDetectorProxy)context.GetService(); var child = context.Set().Include(e => e.Parent).Single(e => e.Id == 12); @@ -872,13 +895,22 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_already_loaded(E } [Theory] - [InlineData(EntityState.Unchanged)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Deleted)] - public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state) + [InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, CascadeTiming.Never)] + [InlineData(EntityState.Modified, CascadeTiming.Never)] + [InlineData(EntityState.Deleted, CascadeTiming.Never)] + public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded( + EntityState state, CascadeTiming cascadeDeleteTiming) { using (var context = CreateContext(lazyLoadingEnabled: true)) { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + var changeDetector = (ChangeDetectorProxy)context.GetService(); var parent = context.Set().Include(e => e.Single).Single(); @@ -907,7 +939,17 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(E var single = context.ChangeTracker.Entries().Single().Entity; Assert.Same(single, parent.Single); - Assert.Same(parent, single.Parent); + + if (cascadeDeleteTiming == CascadeTiming.Immediate + && state == EntityState.Deleted) + { + // No fixup to Deleted entity. + Assert.Null(single.Parent); + } + else + { + Assert.Same(parent, single.Parent); + } } } diff --git a/test/EFCore.Specification.Tests/LoadTestBase.cs b/test/EFCore.Specification.Tests/LoadTestBase.cs index 22c46edbb81..790680e31c8 100644 --- a/test/EFCore.Specification.Tests/LoadTestBase.cs +++ b/test/EFCore.Specification.Tests/LoadTestBase.cs @@ -6,6 +6,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Infrastructure; @@ -512,13 +513,18 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_not_found(Entity } [Theory] - [InlineData(EntityState.Unchanged)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Deleted)] - public virtual void Lazy_load_collection_already_loaded(EntityState state) + [InlineData(EntityState.Unchanged, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)] + public virtual void Lazy_load_collection_already_loaded(EntityState state, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext(lazyLoadingEnabled: true)) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var changeDetector = (ChangeDetectorProxy)context.GetService(); var parent = context.Set().Include(e => e.Children).Single(); @@ -543,7 +549,16 @@ public virtual void Lazy_load_collection_already_loaded(EntityState state) context.ChangeTracker.LazyLoadingEnabled = false; Assert.Equal(2, parent.Children.Count()); - Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + + if (state == EntityState.Deleted + && deleteOrphansTiming != CascadeTiming.Never) + { + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Null(c)); + } + else + { + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + } Assert.Equal(3, context.ChangeTracker.Entries().Count()); } @@ -630,13 +645,18 @@ public virtual void Lazy_load_one_to_one_reference_to_principal_already_loaded(E } [Theory] - [InlineData(EntityState.Unchanged)] - [InlineData(EntityState.Modified)] - [InlineData(EntityState.Deleted)] - public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state) + [InlineData(EntityState.Unchanged, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, CascadeTiming.OnSaveChanges)] + public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext(lazyLoadingEnabled: true)) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var changeDetector = (ChangeDetectorProxy)context.GetService(); var parent = context.Set().Include(e => e.Single).Single(); @@ -665,7 +685,16 @@ public virtual void Lazy_load_one_to_one_reference_to_dependent_already_loaded(E var single = context.ChangeTracker.Entries().Single().Entity; Assert.Same(single, parent.Single); - Assert.Same(parent, single.Parent); + + if (state == EntityState.Deleted + && deleteOrphansTiming != CascadeTiming.Never) + { + Assert.Null(single.Parent); + } + else + { + Assert.Same(parent, single.Parent); + } } } @@ -2498,16 +2527,24 @@ public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_not } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_collection_already_loaded(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_collection_already_loaded(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var parent = context.Set().Include(e => e.Children).Single(); ClearLog(); @@ -2532,7 +2569,16 @@ public virtual async Task Load_collection_already_loaded(EntityState state, bool RecordLog(); Assert.Equal(2, parent.Children.Count()); - Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + + if (state == EntityState.Deleted + && deleteOrphansTiming != CascadeTiming.Never) + { + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Null(c)); + } + else + { + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + } Assert.Equal(3, context.ChangeTracker.Entries().Count()); } @@ -2582,16 +2628,24 @@ public virtual async Task Load_many_to_one_reference_to_principal_already_loaded } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_one_to_one_reference_to_principal_already_loaded(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_one_to_one_reference_to_principal_already_loaded(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var single = context.Set().Include(e => e.Parent).Single(); ClearLog(); @@ -2625,16 +2679,24 @@ public virtual async Task Load_one_to_one_reference_to_principal_already_loaded( } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_one_to_one_reference_to_dependent_already_loaded(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_one_to_one_reference_to_dependent_already_loaded(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var parent = context.Set().Include(e => e.Single).Single(); ClearLog(); @@ -2663,7 +2725,16 @@ public virtual async Task Load_one_to_one_reference_to_dependent_already_loaded( var single = context.ChangeTracker.Entries().Single().Entity; Assert.Same(single, parent.Single); - Assert.Same(parent, single.Parent); + + if (state == EntityState.Deleted + && deleteOrphansTiming != CascadeTiming.Never) + { + Assert.Null(single.Parent); + } + else + { + Assert.Same(parent, single.Parent); + } } } @@ -2754,16 +2825,25 @@ public virtual async Task Load_one_to_one_PK_to_PK_reference_to_dependent_alread } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_collection_using_Query_already_loaded(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_collection_using_Query_already_loaded(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; + var parent = context.Set().Include(e => e.Children).Single(); ClearLog(); @@ -2786,7 +2866,6 @@ public virtual async Task Load_collection_using_Query_already_loaded(EntityState Assert.Equal(2, parent.Children.Count()); Assert.All(children.Select(e => e.Parent), c => Assert.Same(parent, c)); Assert.All(children, p => Assert.Contains(p, parent.Children)); - Assert.Equal(3, context.ChangeTracker.Entries().Count()); } } @@ -2866,16 +2945,25 @@ public virtual async Task Load_one_to_one_reference_to_principal_using_Query_alr } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_already_loaded(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_already_loaded(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; + var parent = context.Set().Include(e => e.Single).Single(); ClearLog(); @@ -2897,7 +2985,6 @@ public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_alr Assert.NotNull(single); Assert.Same(single, parent.Single); Assert.Same(parent, single.Parent); - Assert.Equal(2, context.ChangeTracker.Entries().Count()); } } @@ -3638,16 +3725,24 @@ public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_not } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_collection_already_loaded_untyped(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_collection_already_loaded_untyped(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var parent = context.Set().Include(e => e.Children).Single(); ClearLog(); @@ -3672,7 +3767,16 @@ public virtual async Task Load_collection_already_loaded_untyped(EntityState sta RecordLog(); Assert.Equal(2, parent.Children.Count()); - Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + + if (state == EntityState.Deleted + && deleteOrphansTiming != CascadeTiming.Never) + { + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Null(c)); + } + else + { + Assert.All(parent.Children.Select(e => e.Parent), c => Assert.Same(parent, c)); + } Assert.Equal(3, context.ChangeTracker.Entries().Count()); } @@ -3765,16 +3869,24 @@ public virtual async Task Load_one_to_one_reference_to_principal_already_loaded_ } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_one_to_one_reference_to_dependent_already_loaded_untyped(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_one_to_one_reference_to_dependent_already_loaded_untyped(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var parent = context.Set().Include(e => e.Single).Single(); ClearLog(); @@ -3803,21 +3915,39 @@ public virtual async Task Load_one_to_one_reference_to_dependent_already_loaded_ var single = context.ChangeTracker.Entries().Single().Entity; Assert.Same(single, parent.Single); - Assert.Same(parent, single.Parent); + + if (state == EntityState.Deleted + && deleteOrphansTiming != CascadeTiming.Never) + { + Assert.Null(single.Parent); + } + else + { + Assert.Same(parent, single.Parent); + } } } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_collection_using_Query_already_loaded_untyped(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_collection_using_Query_already_loaded_untyped(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; + var parent = context.Set().Include(e => e.Children).Single(); ClearLog(); @@ -3840,7 +3970,6 @@ public virtual async Task Load_collection_using_Query_already_loaded_untyped(Ent Assert.Equal(2, parent.Children.Count()); Assert.All(children.Select(e => ((Child)e).Parent), c => Assert.Same(parent, c)); Assert.All(children, p => Assert.Contains(p, parent.Children)); - Assert.Equal(3, context.ChangeTracker.Entries().Count()); } } @@ -3920,16 +4049,25 @@ public virtual async Task Load_one_to_one_reference_to_principal_using_Query_alr } [Theory] - [InlineData(EntityState.Unchanged, true)] - [InlineData(EntityState.Unchanged, false)] - [InlineData(EntityState.Modified, true)] - [InlineData(EntityState.Modified, false)] - [InlineData(EntityState.Deleted, true)] - [InlineData(EntityState.Deleted, false)] - public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_already_loaded_untyped(EntityState state, bool async) + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_already_loaded_untyped(EntityState state, bool async, CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + context.ChangeTracker.CascadeDeleteTiming = CascadeTiming.OnSaveChanges; + var parent = context.Set().Include(e => e.Single).Single(); ClearLog(); @@ -3950,6 +4088,7 @@ public virtual async Task Load_one_to_one_reference_to_dependent_using_Query_alr Assert.NotNull(single); Assert.Same(single, parent.Single); + Assert.Same(parent, ((Single)single).Parent); Assert.Equal(2, context.ChangeTracker.Entries().Count()); diff --git a/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs b/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs index a0a62045d71..29e927aec4a 100644 --- a/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs +++ b/test/EFCore.Specification.Tests/PropertyValuesTestBase.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.ChangeTracking; @@ -1658,42 +1659,74 @@ private void TestKeyChange(Func, PropertyValues> getProper } } - [Fact] - public virtual void Non_nullable_property_in_current_values_results_in_conceptual_null() + [Theory] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never)] + public virtual void Non_nullable_property_in_current_values_results_in_conceptual_null(CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var building = context.Set().Single(b => b.Name == "Building One"); var entry = context.Entry(building); var values = entry.CurrentValues; + var originalValue = values["Value"]; Assert.False(entry.GetInfrastructure().HasConceptualNull); - values["Value"] = null; + if (deleteOrphansTiming == CascadeTiming.Immediate) + { + Assert.Equal( + CoreStrings.PropertyConceptualNullSensitive( + "Value", + nameof(Building), + "{Value: " + Convert.ToString(originalValue, CultureInfo.InvariantCulture) + "}"), + Assert.Throws(() => values["Value"] = null).Message); + } + else + { + values["Value"] = null; - Assert.True(entry.GetInfrastructure().HasConceptualNull); + Assert.True(entry.GetInfrastructure().HasConceptualNull); - Assert.Equal(1500000m, values["Value"]); - Assert.Equal(1500000m, building.Value); + Assert.Equal(1500000m, values["Value"]); + Assert.Equal(1500000m, building.Value); + } } } - [Fact] - public virtual void Non_nullable_shadow_property_in_current_values_results_in_conceptual_null() + [Theory] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never)] + public virtual void Non_nullable_shadow_property_in_current_values_results_in_conceptual_null(CascadeTiming deleteOrphansTiming) { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + var building = context.Set().Single(b => b.Name == "Building One"); var entry = context.Entry(building); var values = entry.CurrentValues; Assert.False(entry.GetInfrastructure().HasConceptualNull); - values["Shadow1"] = null; + if (deleteOrphansTiming == CascadeTiming.Immediate) + { + Assert.Equal( + CoreStrings.PropertyConceptualNullSensitive("Shadow1", nameof(Building), "{Shadow1: 11}"), + Assert.Throws(() => values["Shadow1"] = null).Message); + } + else + { + values["Shadow1"] = null; - Assert.True(entry.GetInfrastructure().HasConceptualNull); + Assert.True(entry.GetInfrastructure().HasConceptualNull); - Assert.Equal(11, values["Shadow1"]); + Assert.Equal(11, values["Shadow1"]); + } } } diff --git a/test/EFCore.Specification.Tests/ProxyGraphUpdatesTestBase.cs b/test/EFCore.Specification.Tests/ProxyGraphUpdatesTestBase.cs index b5fd96081de..2ccdb78543b 100644 --- a/test/EFCore.Specification.Tests/ProxyGraphUpdatesTestBase.cs +++ b/test/EFCore.Specification.Tests/ProxyGraphUpdatesTestBase.cs @@ -2909,16 +2909,30 @@ public virtual void Reparent_required_non_PK_one_to_one_with_alternate_key(Chang }); } - [ConditionalFact] - public virtual void Required_many_to_one_dependents_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_many_to_one_dependents_are_cascade_deleted( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); Assert.Equal(2, root.RequiredChildren.Count()); @@ -2934,44 +2948,68 @@ public virtual void Required_many_to_one_dependents_are_cascade_deleted() Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.Equal(1, root.RequiredChildren.Count()); - Assert.DoesNotContain(removedId, root.RequiredChildren.Select(e => e.Id)); + Assert.Equal(1, root.RequiredChildren.Count()); + Assert.DoesNotContain(removedId, root.RequiredChildren.Select(e => e.Id)); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - }, + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Equal(1, root.RequiredChildren.Count()); - Assert.DoesNotContain(removedId, root.RequiredChildren.Select(e => e.Id)); + Assert.Equal(1, root.RequiredChildren.Count()); + Assert.DoesNotContain(removedId, root.RequiredChildren.Select(e => e.Id)); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); + } + }); } - [ConditionalFact] - public virtual void Optional_many_to_one_dependents_are_orphaned() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_many_to_one_dependents_are_orphaned( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); Assert.Equal(2, root.OptionalChildren.Count()); @@ -3015,16 +3053,30 @@ public virtual void Optional_many_to_one_dependents_are_orphaned() }); } - [ConditionalFact] - public virtual void Optional_one_to_one_are_orphaned() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_one_to_one_are_orphaned( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.OptionalSingle; @@ -3062,16 +3114,30 @@ public virtual void Optional_one_to_one_are_orphaned() }); } - [ConditionalFact] - public virtual void Required_one_to_one_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_one_to_one_are_cascade_deleted( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.RequiredSingle; @@ -3083,42 +3149,66 @@ public virtual void Required_one_to_one_are_cascade_deleted() Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Null(root.RequiredSingle); + Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredSingle); + Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_non_PK_one_to_one_are_cascade_deleted( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.RequiredNonPkSingle; @@ -3130,42 +3220,66 @@ public virtual void Required_non_PK_one_to_one_are_cascade_deleted() Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Null(root.RequiredNonPkSingle); + Assert.Null(root.RequiredNonPkSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredNonPkSingle); + Assert.Null(root.RequiredNonPkSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orphaned() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orphaned( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); Assert.Equal(2, root.OptionalChildrenAk.Count()); @@ -3210,8 +3324,19 @@ public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orpha }); } - [ConditionalFact] - public virtual void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -3219,8 +3344,11 @@ public virtual void Required_many_to_one_dependents_with_alternate_key_are_casca ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); Assert.Equal(2, root.RequiredChildrenAk.Count()); @@ -3239,38 +3367,59 @@ public virtual void Required_many_to_one_dependents_with_alternate_key_are_casca Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.Equal(1, root.RequiredChildrenAk.Count()); - Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); + Assert.Equal(1, root.RequiredChildrenAk.Count()); + Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - }, + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Equal(1, root.RequiredChildrenAk.Count()); - Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); + Assert.Equal(1, root.RequiredChildrenAk.Count()); + Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); - Assert.Empty(context.Set().Where(e => orphanedIdCs.Contains(e.Id))); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); + Assert.Empty(context.Set().Where(e => orphanedIdCs.Contains(e.Id))); + } + }); } - [ConditionalFact] - public virtual void Optional_one_to_one_with_alternate_key_are_orphaned() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_one_to_one_with_alternate_key_are_orphaned( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -3278,8 +3427,11 @@ public virtual void Optional_one_to_one_with_alternate_key_are_orphaned() ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.OptionalSingleAk; @@ -3322,8 +3474,19 @@ public virtual void Optional_one_to_one_with_alternate_key_are_orphaned() }); } - [ConditionalFact] - public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -3331,8 +3494,11 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted() ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.RequiredSingleAk; @@ -3346,45 +3512,69 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted() Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); - Assert.Null(root.RequiredSingleAk); + Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredSingleAk); + Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + } + }); } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.RequiredNonPkSingleAk; @@ -3396,34 +3586,55 @@ public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_de Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Null(root.RequiredNonPkSingleAk); + Assert.Null(root.RequiredNonPkSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredNonPkSingleAk); + Assert.Null(root.RequiredNonPkSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Required_many_to_one_dependents_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_many_to_one_dependents_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -3439,8 +3650,11 @@ public virtual void Required_many_to_one_dependents_are_cascade_deleted_in_store Assert.Equal(2, orphanedIds.Count); }, context => - { - var root = context.Set().Include(e => e.RequiredChildren).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredChildren).Single(IsTheRoot); var removed = root.RequiredChildren.Single(e => e.Id == removedId); @@ -3477,8 +3691,19 @@ public virtual void Required_many_to_one_dependents_are_cascade_deleted_in_store }); } - [ConditionalFact] - public virtual void Required_one_to_one_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_one_to_one_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -3492,8 +3717,11 @@ public virtual void Required_one_to_one_are_cascade_deleted_in_store() orphanedId = removed.Single.Id; }, context => - { - var root = context.Set().Include(e => e.RequiredSingle).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredSingle).Single(IsTheRoot); var removed = root.RequiredSingle; var orphaned = removed.Single; @@ -3502,33 +3730,54 @@ public virtual void Required_one_to_one_are_cascade_deleted_in_store() Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Null(root.RequiredSingle); + Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredSingle); + Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_non_PK_one_to_one_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -3542,8 +3791,11 @@ public virtual void Required_non_PK_one_to_one_are_cascade_deleted_in_store() orphanedId = removed.Single.Id; }, context => - { - var root = context.Set().Include(e => e.RequiredNonPkSingle).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredNonPkSingle).Single(IsTheRoot); var removed = root.RequiredNonPkSingle; var orphaned = removed.Single; @@ -3552,33 +3804,54 @@ public virtual void Required_non_PK_one_to_one_are_cascade_deleted_in_store() Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Null(root.RequiredNonPkSingle); + Assert.Null(root.RequiredNonPkSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredNonPkSingle); + Assert.Null(root.RequiredNonPkSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -3597,8 +3870,11 @@ public virtual void Required_many_to_one_dependents_with_alternate_key_are_casca Assert.Equal(2, orphanedIdCs.Count); }, context => - { - var root = context.Set().Include(e => e.RequiredChildrenAk).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredChildrenAk).Single(IsTheRoot); var removed = root.RequiredChildrenAk.Single(e => e.Id == removedId); @@ -3635,8 +3911,19 @@ public virtual void Required_many_to_one_dependents_with_alternate_key_are_casca }); } - [ConditionalFact] - public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -3652,8 +3939,11 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_i orphanedIdC = removed.SingleComposite.Id; }, context => - { - var root = context.Set().Include(e => e.RequiredSingleAk).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredSingleAk).Single(IsTheRoot); var removed = root.RequiredSingleAk; var orphaned = removed.Single; @@ -3662,35 +3952,56 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_i Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Null(root.RequiredSingleAk); + Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredSingleAk); + Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + } + }); } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -3704,8 +4015,11 @@ public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_de orphanedId = removed.Single.Id; }, context => - { - var root = context.Set().Include(e => e.RequiredNonPkSingleAk).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredNonPkSingleAk).Single(IsTheRoot); var removed = root.RequiredNonPkSingleAk; var orphaned = removed.Single; @@ -3714,33 +4028,54 @@ public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_de Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Null(root.RequiredNonPkSingleAk); + Assert.Null(root.RequiredNonPkSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredNonPkSingleAk); + Assert.Null(root.RequiredNonPkSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Optional_many_to_one_dependents_are_orphaned_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_many_to_one_dependents_are_orphaned_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -3756,8 +4091,11 @@ public virtual void Optional_many_to_one_dependents_are_orphaned_in_store() Assert.Equal(2, orphanedIds.Count); }, context => - { - var root = context.Set().Include(e => e.OptionalChildren).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.OptionalChildren).Single(IsTheRoot); var removed = root.OptionalChildren.First(e => e.Id == removedId); @@ -3800,8 +4138,19 @@ public virtual void Optional_many_to_one_dependents_are_orphaned_in_store() }); } - [ConditionalFact] - public virtual void Optional_one_to_one_are_orphaned_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_one_to_one_are_orphaned_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -3815,8 +4164,11 @@ public virtual void Optional_one_to_one_are_orphaned_in_store() orphanedId = removed.Single.Id; }, context => - { - var root = context.Set().Include(e => e.OptionalSingle).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.OptionalSingle).Single(IsTheRoot); var removed = root.OptionalSingle; var orphaned = removed.Single; @@ -3850,8 +4202,19 @@ public virtual void Optional_one_to_one_are_orphaned_in_store() }); } - [ConditionalFact] - public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -3870,8 +4233,11 @@ public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orpha Assert.Equal(2, orphanedIdCs.Count); }, context => - { - var root = context.Set().Include(e => e.OptionalChildrenAk).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.OptionalChildrenAk).Single(IsTheRoot); var removed = root.OptionalChildrenAk.First(e => e.Id == removedId); @@ -3925,8 +4291,19 @@ public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orpha }); } - [ConditionalFact] - public virtual void Optional_one_to_one_with_alternate_key_are_orphaned_in_store() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_one_to_one_with_alternate_key_are_orphaned_in_store( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -3942,8 +4319,11 @@ public virtual void Optional_one_to_one_with_alternate_key_are_orphaned_in_store orphanedIdC = removed.SingleComposite.Id; }, context => - { - var root = context.Set().Include(e => e.OptionalSingleAk).Single(IsTheRoot); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.OptionalSingleAk).Single(IsTheRoot); var removed = root.OptionalSingleAk; var orphaned = removed.Single; @@ -3983,8 +4363,19 @@ public virtual void Optional_one_to_one_with_alternate_key_are_orphaned_in_store }); } - [ConditionalFact] - public virtual void Required_many_to_one_dependents_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_many_to_one_dependents_are_cascade_deleted_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -4002,43 +4393,72 @@ public virtual void Required_many_to_one_dependents_are_cascade_deleted_starting Assert.Equal(2, root.RequiredChildren.Count()); }, context => - { - removedId = removed.Id; - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); Assert.Equal(2, orphanedIds.Count); context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == expectedState)); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - }, + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, context => - { - root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); - Assert.Equal(1, root.RequiredChildren.Count()); - Assert.DoesNotContain(removedId, root.RequiredChildren.Select(e => e.Id)); + Assert.Equal(1, root.RequiredChildren.Count()); + Assert.DoesNotContain(removedId, root.RequiredChildren.Select(e => e.Id)); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); + } + }); } - [ConditionalFact] - public virtual void Optional_many_to_one_dependents_are_orphaned_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_many_to_one_dependents_are_orphaned_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -4056,16 +4476,24 @@ public virtual void Optional_many_to_one_dependents_are_orphaned_starting_detach Assert.Equal(2, root.OptionalChildren.Count()); }, context => - { - removedId = removed.Id; - orphanedIds = orphaned.Select(e => e.Id).ToList(); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedIds = orphaned.Select(e => e.Id).ToList(); Assert.Equal(2, orphanedIds.Count); context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.True(orphaned.All(e => context.Entry(e).State == expectedState)); Assert.True(context.ChangeTracker.HasChanges()); @@ -4091,8 +4519,19 @@ public virtual void Optional_many_to_one_dependents_are_orphaned_starting_detach }); } - [ConditionalFact] - public virtual void Optional_one_to_one_are_orphaned_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_one_to_one_are_orphaned_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -4108,14 +4547,22 @@ public virtual void Optional_one_to_one_are_orphaned_starting_detached() orphaned = removed.Single; }, context => - { - removedId = removed.Id; - orphanedId = orphaned.Id; + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedId = orphaned.Id; context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -4140,8 +4587,19 @@ public virtual void Optional_one_to_one_are_orphaned_starting_detached() }); } - [ConditionalFact] - public virtual void Required_one_to_one_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_one_to_one_are_cascade_deleted_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -4157,39 +4615,68 @@ public virtual void Required_one_to_one_are_cascade_deleted_starting_detached() orphaned = removed.Single; }, context => - { - removedId = removed.Id; - orphanedId = orphaned.Id; + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedId = orphaned.Id; context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => { - root = LoadRoot(context); - Assert.Null(root.RequiredSingle); + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); + Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } }); } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_non_PK_one_to_one_are_cascade_deleted_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -4205,40 +4692,69 @@ public virtual void Required_non_PK_one_to_one_are_cascade_deleted_starting_deta orphaned = removed.Single; }, context => - { - removedId = removed.Id; - orphanedId = orphaned.Id; + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedId = orphaned.Id; context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); - Assert.Null(root.RequiredNonPkSingle); + Assert.Null(root.RequiredNonPkSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orphaned_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orphaned_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -4259,19 +4775,27 @@ public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orpha Assert.Equal(2, root.OptionalChildrenAk.Count()); }, context => - { - removedId = removed.Id; - orphanedIds = orphaned.Select(e => e.Id).ToList(); - orphanedIdCs = orphanedC.Select(e => e.Id).ToList(); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedIds = orphaned.Select(e => e.Id).ToList(); + orphanedIdCs = orphanedC.Select(e => e.Id).ToList(); Assert.Equal(2, orphanedIds.Count); Assert.Equal(2, orphanedIdCs.Count); context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); - Assert.True(orphanedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.True(orphaned.All(e => context.Entry(e).State == expectedState)); + Assert.True(orphanedC.All(e => context.Entry(e).State == expectedState)); Assert.True(context.ChangeTracker.HasChanges()); @@ -4299,8 +4823,19 @@ public virtual void Optional_many_to_one_dependents_with_alternate_key_are_orpha }); } - [ConditionalFact] - public virtual void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -4321,47 +4856,76 @@ public virtual void Required_many_to_one_dependents_with_alternate_key_are_casca Assert.Equal(2, root.RequiredChildrenAk.Count()); }, context => - { - removedId = removed.Id; - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); Assert.Equal(2, orphanedIds.Count); context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); - Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == expectedState)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == expectedState)); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - }, + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, context => - { - root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); - Assert.Equal(1, root.RequiredChildrenAk.Count()); - Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); + Assert.Equal(1, root.RequiredChildrenAk.Count()); + Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); - Assert.Empty(context.Set().Where(e => orphanedIdCs.Contains(e.Id))); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); + Assert.Empty(context.Set().Where(e => orphanedIdCs.Contains(e.Id))); + } + }); } - [ConditionalFact] - public virtual void Optional_one_to_one_with_alternate_key_are_orphaned_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Optional_one_to_one_with_alternate_key_are_orphaned_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -4380,16 +4944,24 @@ public virtual void Optional_one_to_one_with_alternate_key_are_orphaned_starting orphanedC = removed.SingleComposite; }, context => - { - removedId = removed.Id; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); Assert.True(context.ChangeTracker.HasChanges()); @@ -4416,8 +4988,19 @@ public virtual void Optional_one_to_one_with_alternate_key_are_orphaned_starting }); } - [ConditionalFact] - public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -4436,44 +5019,73 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_deleted_s orphanedC = removed.SingleComposite; }, context => - { - removedId = removed.Id; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); - Assert.Null(root.RequiredSingleAk); + Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + } + }); } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -4489,48 +5101,80 @@ public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_de orphaned = removed.Single; }, context => - { - removedId = removed.Id; - orphanedId = orphaned.Id; + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + removedId = removed.Id; + orphanedId = orphaned.Id; context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); - Assert.Null(root.RequiredNonPkSingleAk); + Assert.Null(root.RequiredNonPkSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Required_many_to_one_dependents_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_many_to_one_dependents_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); Assert.Equal(2, root.RequiredChildren.Count()); @@ -4550,51 +5194,85 @@ public virtual void Required_many_to_one_dependents_are_cascade_detached_when_Ad context.ChangeTracker.DetectChanges(); } - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(added).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + + Assert.Equal(EntityState.Added, context.Entry(added).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(added).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == CascadeTiming.Immediate) + { + Assert.Equal(EntityState.Detached, context.Entry(added).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Deleted)); + } + else + { + Assert.Equal(EntityState.Added, context.Entry(added).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + } Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(added).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(added).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.Same(root, removed.Parent); - Assert.Equal(3, removed.Children.Count()); - }, + Assert.Same(root, removed.Parent); + Assert.Equal(3, removed.Children.Count()); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Equal(1, root.RequiredChildren.Count()); - Assert.DoesNotContain(removedId, root.RequiredChildren.Select(e => e.Id)); + Assert.Equal(1, root.RequiredChildren.Count()); + Assert.DoesNotContain(removedId, root.RequiredChildren.Select(e => e.Id)); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); + } + }); } - [ConditionalFact] - public virtual void Required_one_to_one_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_one_to_one_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.RequiredSingle; @@ -4609,42 +5287,71 @@ public virtual void Required_one_to_one_are_cascade_detached_when_Added() context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredSingle); + Assert.Null(root.RequiredSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_non_PK_one_to_one_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.RequiredNonPkSingle; @@ -4659,34 +5366,60 @@ public virtual void Required_non_PK_one_to_one_are_cascade_detached_when_Added() context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredNonPkSingle); + Assert.Null(root.RequiredNonPkSingle); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } - [ConditionalFact] - public virtual void Required_many_to_one_dependents_with_alternate_key_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_many_to_one_dependents_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; List orphanedIds = null; @@ -4694,8 +5427,11 @@ public virtual void Required_many_to_one_dependents_with_alternate_key_are_casca ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); Assert.Equal(2, root.RequiredChildrenAk.Count()); @@ -4728,42 +5464,74 @@ public virtual void Required_many_to_one_dependents_with_alternate_key_are_casca context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(added).State); - Assert.Equal(EntityState.Added, context.Entry(addedC).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); - Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == CascadeTiming.Immediate) + { + Assert.Equal(EntityState.Detached, context.Entry(added).State); + Assert.Equal(EntityState.Detached, context.Entry(addedC).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Deleted)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Deleted)); + } + else + { + Assert.Equal(EntityState.Added, context.Entry(added).State); + Assert.Equal(EntityState.Added, context.Entry(addedC).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Unchanged)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Unchanged)); + } Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(added).State); - Assert.Equal(EntityState.Detached, context.Entry(addedC).State); - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(added).State); + Assert.Equal(EntityState.Detached, context.Entry(addedC).State); + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == EntityState.Detached)); + Assert.True(cascadeRemovedC.All(e => context.Entry(e).State == EntityState.Detached)); - Assert.Same(root, removed.Parent); - Assert.Equal(3, removed.Children.Count()); - }, + Assert.Same(root, removed.Parent); + Assert.Equal(3, removed.Children.Count()); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Equal(1, root.RequiredChildrenAk.Count()); - Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); + Assert.Equal(1, root.RequiredChildrenAk.Count()); + Assert.DoesNotContain(removedId, root.RequiredChildrenAk.Select(e => e.Id)); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); - Assert.Empty(context.Set().Where(e => orphanedIdCs.Contains(e.Id))); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => orphanedIds.Contains(e.Id))); + Assert.Empty(context.Set().Where(e => orphanedIdCs.Contains(e.Id))); + } + }); } - [ConditionalFact] - public virtual void Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; @@ -4771,8 +5539,11 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_detached_ ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.RequiredSingleAk; @@ -4791,45 +5562,74 @@ public virtual void Required_one_to_one_with_alternate_key_are_cascade_detached_ context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - Assert.Equal(EntityState.Added, context.Entry(orphanedC).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredSingleAk); + Assert.Null(root.RequiredSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + } + }); } - [ConditionalFact] - public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never, CascadeTiming.Never)] + public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming cascadeDeleteTiming, + CascadeTiming deleteOrphansTiming) { var removedId = 0; var orphanedId = 0; ExecuteWithStrategyInTransaction( context => - { - var root = LoadRoot(context); + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); var removed = root.RequiredNonPkSingleAk; @@ -4844,30 +5644,45 @@ public virtual void Required_non_PK_one_to_one_with_alternate_key_are_cascade_de context.Remove(removed); - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); Assert.True(context.ChangeTracker.HasChanges()); - context.SaveChanges(); + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); - Assert.False(context.ChangeTracker.HasChanges()); + Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, context => - { - var root = LoadRoot(context); + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); - Assert.Null(root.RequiredNonPkSingleAk); + Assert.Null(root.RequiredNonPkSingleAk); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - }); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); } [ConditionalFact] diff --git a/test/EFCore.Specification.Tests/StoreGeneratedFixupTestBase.cs b/test/EFCore.Specification.Tests/StoreGeneratedFixupTestBase.cs index 5d5a5078ea1..4f4ebc0fd50 100644 --- a/test/EFCore.Specification.Tests/StoreGeneratedFixupTestBase.cs +++ b/test/EFCore.Specification.Tests/StoreGeneratedFixupTestBase.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; @@ -4003,6 +4004,8 @@ public virtual void Remove_overlapping_principal() { using (var context = CreateContext()) { + context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + var game = new Game { Id = Guid77 }; var level = new Level { Game = game }; var item = new Item { Level = level }; diff --git a/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs b/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs index 531d12164ba..3093057ad41 100644 --- a/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/DbContextPoolingTest.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.TestUtilities; @@ -74,6 +75,8 @@ public PooledContext(DbContextOptions options) ChangeTracker.AutoDetectChangesEnabled = false; ChangeTracker.LazyLoadingEnabled = false; Database.AutoTransactionsEnabled = false; + ChangeTracker.CascadeDeleteTiming = CascadeTiming.Never; + ChangeTracker.DeleteOrphansTiming= CascadeTiming.Never; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) @@ -289,6 +292,8 @@ public void Context_configuration_is_reset(bool useInterface) context1.ChangeTracker.AutoDetectChangesEnabled = true; context1.ChangeTracker.LazyLoadingEnabled = true; context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; + context1.ChangeTracker.DeleteOrphansTiming= CascadeTiming.Immediate; context1.Database.AutoTransactionsEnabled = true; serviceScope.Dispose(); @@ -305,6 +310,8 @@ public void Context_configuration_is_reset(bool useInterface) Assert.False(context2.ChangeTracker.AutoDetectChangesEnabled); Assert.False(context2.ChangeTracker.LazyLoadingEnabled); Assert.Equal(QueryTrackingBehavior.TrackAll, context2.ChangeTracker.QueryTrackingBehavior); + Assert.Equal(CascadeTiming.Never, context2.ChangeTracker.CascadeDeleteTiming); + Assert.Equal(CascadeTiming.Never, context2.ChangeTracker.DeleteOrphansTiming); Assert.False(context2.Database.AutoTransactionsEnabled); } @@ -322,6 +329,8 @@ public void Default_Context_configuration__is_reset() context1.ChangeTracker.LazyLoadingEnabled = false; context1.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; context1.Database.AutoTransactionsEnabled = false; + context1.ChangeTracker.CascadeDeleteTiming = CascadeTiming.Immediate; + context1.ChangeTracker.DeleteOrphansTiming= CascadeTiming.Immediate; serviceScope.Dispose(); @@ -335,6 +344,8 @@ public void Default_Context_configuration__is_reset() Assert.True(context2.ChangeTracker.AutoDetectChangesEnabled); Assert.True(context2.ChangeTracker.LazyLoadingEnabled); Assert.Equal(QueryTrackingBehavior.TrackAll, context2.ChangeTracker.QueryTrackingBehavior); + Assert.Equal(CascadeTiming.Immediate, context2.ChangeTracker.CascadeDeleteTiming); + Assert.Equal(CascadeTiming.Immediate, context2.ChangeTracker.DeleteOrphansTiming); Assert.True(context2.Database.AutoTransactionsEnabled); } diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTest.cs index 0d93a03126d..6328401b11d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTest.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.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Storage; @@ -56,9 +57,10 @@ public Restrict(GraphUpdatesWithRestrictSqlServerFixture fixture) { } - public override DbUpdateException Optional_One_to_one_relationships_are_one_to_one() + public override DbUpdateException Optional_One_to_one_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_One_to_one_relationships_are_one_to_one(); + var updateException = base.Optional_One_to_one_relationships_are_one_to_one(deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("IX_OptionalSingle1_RootId", updateException.InnerException.Message); @@ -66,9 +68,10 @@ public override DbUpdateException Optional_One_to_one_relationships_are_one_to_o return updateException; } - public override DbUpdateException Required_One_to_one_relationships_are_one_to_one() + public override DbUpdateException Required_One_to_one_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_One_to_one_relationships_are_one_to_one(); + var updateException = base.Required_One_to_one_relationships_are_one_to_one(deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("PK_RequiredSingle1", updateException.InnerException.Message); @@ -76,9 +79,10 @@ public override DbUpdateException Required_One_to_one_relationships_are_one_to_o return updateException; } - public override DbUpdateException Optional_One_to_one_with_AK_relationships_are_one_to_one() + public override DbUpdateException Optional_One_to_one_with_AK_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_One_to_one_with_AK_relationships_are_one_to_one(); + var updateException = base.Optional_One_to_one_with_AK_relationships_are_one_to_one(deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("IX_OptionalSingleAk1_RootId", updateException.InnerException.Message); @@ -86,9 +90,10 @@ public override DbUpdateException Optional_One_to_one_with_AK_relationships_are_ return updateException; } - public override DbUpdateException Required_One_to_one_with_AK_relationships_are_one_to_one() + public override DbUpdateException Required_One_to_one_with_AK_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_One_to_one_with_AK_relationships_are_one_to_one(); + var updateException = base.Required_One_to_one_with_AK_relationships_are_one_to_one(deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("IX_RequiredSingleAk1_RootId", updateException.InnerException.Message); @@ -96,9 +101,11 @@ public override DbUpdateException Required_One_to_one_with_AK_relationships_are_ return updateException; } - public override DbUpdateException Save_required_one_to_one_changed_by_reference(ChangeMechanism changeMechanism) + public override DbUpdateException Save_required_one_to_one_changed_by_reference( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Save_required_one_to_one_changed_by_reference(changeMechanism); + var updateException = base.Save_required_one_to_one_changed_by_reference(changeMechanism, deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingle2_RequiredSingle1_Id", updateException.InnerException.Message); @@ -106,9 +113,11 @@ public override DbUpdateException Save_required_one_to_one_changed_by_reference( return updateException; } - public override DbUpdateException Sever_required_one_to_one(ChangeMechanism changeMechanism) + public override DbUpdateException Sever_required_one_to_one( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Sever_required_one_to_one(changeMechanism); + var updateException = base.Sever_required_one_to_one(changeMechanism, deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingle2_RequiredSingle1_Id", updateException.InnerException.Message); @@ -116,9 +125,13 @@ public override DbUpdateException Sever_required_one_to_one(ChangeMechanism chan return updateException; } - public override DbUpdateException Required_many_to_one_dependents_are_cascade_deleted() + public override DbUpdateException Required_many_to_one_dependents_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_many_to_one_dependents_are_cascade_deleted(); + var updateException = base.Required_many_to_one_dependents_are_cascade_deleted( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_Required2_Required1_ParentId", updateException.InnerException.Message); @@ -126,9 +139,13 @@ public override DbUpdateException Required_many_to_one_dependents_are_cascade_de return updateException; } - public override DbUpdateException Optional_many_to_one_dependents_are_orphaned() + public override DbUpdateException Optional_many_to_one_dependents_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_many_to_one_dependents_are_orphaned(); + var updateException = base.Optional_many_to_one_dependents_are_orphaned( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_Optional2_Optional1_ParentId", updateException.InnerException.Message); @@ -136,9 +153,13 @@ public override DbUpdateException Optional_many_to_one_dependents_are_orphaned() return updateException; } - public override DbUpdateException Optional_one_to_one_are_orphaned() + public override DbUpdateException Optional_one_to_one_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_one_to_one_are_orphaned(); + var updateException = base.Optional_one_to_one_are_orphaned( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalSingle2_OptionalSingle1_BackId", updateException.InnerException.Message); @@ -146,9 +167,13 @@ public override DbUpdateException Optional_one_to_one_are_orphaned() return updateException; } - public override DbUpdateException Required_one_to_one_are_cascade_deleted() + public override DbUpdateException Required_one_to_one_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_one_to_one_are_cascade_deleted(); + var updateException = base.Required_one_to_one_are_cascade_deleted( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingle2_RequiredSingle1_Id", updateException.InnerException.Message); @@ -156,9 +181,13 @@ public override DbUpdateException Required_one_to_one_are_cascade_deleted() return updateException; } - public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted() + public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_non_PK_one_to_one_are_cascade_deleted(); + var updateException = base.Required_non_PK_one_to_one_are_cascade_deleted( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredNonPkSingle2_RequiredNonPkSingle1_BackId", updateException.InnerException.Message); @@ -166,9 +195,13 @@ public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted return updateException; } - public override DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned() + public override DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_many_to_one_dependents_with_alternate_key_are_orphaned(); + var updateException = base.Optional_many_to_one_dependents_with_alternate_key_are_orphaned( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalAk2_OptionalAk1_ParentId", updateException.InnerException.Message); @@ -176,9 +209,13 @@ public override DbUpdateException Optional_many_to_one_dependents_with_alternate return updateException; } - public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted() + public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted(); + var updateException = base.Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredAk2_RequiredAk1_ParentId", updateException.InnerException.Message); @@ -186,9 +223,13 @@ public override DbUpdateException Required_many_to_one_dependents_with_alternate return updateException; } - public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned() + public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_one_to_one_with_alternate_key_are_orphaned(); + var updateException = base.Optional_one_to_one_with_alternate_key_are_orphaned( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalSingleAk2_OptionalSingleAk1_BackId", updateException.InnerException.Message); @@ -196,9 +237,13 @@ public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orp return updateException; } - public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted() + public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_one_to_one_with_alternate_key_are_cascade_deleted(); + var updateException = base.Required_one_to_one_with_alternate_key_are_cascade_deleted( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingleAk2_RequiredSingleAk1_BackId", updateException.InnerException.Message); @@ -206,9 +251,13 @@ public override DbUpdateException Required_one_to_one_with_alternate_key_are_cas return updateException; } - public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted() + public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted(); + var updateException = base.Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredNonPkSingleAk2_RequiredNonPkSingleAk1_BackId", updateException.InnerException.Message); @@ -216,9 +265,13 @@ public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_ return updateException; } - public override DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_in_store() + public override DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_many_to_one_dependents_are_cascade_deleted_in_store(); + var updateException = base.Required_many_to_one_dependents_are_cascade_deleted_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_Required2_Required1_ParentId", updateException.InnerException.Message); @@ -226,9 +279,13 @@ public override DbUpdateException Required_many_to_one_dependents_are_cascade_de return updateException; } - public override DbUpdateException Required_one_to_one_are_cascade_deleted_in_store() + public override DbUpdateException Required_one_to_one_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_one_to_one_are_cascade_deleted_in_store(); + var updateException = base.Required_one_to_one_are_cascade_deleted_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingle2_RequiredSingle1_Id", updateException.InnerException.Message); @@ -236,9 +293,13 @@ public override DbUpdateException Required_one_to_one_are_cascade_deleted_in_sto return updateException; } - public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_in_store() + public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_non_PK_one_to_one_are_cascade_deleted_in_store(); + var updateException = base.Required_non_PK_one_to_one_are_cascade_deleted_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredNonPkSingle2_RequiredNonPkSingle1_BackId", updateException.InnerException.Message); @@ -246,9 +307,13 @@ public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted return updateException; } - public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store() + public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store(); + var updateException = base.Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredAk2_RequiredAk1_ParentId", updateException.InnerException.Message); @@ -256,9 +321,13 @@ public override DbUpdateException Required_many_to_one_dependents_with_alternate return updateException; } - public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store(); + var updateException = base.Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingleAk2_RequiredSingleAk1_BackId", updateException.InnerException.Message); @@ -266,9 +335,13 @@ public override DbUpdateException Required_one_to_one_with_alternate_key_are_cas return updateException; } - public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store() + public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store(); + var updateException = base.Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredNonPkSingleAk2_RequiredNonPkSingleAk1_BackId", updateException.InnerException.Message); @@ -276,9 +349,13 @@ public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_ return updateException; } - public override DbUpdateException Optional_many_to_one_dependents_are_orphaned_in_store() + public override DbUpdateException Optional_many_to_one_dependents_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_many_to_one_dependents_are_orphaned_in_store(); + var updateException = base.Optional_many_to_one_dependents_are_orphaned_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_Optional2_Optional1_ParentId", updateException.InnerException.Message); @@ -286,9 +363,13 @@ public override DbUpdateException Optional_many_to_one_dependents_are_orphaned_i return updateException; } - public override DbUpdateException Optional_one_to_one_are_orphaned_in_store() + public override DbUpdateException Optional_one_to_one_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_one_to_one_are_orphaned_in_store(); + var updateException = base.Optional_one_to_one_are_orphaned_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalSingle2_OptionalSingle1_BackId", updateException.InnerException.Message); @@ -296,9 +377,13 @@ public override DbUpdateException Optional_one_to_one_are_orphaned_in_store() return updateException; } - public override DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store() + public override DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store(); + var updateException = base.Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalAk2_OptionalAk1_ParentId", updateException.InnerException.Message); @@ -306,9 +391,13 @@ public override DbUpdateException Optional_many_to_one_dependents_with_alternate return updateException; } - public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_in_store() + public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_one_to_one_with_alternate_key_are_orphaned_in_store(); + var updateException = base.Optional_one_to_one_with_alternate_key_are_orphaned_in_store( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalSingleAk2_OptionalSingleAk1_BackId", updateException.InnerException.Message); @@ -316,9 +405,13 @@ public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orp return updateException; } - public override DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_starting_detached() + public override DbUpdateException Required_many_to_one_dependents_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_many_to_one_dependents_are_cascade_deleted_starting_detached(); + var updateException = base.Required_many_to_one_dependents_are_cascade_deleted_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_Required2_Required1_ParentId", updateException.InnerException.Message); @@ -326,9 +419,13 @@ public override DbUpdateException Required_many_to_one_dependents_are_cascade_de return updateException; } - public override DbUpdateException Optional_many_to_one_dependents_are_orphaned_starting_detached() + public override DbUpdateException Optional_many_to_one_dependents_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_many_to_one_dependents_are_orphaned_starting_detached(); + var updateException = base.Optional_many_to_one_dependents_are_orphaned_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_Optional2_Optional1_ParentId", updateException.InnerException.Message); @@ -336,9 +433,13 @@ public override DbUpdateException Optional_many_to_one_dependents_are_orphaned_s return updateException; } - public override DbUpdateException Optional_one_to_one_are_orphaned_starting_detached() + public override DbUpdateException Optional_one_to_one_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_one_to_one_are_orphaned_starting_detached(); + var updateException = base.Optional_one_to_one_are_orphaned_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalSingle2_OptionalSingle1_BackId", updateException.InnerException.Message); @@ -346,9 +447,13 @@ public override DbUpdateException Optional_one_to_one_are_orphaned_starting_deta return updateException; } - public override DbUpdateException Required_one_to_one_are_cascade_deleted_starting_detached() + public override DbUpdateException Required_one_to_one_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_one_to_one_are_cascade_deleted_starting_detached(); + var updateException = base.Required_one_to_one_are_cascade_deleted_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingle2_RequiredSingle1_Id", updateException.InnerException.Message); @@ -356,9 +461,13 @@ public override DbUpdateException Required_one_to_one_are_cascade_deleted_starti return updateException; } - public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_starting_detached() + public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_non_PK_one_to_one_are_cascade_deleted_starting_detached(); + var updateException = base.Required_non_PK_one_to_one_are_cascade_deleted_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredNonPkSingle2_RequiredNonPkSingle1_BackId", updateException.InnerException.Message); @@ -366,9 +475,13 @@ public override DbUpdateException Required_non_PK_one_to_one_are_cascade_deleted return updateException; } - public override DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_starting_detached() + public override DbUpdateException Optional_many_to_one_dependents_with_alternate_key_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_many_to_one_dependents_with_alternate_key_are_orphaned_starting_detached(); + var updateException = base.Optional_many_to_one_dependents_with_alternate_key_are_orphaned_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalAk2_OptionalAk1_ParentId", updateException.InnerException.Message); @@ -376,9 +489,13 @@ public override DbUpdateException Optional_many_to_one_dependents_with_alternate return updateException; } - public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_starting_detached() + public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_starting_detached(); + var updateException = base.Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredAk2_RequiredAk1_ParentId", updateException.InnerException.Message); @@ -386,9 +503,13 @@ public override DbUpdateException Required_many_to_one_dependents_with_alternate return updateException; } - public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_starting_detached() + public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Optional_one_to_one_with_alternate_key_are_orphaned_starting_detached(); + var updateException = base.Optional_one_to_one_with_alternate_key_are_orphaned_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_OptionalSingleAk2_OptionalSingleAk1_BackId", updateException.InnerException.Message); @@ -396,9 +517,13 @@ public override DbUpdateException Optional_one_to_one_with_alternate_key_are_orp return updateException; } - public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached() + public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached(); + var updateException = base.Required_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingleAk2_RequiredSingleAk1_BackId", updateException.InnerException.Message); @@ -406,9 +531,13 @@ public override DbUpdateException Required_one_to_one_with_alternate_key_are_cas return updateException; } - public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached() + public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached(); + var updateException = base.Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_starting_detached( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredNonPkSingleAk2_RequiredNonPkSingleAk1_BackId", updateException.InnerException.Message); @@ -416,9 +545,13 @@ public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_ return updateException; } - public override DbUpdateException Required_many_to_one_dependents_are_cascade_detached_when_Added() + public override DbUpdateException Required_many_to_one_dependents_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_many_to_one_dependents_are_cascade_detached_when_Added(); + var updateException = base.Required_many_to_one_dependents_are_cascade_detached_when_Added( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_Required2_Required1_ParentId", updateException.InnerException.Message); @@ -426,9 +559,13 @@ public override DbUpdateException Required_many_to_one_dependents_are_cascade_de return updateException; } - public override DbUpdateException Required_one_to_one_are_cascade_detached_when_Added() + public override DbUpdateException Required_one_to_one_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_one_to_one_are_cascade_detached_when_Added(); + var updateException = base.Required_one_to_one_are_cascade_detached_when_Added( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingle2_RequiredSingle1_Id", updateException.InnerException.Message); @@ -436,9 +573,13 @@ public override DbUpdateException Required_one_to_one_are_cascade_detached_when_ return updateException; } - public override DbUpdateException Required_non_PK_one_to_one_are_cascade_detached_when_Added() + public override DbUpdateException Required_non_PK_one_to_one_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_non_PK_one_to_one_are_cascade_detached_when_Added(); + var updateException = base.Required_non_PK_one_to_one_are_cascade_detached_when_Added( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredNonPkSingle2_RequiredNonPkSingle1_BackId", updateException.InnerException.Message); @@ -446,9 +587,13 @@ public override DbUpdateException Required_non_PK_one_to_one_are_cascade_detache return updateException; } - public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_detached_when_Added() + public override DbUpdateException Required_many_to_one_dependents_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_many_to_one_dependents_with_alternate_key_are_cascade_detached_when_Added(); + var updateException = base.Required_many_to_one_dependents_with_alternate_key_are_cascade_detached_when_Added( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredAk2_RequiredAk1_ParentId", updateException.InnerException.Message); @@ -456,9 +601,13 @@ public override DbUpdateException Required_many_to_one_dependents_with_alternate return updateException; } - public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + public override DbUpdateException Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added(); + var updateException = base.Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredSingleAk2_RequiredSingleAk1_BackId", updateException.InnerException.Message); @@ -466,9 +615,13 @@ public override DbUpdateException Required_one_to_one_with_alternate_key_are_cas return updateException; } - public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added() + public override DbUpdateException Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { - var updateException = base.Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added(); + var updateException = base.Required_non_PK_one_to_one_with_alternate_key_are_cascade_detached_when_Added( + cascadeDeleteTiming, + deleteOrphansTiming); // Disabled check -- see issue #11031 //Assert.Contains("FK_RequiredNonPkSingleAk2_RequiredNonPkSingleAk1_BackId", updateException.InnerException.Message); diff --git a/test/EFCore.SqlServer.FunctionalTests/LazyLoadProxySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/LazyLoadProxySqlServerTest.cs index d18d2e89117..1330608fcb9 100644 --- a/test/EFCore.SqlServer.FunctionalTests/LazyLoadProxySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/LazyLoadProxySqlServerTest.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.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -170,16 +171,16 @@ public override void Lazy_load_one_to_one_reference_to_dependent_not_found(Entit ignoreLineEndingDifferences: true); } - public override void Lazy_load_collection_already_loaded(EntityState state) + public override void Lazy_load_collection_already_loaded(EntityState state, CascadeTiming cascadeDeleteTiming) { - base.Lazy_load_collection_already_loaded(state); + base.Lazy_load_collection_already_loaded(state, cascadeDeleteTiming); Assert.Equal("", Sql); } - public override void Lazy_load_many_to_one_reference_to_principal_already_loaded(EntityState state) + public override void Lazy_load_many_to_one_reference_to_principal_already_loaded(EntityState state, CascadeTiming cascadeDeleteTiming) { - base.Lazy_load_many_to_one_reference_to_principal_already_loaded(state); + base.Lazy_load_many_to_one_reference_to_principal_already_loaded(state, cascadeDeleteTiming); Assert.Equal("", Sql); } @@ -191,9 +192,9 @@ public override void Lazy_load_one_to_one_reference_to_principal_already_loaded( Assert.Equal("", Sql); } - public override void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state) + public override void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state, CascadeTiming cascadeDeleteTiming) { - base.Lazy_load_one_to_one_reference_to_dependent_already_loaded(state); + base.Lazy_load_one_to_one_reference_to_dependent_already_loaded(state, cascadeDeleteTiming); Assert.Equal("", Sql); } diff --git a/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs index 1f8d1fb6e3c..1d11b8d6830 100644 --- a/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/LoadSqlServerTest.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.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -170,9 +171,9 @@ public override void Lazy_load_one_to_one_reference_to_dependent_not_found(Entit ignoreLineEndingDifferences: true); } - public override void Lazy_load_collection_already_loaded(EntityState state) + public override void Lazy_load_collection_already_loaded(EntityState state, CascadeTiming cascadeDeleteTiming) { - base.Lazy_load_collection_already_loaded(state); + base.Lazy_load_collection_already_loaded(state, cascadeDeleteTiming); Assert.Equal("", Sql); } @@ -191,9 +192,9 @@ public override void Lazy_load_one_to_one_reference_to_principal_already_loaded( Assert.Equal("", Sql); } - public override void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state) + public override void Lazy_load_one_to_one_reference_to_dependent_already_loaded(EntityState state, CascadeTiming cascadeDeleteTiming) { - base.Lazy_load_one_to_one_reference_to_dependent_already_loaded(state); + base.Lazy_load_one_to_one_reference_to_dependent_already_loaded(state, cascadeDeleteTiming); Assert.Equal("", Sql); } @@ -802,9 +803,9 @@ public override async Task Load_one_to_one_reference_to_dependent_using_Query_no } } - public override async Task Load_collection_already_loaded(EntityState state, bool async) + public override async Task Load_collection_already_loaded(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_collection_already_loaded(state, async); + await base.Load_collection_already_loaded(state, async, cascadeDeleteTiming); if (!async) { @@ -822,9 +823,9 @@ public override async Task Load_many_to_one_reference_to_principal_already_loade } } - public override async Task Load_one_to_one_reference_to_principal_already_loaded(EntityState state, bool async) + public override async Task Load_one_to_one_reference_to_principal_already_loaded(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_one_to_one_reference_to_principal_already_loaded(state, async); + await base.Load_one_to_one_reference_to_principal_already_loaded(state, async, cascadeDeleteTiming); if (!async) { @@ -832,9 +833,9 @@ public override async Task Load_one_to_one_reference_to_principal_already_loaded } } - public override async Task Load_one_to_one_reference_to_dependent_already_loaded(EntityState state, bool async) + public override async Task Load_one_to_one_reference_to_dependent_already_loaded(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_one_to_one_reference_to_dependent_already_loaded(state, async); + await base.Load_one_to_one_reference_to_dependent_already_loaded(state, async, cascadeDeleteTiming); if (!async) { @@ -862,9 +863,9 @@ public override async Task Load_one_to_one_PK_to_PK_reference_to_dependent_alrea } } - public override async Task Load_collection_using_Query_already_loaded(EntityState state, bool async) + public override async Task Load_collection_using_Query_already_loaded(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_collection_using_Query_already_loaded(state, async); + await base.Load_collection_using_Query_already_loaded(state, async, cascadeDeleteTiming); if (!async) { @@ -913,9 +914,9 @@ public override async Task Load_one_to_one_reference_to_principal_using_Query_al } } - public override async Task Load_one_to_one_reference_to_dependent_using_Query_already_loaded(EntityState state, bool async) + public override async Task Load_one_to_one_reference_to_dependent_using_Query_already_loaded(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_one_to_one_reference_to_dependent_using_Query_already_loaded(state, async); + await base.Load_one_to_one_reference_to_dependent_using_Query_already_loaded(state, async, cascadeDeleteTiming); if (!async) { @@ -1236,9 +1237,9 @@ public override async Task Load_one_to_one_reference_to_dependent_using_Query_no } } - public override async Task Load_collection_already_loaded_untyped(EntityState state, bool async) + public override async Task Load_collection_already_loaded_untyped(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_collection_already_loaded_untyped(state, async); + await base.Load_collection_already_loaded_untyped(state, async, cascadeDeleteTiming); if (!async) { @@ -1266,9 +1267,9 @@ public override async Task Load_one_to_one_reference_to_principal_already_loaded } } - public override async Task Load_one_to_one_reference_to_dependent_already_loaded_untyped(EntityState state, bool async) + public override async Task Load_one_to_one_reference_to_dependent_already_loaded_untyped(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_one_to_one_reference_to_dependent_already_loaded_untyped(state, async); + await base.Load_one_to_one_reference_to_dependent_already_loaded_untyped(state, async, cascadeDeleteTiming); if (!async) { @@ -1276,9 +1277,9 @@ public override async Task Load_one_to_one_reference_to_dependent_already_loaded } } - public override async Task Load_collection_using_Query_already_loaded_untyped(EntityState state, bool async) + public override async Task Load_collection_using_Query_already_loaded_untyped(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_collection_using_Query_already_loaded_untyped(state, async); + await base.Load_collection_using_Query_already_loaded_untyped(state, async, cascadeDeleteTiming); if (!async) { @@ -1327,9 +1328,9 @@ public override async Task Load_one_to_one_reference_to_principal_using_Query_al } } - public override async Task Load_one_to_one_reference_to_dependent_using_Query_already_loaded_untyped(EntityState state, bool async) + public override async Task Load_one_to_one_reference_to_dependent_using_Query_already_loaded_untyped(EntityState state, bool async, CascadeTiming cascadeDeleteTiming) { - await base.Load_one_to_one_reference_to_dependent_using_Query_already_loaded_untyped(state, async); + await base.Load_one_to_one_reference_to_dependent_using_Query_already_loaded_untyped(state, async, cascadeDeleteTiming); if (!async) { diff --git a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs index 1c805828eda..aed76d01809 100644 --- a/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/ChangeTrackerTest.cs @@ -353,58 +353,243 @@ public void Reset(bool generateTemporaryValues) } [Theory] - [InlineData(false)] - [InlineData(true)] - public void Cascade_delete_is_logged(bool sensitive) + [InlineData(false, CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(false, CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(false, CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(false, CascadeTiming.OnSaveChanges, null)] + [InlineData(false, CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(false, CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(false, CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(false, CascadeTiming.Immediate, null)] + [InlineData(false, CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(false, CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(false, CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(false, CascadeTiming.Never, null)] + [InlineData(false, null, CascadeTiming.OnSaveChanges)] + [InlineData(false, null, CascadeTiming.Immediate)] + [InlineData(false, null, CascadeTiming.Never)] + [InlineData(false, null, null)] + [InlineData(true, CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(true, CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(true, CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(true, CascadeTiming.OnSaveChanges, null)] + [InlineData(true, CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(true, CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(true, CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(true, CascadeTiming.Immediate, null)] + [InlineData(true, CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(true, CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(true, CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(true, CascadeTiming.Never, null)] + [InlineData(true, null, CascadeTiming.OnSaveChanges)] + [InlineData(true, null, CascadeTiming.Immediate)] + [InlineData(true, null, CascadeTiming.Never)] + [InlineData(true, null, null)] + public void Cascade_delete_is_logged( + bool sensitive, + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { Seed(sensitive); using (var context = sensitive ? new LikeAZooContextSensitive() : new LikeAZooContext()) { + if (cascadeDeleteTiming.HasValue) + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming.Value; + } + + if (deleteOrphansTiming.HasValue) + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming.Value; + } + var cat = context.Cats.Include(e => e.Hats).Single(e => e.Id == 1); - context.Entry(cat).State = EntityState.Deleted; + LogLevel? cascadeDeleteLevel = null; + string cascadeDeleteMessage = null; + string deleteOrphansMessage = null; - _loggerFactory.Log.Clear(); + void CaptureMessages() + { + (cascadeDeleteLevel, _, cascadeDeleteMessage, _, _) = _loggerFactory.Log.FirstOrDefault(e => e.Id.Id == CoreEventId.CascadeDelete.Id); + (_, _, deleteOrphansMessage, _, _) = _loggerFactory.Log.FirstOrDefault(e => e.Id.Id == CoreEventId.CascadeDeleteOrphan.Id); + } - context.SaveChanges(); + void ClearMessages() + { + _loggerFactory.Log.Clear(); + } - var (level, _, message, _, _) = _loggerFactory.Log.Single(e => e.Id.Id == CoreEventId.CascadeDelete.Id); - Assert.Equal(LogLevel.Debug, level); - Assert.Equal( - sensitive - ? CoreStrings.LogCascadeDeleteSensitive.GenerateMessage( - nameof(Hat), "{Id: 77}", EntityState.Deleted, nameof(Cat), "{Id: 1}") - : CoreStrings.LogCascadeDelete.GenerateMessage(nameof(Hat), EntityState.Deleted, nameof(Cat)), - message); + switch (cascadeDeleteTiming) + { + case CascadeTiming.Immediate: + case null: + ClearMessages(); + + context.Entry(cat).State = EntityState.Deleted; + + CaptureMessages(); + + context.SaveChanges(); + break; + case CascadeTiming.OnSaveChanges: + context.Entry(cat).State = EntityState.Deleted; + + ClearMessages(); + + context.SaveChanges(); + + CaptureMessages(); + break; + case CascadeTiming.Never: + ClearMessages(); + + context.Entry(cat).State = EntityState.Deleted; + + Assert.Throws(() => context.SaveChanges()); + + CaptureMessages(); + break; + } + + Assert.Null(deleteOrphansMessage); + + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Null(cascadeDeleteMessage); + } + else + { + Assert.Equal(LogLevel.Debug, cascadeDeleteLevel); + Assert.Equal( + sensitive + ? CoreStrings.LogCascadeDeleteSensitive.GenerateMessage(nameof(Hat), "{Id: 77}", EntityState.Deleted, nameof(Cat), "{Id: 1}") + : CoreStrings.LogCascadeDelete.GenerateMessage(nameof(Hat), EntityState.Deleted, nameof(Cat)), + cascadeDeleteMessage); + } } } [Theory] - [InlineData(false)] - [InlineData(true)] - public void Cascade_delete_orphan_is_logged(bool sensitive) + [InlineData(false, CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(false, CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(false, CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(false, CascadeTiming.OnSaveChanges, null)] + [InlineData(false, CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(false, CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(false, CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(false, CascadeTiming.Immediate, null)] + [InlineData(false, CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(false, CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(false, CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(false, CascadeTiming.Never, null)] + [InlineData(false, null, CascadeTiming.OnSaveChanges)] + [InlineData(false, null, CascadeTiming.Immediate)] + [InlineData(false, null, CascadeTiming.Never)] + [InlineData(false, null, null)] + [InlineData(true, CascadeTiming.OnSaveChanges, CascadeTiming.OnSaveChanges)] + [InlineData(true, CascadeTiming.OnSaveChanges, CascadeTiming.Immediate)] + [InlineData(true, CascadeTiming.OnSaveChanges, CascadeTiming.Never)] + [InlineData(true, CascadeTiming.OnSaveChanges, null)] + [InlineData(true, CascadeTiming.Immediate, CascadeTiming.OnSaveChanges)] + [InlineData(true, CascadeTiming.Immediate, CascadeTiming.Immediate)] + [InlineData(true, CascadeTiming.Immediate, CascadeTiming.Never)] + [InlineData(true, CascadeTiming.Immediate, null)] + [InlineData(true, CascadeTiming.Never, CascadeTiming.OnSaveChanges)] + [InlineData(true, CascadeTiming.Never, CascadeTiming.Immediate)] + [InlineData(true, CascadeTiming.Never, CascadeTiming.Never)] + [InlineData(true, CascadeTiming.Never, null)] + [InlineData(true, null, CascadeTiming.OnSaveChanges)] + [InlineData(true, null, CascadeTiming.Immediate)] + [InlineData(true, null, CascadeTiming.Never)] + [InlineData(true, null, null)] + public void Cascade_delete_orphan_is_logged( + bool sensitive, + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) { Seed(sensitive); using (var context = sensitive ? new LikeAZooContextSensitive() : new LikeAZooContext()) { + if (cascadeDeleteTiming.HasValue) + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming.Value; + } + + if (deleteOrphansTiming.HasValue) + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming.Value; + } + var cat = context.Cats.Include(e => e.Hats).Single(e => e.Id == 1); - cat.Hats.Clear(); + LogLevel? deleteOrphansLevel = null; + string cascadeDeleteMessage = null; + string deleteOrphansMessage = null; - _loggerFactory.Log.Clear(); + void CaptureMessages() + { + (_, _, cascadeDeleteMessage, _, _) = _loggerFactory.Log.FirstOrDefault(e => e.Id.Id == CoreEventId.CascadeDelete.Id); + (deleteOrphansLevel, _, deleteOrphansMessage, _, _) = _loggerFactory.Log.FirstOrDefault(e => e.Id.Id == CoreEventId.CascadeDeleteOrphan.Id); + } - context.SaveChanges(); + void ClearMessages() + { + _loggerFactory.Log.Clear(); + } - var (level, _, message, _, _) = _loggerFactory.Log.Single(e => e.Id.Id == CoreEventId.CascadeDeleteOrphan.Id); - Assert.Equal(LogLevel.Debug, level); - Assert.Equal( - sensitive - ? CoreStrings.LogCascadeDeleteOrphanSensitive.GenerateMessage( - nameof(Hat), "{Id: 77}", EntityState.Deleted, nameof(Cat)) - : CoreStrings.LogCascadeDeleteOrphan.GenerateMessage(nameof(Hat), EntityState.Deleted, nameof(Cat)), - message); + switch (deleteOrphansTiming) + { + case CascadeTiming.Immediate: + case null: + ClearMessages(); + + cat.Hats.Clear(); + context.ChangeTracker.DetectChanges(); + + CaptureMessages(); + + context.SaveChanges(); + break; + case CascadeTiming.OnSaveChanges: + cat.Hats.Clear(); + context.ChangeTracker.DetectChanges(); + + ClearMessages(); + + context.SaveChanges(); + + CaptureMessages(); + break; + case CascadeTiming.Never: + ClearMessages(); + + cat.Hats.Clear(); + context.ChangeTracker.DetectChanges(); + + Assert.Throws(() => context.SaveChanges()); + + CaptureMessages(); + break; + } + + Assert.Null(cascadeDeleteMessage); + + if (deleteOrphansTiming == CascadeTiming.Never) + { + Assert.Null(deleteOrphansMessage); + } + else + { + Assert.Equal(LogLevel.Debug, deleteOrphansLevel); + Assert.Equal( + sensitive + ? CoreStrings.LogCascadeDeleteOrphanSensitive.GenerateMessage(nameof(Hat), "{Id: 77}", EntityState.Deleted, nameof(Cat)) + : CoreStrings.LogCascadeDeleteOrphan.GenerateMessage(nameof(Hat), EntityState.Deleted, nameof(Cat)), + deleteOrphansMessage); + } } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs index cb52318f739..28972dd203b 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/FixupTest.cs @@ -2127,6 +2127,8 @@ public void Replace_dependent_one_to_one_FK_not_set_dependent_nav_set(EntityStat { using (var context = new FixupContext()) { + context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + var principal = new Parent(77); var oldDependent = new Child(78, principal.Id); oldDependent.SetParent(principal); @@ -2218,6 +2220,8 @@ public void Replace_dependent_one_to_one_prin_uni_FK_set_principal_nav_set(Entit { using (var context = new FixupContext()) { + context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + var principal = new ParentPN { Id = 77 @@ -2312,6 +2316,8 @@ public void Replace_dependent_one_to_one_dep_uni_FK_set_dependent_nav_set(Entity { using (var context = new FixupContext()) { + context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + var principal = new ParentDN { Id = 77 @@ -2369,6 +2375,8 @@ public void Replace_dependent_one_to_one_no_navs_FK_set(EntityState oldEntitySta { using (var context = new FixupContext()) { + context.ChangeTracker.DeleteOrphansTiming = CascadeTiming.OnSaveChanges; + var principal = new ParentNN { Id = 77 diff --git a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs index 2cf90f00af0..b2dbc466e2a 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/InternalEntityEntryTestBase.cs @@ -1250,7 +1250,7 @@ public void Unchanged_entity_with_conceptually_null_FK_with_cascade_delete_is_ma entry.SetEntityState(EntityState.Unchanged); entry[fkProperty] = null; - entry.HandleConceptualNulls(false); + entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); Assert.Equal(EntityState.Deleted, entry.EntityState); } @@ -1268,7 +1268,7 @@ public void Added_entity_with_conceptually_null_FK_with_cascade_delete_is_detach entry.SetEntityState(EntityState.Added); entry[fkProperty] = null; - entry.HandleConceptualNulls(false); + entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); Assert.Equal(EntityState.Detached, entry.EntityState); } @@ -1291,7 +1291,7 @@ public void Entity_with_partially_null_composite_FK_with_cascade_delete_is_marke entry.SetEntityState(EntityState.Unchanged); entry[fkProperty1] = null; - entry.HandleConceptualNulls(false); + entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); Assert.Equal(EntityState.Deleted, entry.EntityState); } @@ -1314,7 +1314,7 @@ public void Entity_with_partially_null_composite_FK_without_cascade_delete_is_or entry.SetEntityState(EntityState.Unchanged); entry[fkProperty1] = null; - entry.HandleConceptualNulls(false); + entry.HandleConceptualNulls(false, force: false, isCascadeDelete: false); Assert.Equal(EntityState.Modified, entry.EntityState); @@ -1322,81 +1322,6 @@ public void Entity_with_partially_null_composite_FK_without_cascade_delete_is_or Assert.Null(entry[fkProperty2]); } - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Unchanged_entity_with_conceptually_null_FK_without_cascade_delete_throws(bool sensitiveLoggingEnabled) - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeDependentEntity).FullName); - var keyProperties = new[] { entityType.FindProperty("Id1"), entityType.FindProperty("Id2") }; - var fkProperty = entityType.FindProperty("SomeEntityId"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeDependentEntity()); - entry.SetOriginalValue(keyProperties[0], 77); - entry.SetOriginalValue(keyProperties[1], "ReadySalted"); - entry[keyProperties[0]] = 77; - entry[keyProperties[1]] = "ReadySalted"; - entry[fkProperty] = 99; - - entry.SetEntityState(EntityState.Unchanged); - entry[fkProperty] = null; - - var exception = Assert.Throws(() => entry.HandleConceptualNulls(sensitiveLoggingEnabled)).Message; - if (sensitiveLoggingEnabled) - { - Assert.Equal( - CoreStrings.RelationshipConceptualNullSensitive( - model.FindEntityType(typeof(SomeEntity).FullName).DisplayName(), - entityType.DisplayName(), - "{Id1: 77, Id2: ReadySalted}"), exception); - } - else - { - Assert.Equal( - CoreStrings.RelationshipConceptualNull( - model.FindEntityType(typeof(SomeEntity).FullName).DisplayName(), - entityType.DisplayName()), exception); - } - } - - [Theory] - [InlineData(true)] - [InlineData(false)] - public void Unchanged_entity_with_conceptually_null_non_FK_property_throws(bool sensitiveLoggingEnabled) - { - var model = BuildModel(); - var entityType = model.FindEntityType(typeof(SomeDependentEntity).FullName); - var keyProperties = new[] { entityType.FindProperty("Id1"), entityType.FindProperty("Id2") }; - var property = entityType.FindProperty("JustAProperty"); - var configuration = InMemoryTestHelpers.Instance.CreateContextServices(model); - - var entry = CreateInternalEntry(configuration, entityType, new SomeDependentEntity()); - entry.SetOriginalValue(keyProperties[0], 77); - entry.SetOriginalValue(keyProperties[1], "ReadySalted"); - entry[keyProperties[0]] = 77; - entry[keyProperties[1]] = "ReadySalted"; - entry[property] = 99; - - entry.SetEntityState(EntityState.Unchanged); - entry[property] = null; - - var exception = Assert.Throws(() => entry.HandleConceptualNulls(sensitiveLoggingEnabled)).Message; - if (sensitiveLoggingEnabled) - { - Assert.Equal( - CoreStrings.PropertyConceptualNullSensitive("JustAProperty", entityType.DisplayName(), "{Id1: 77, Id2: ReadySalted}"), - exception); - } - else - { - Assert.Equal( - CoreStrings.PropertyConceptualNull("JustAProperty", entityType.DisplayName()), - exception); - } - } - // ReSharper disable once ClassNeverInstantiated.Local private class Root { diff --git a/test/EFCore.Tests/ChangeTracking/Internal/NavigationFixerTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/NavigationFixerTest.cs index 3bd5e5d22bf..9d074ae6a94 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/NavigationFixerTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/NavigationFixerTest.cs @@ -1445,11 +1445,11 @@ public void Nulls_one_to_one_navigation_to_principal_after_deletion() principalEntry1.SetEntityState(EntityState.Deleted); - Assert.Same(principal1, dependent1.AlternateProduct); + Assert.Null(dependent1.AlternateProduct); Assert.Same(dependent1, principal1.OriginalProduct); Assert.Same(principal2, dependent2.AlternateProduct); Assert.Same(dependent2, principal2.OriginalProduct); - Assert.Equal(dependent1.AlternateProductId, principal1.Id); + Assert.Null(dependent1.AlternateProductId); Assert.Equal(dependent2.AlternateProductId, principal2.Id); principalEntry1.SetEntityState(EntityState.Detached); @@ -1458,7 +1458,7 @@ public void Nulls_one_to_one_navigation_to_principal_after_deletion() Assert.Same(dependent1, principal1.OriginalProduct); Assert.Same(principal2, dependent2.AlternateProduct); Assert.Same(dependent2, principal2.OriginalProduct); - Assert.Equal(dependent1.AlternateProductId, principal1.Id); + Assert.Null(dependent1.AlternateProductId); Assert.Equal(dependent2.AlternateProductId, principal2.Id); } diff --git a/test/EFCore.Tests/DbContextTrackingTest.cs b/test/EFCore.Tests/DbContextTrackingTest.cs index 82a2e353c60..7b82fd879af 100644 --- a/test/EFCore.Tests/DbContextTrackingTest.cs +++ b/test/EFCore.Tests/DbContextTrackingTest.cs @@ -98,15 +98,13 @@ public Task Can_add_existing_entities_to_context_to_be_updated_with_graph_method Assert.Same(relatedDependent, relatedDependentEntry.Entity); Assert.Same(dependent, dependentEntry.Entity); - var expectedRelatedState = expectedState == EntityState.Deleted ? EntityState.Unchanged : expectedState; - Assert.Same(principal, principalEntry.Entity); Assert.Equal(expectedState, principalEntry.State); Assert.Same(relatedPrincipal, relatedPrincipalEntry.Entity); - Assert.Equal(expectedRelatedState, relatedPrincipalEntry.State); + Assert.Equal(expectedState == EntityState.Deleted ? EntityState.Unchanged : expectedState, relatedPrincipalEntry.State); Assert.Same(relatedDependent, relatedDependentEntry.Entity); - Assert.Equal(expectedRelatedState, relatedDependentEntry.State); + Assert.Equal(expectedState, relatedDependentEntry.State); Assert.Same(dependent, dependentEntry.Entity); Assert.Equal(expectedState, dependentEntry.State); @@ -200,15 +198,13 @@ public Task Can_add_multiple_existing_entities_to_context_to_be_deleted() Assert.Same(relatedDependent, context.Entry(relatedDependent).Entity); Assert.Same(dependent, context.Entry(dependent).Entity); - var expectedRelatedState = expectedState == EntityState.Deleted ? EntityState.Unchanged : expectedState; - Assert.Same(principal, context.Entry(principal).Entity); Assert.Equal(expectedState, context.Entry(principal).State); Assert.Same(relatedPrincipal, context.Entry(relatedPrincipal).Entity); - Assert.Equal(expectedRelatedState, context.Entry(relatedPrincipal).State); + Assert.Equal(expectedState == EntityState.Deleted ? EntityState.Unchanged : expectedState, context.Entry(relatedPrincipal).State); Assert.Same(relatedDependent, context.Entry(relatedDependent).Entity); - Assert.Equal(expectedRelatedState, context.Entry(relatedDependent).State); + Assert.Equal(expectedState, context.Entry(relatedDependent).State); Assert.Same(dependent, context.Entry(dependent).Entity); Assert.Equal(expectedState, context.Entry(dependent).State); } @@ -498,15 +494,13 @@ public Task Can_add_existing_entities_to_context_to_be_updated_non_generic_graph Assert.Same(relatedDependent, relatedDependentEntry.Entity); Assert.Same(dependent, dependentEntry.Entity); - var expectedRelatedState = expectedState == EntityState.Deleted ? EntityState.Unchanged : expectedState; - Assert.Same(principal, principalEntry.Entity); Assert.Equal(expectedState, principalEntry.State); Assert.Same(relatedPrincipal, relatedPrincipalEntry.Entity); - Assert.Equal(expectedRelatedState, relatedPrincipalEntry.State); + Assert.Equal(expectedState == EntityState.Deleted ? EntityState.Unchanged : expectedState, relatedPrincipalEntry.State); Assert.Same(relatedDependent, relatedDependentEntry.Entity); - Assert.Equal(expectedRelatedState, relatedDependentEntry.State); + Assert.Equal(expectedState, relatedDependentEntry.State); Assert.Same(dependent, dependentEntry.Entity); Assert.Equal(expectedState, dependentEntry.State); @@ -600,15 +594,13 @@ public Task Can_add_multiple_existing_entities_to_context_to_be_updated_Enumerab Assert.Same(relatedDependent, context.Entry(relatedDependent).Entity); Assert.Same(dependent, context.Entry(dependent).Entity); - var expectedRelatedState = expectedState == EntityState.Deleted ? EntityState.Unchanged : expectedState; - Assert.Same(principal, context.Entry(principal).Entity); Assert.Equal(expectedState, context.Entry(principal).State); Assert.Same(relatedPrincipal, context.Entry(relatedPrincipal).Entity); - Assert.Equal(expectedRelatedState, context.Entry(relatedPrincipal).State); + Assert.Equal(expectedState == EntityState.Deleted ? EntityState.Unchanged : expectedState, context.Entry(relatedPrincipal).State); Assert.Same(relatedDependent, context.Entry(relatedDependent).Entity); - Assert.Equal(expectedRelatedState, context.Entry(relatedDependent).State); + Assert.Equal(expectedState, context.Entry(relatedDependent).State); Assert.Same(dependent, context.Entry(dependent).Entity); Assert.Equal(expectedState, context.Entry(dependent).State); } diff --git a/test/EFCore.Tests/TestUtilities/FakeStateManager.cs b/test/EFCore.Tests/TestUtilities/FakeStateManager.cs index 604f9cf2013..cf5b1bb0dbd 100644 --- a/test/EFCore.Tests/TestUtilities/FakeStateManager.cs +++ b/test/EFCore.Tests/TestUtilities/FakeStateManager.cs @@ -114,6 +114,8 @@ public Task SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationTo public event EventHandler StateChanged; public void OnStateChanged(InternalEntityEntry internalEntityEntry, EntityState oldState) => StateChanged?.Invoke(null, null); public bool SensitiveLoggingEnabled { get; } + public void CascadeChanges(bool force) => throw new NotImplementedException(); + public void CascadeDelete(InternalEntityEntry entry, bool force) => throw new NotImplementedException(); public IDiagnosticsLogger UpdateLogger { get; } } }