From 5eb50a554f68eec42cc760ad5153f21f554a9870 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sat, 1 Feb 2020 15:18:28 -0800 Subject: [PATCH] Refactor graph update Tests to multiple files To improve both editor responsiveness --- .../GraphUpdatesInMemoryTest.cs | 61 +- .../ProxyGraphUpdatesInMemoryTest.cs | 0 .../EFCore.Specification.Tests.csproj | 4 + .../GraphUpdatesTestBase.cs} | 20 +- .../GraphUpdatesTestBaseMiscellaneous.cs | 759 ++ .../GraphUpdatesTestBaseOneToMany.cs | 1853 ++++ .../GraphUpdatesTestBaseOneToManyAk.cs | 1374 +++ .../GraphUpdatesTestBaseOneToOne.cs | 2428 +++++ .../GraphUpdatesTestBaseOneToOneAk.cs | 2622 +++++ .../ProxyGraphUpdatesFixtureBase.cs | 8 +- .../ProxyGraphUpdatesTestBaseMiscellaneous.cs | 81 + .../ProxyGraphUpdatesTestBaseOneToMany.cs | 1392 +++ .../ProxyGraphUpdatesTestBaseOneToManyAk.cs | 1249 +++ .../ProxyGraphUpdatesTestBaseOneToOne.cs | 1967 ++++ .../ProxyGraphUpdatesTestBaseOneToOneAk.cs | 2298 +++++ .../GraphUpdatesTestBase.cs | 8920 ----------------- .../ProxyGraphUpdatesTestBase.cs | 6922 ------------- .../GraphUpdates/GraphUpdatesSqlServerTest.cs | 138 + .../ProxyGraphUpdatesSqlServerTest.cs | 0 .../GraphUpdatesSqlServerTestBase.cs | 27 - .../GraphUpdatesSqlServerTestClientCascade.cs | 35 - ...GraphUpdatesSqlServerTestClientNoAction.cs | 34 - .../GraphUpdatesSqlServerTestIdentity.cs | 26 - .../GraphUpdatesSqlServerTestSequence.cs | 28 - .../GraphUpdatesSqliteTest.cs | 93 +- .../ProxyGraphUpdatesSqliteTest.cs | 0 26 files changed, 16267 insertions(+), 16072 deletions(-) rename test/EFCore.InMemory.FunctionalTests/{ => GraphUpdates}/GraphUpdatesInMemoryTest.cs (95%) rename test/EFCore.InMemory.FunctionalTests/{ => GraphUpdates}/ProxyGraphUpdatesInMemoryTest.cs (100%) rename test/EFCore.Specification.Tests/{GraphUpdatesFixtureBase.cs => GraphUpdates/GraphUpdatesTestBase.cs} (99%) create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseMiscellaneous.cs create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToMany.cs create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToManyAk.cs create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOne.cs create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOneAk.cs rename test/EFCore.Specification.Tests/{ => GraphUpdates}/ProxyGraphUpdatesFixtureBase.cs (99%) create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseMiscellaneous.cs create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToMany.cs create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToManyAk.cs create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToOne.cs create mode 100644 test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToOneAk.cs delete mode 100644 test/EFCore.Specification.Tests/GraphUpdatesTestBase.cs delete mode 100644 test/EFCore.Specification.Tests/ProxyGraphUpdatesTestBase.cs create mode 100644 test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs rename test/EFCore.SqlServer.FunctionalTests/{ => GraphUpdates}/ProxyGraphUpdatesSqlServerTest.cs (100%) delete mode 100644 test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestBase.cs delete mode 100644 test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestClientCascade.cs delete mode 100644 test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestClientNoAction.cs delete mode 100644 test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestIdentity.cs delete mode 100644 test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestSequence.cs rename test/EFCore.Sqlite.FunctionalTests/{ => GraphUpdates}/GraphUpdatesSqliteTest.cs (67%) rename test/EFCore.Sqlite.FunctionalTests/{ => GraphUpdates}/ProxyGraphUpdatesSqliteTest.cs (100%) diff --git a/test/EFCore.InMemory.FunctionalTests/GraphUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/GraphUpdates/GraphUpdatesInMemoryTest.cs similarity index 95% rename from test/EFCore.InMemory.FunctionalTests/GraphUpdatesInMemoryTest.cs rename to test/EFCore.InMemory.FunctionalTests/GraphUpdates/GraphUpdatesInMemoryTest.cs index 16c78c6fa8a..f8447a670e7 100644 --- a/test/EFCore.InMemory.FunctionalTests/GraphUpdatesInMemoryTest.cs +++ b/test/EFCore.InMemory.FunctionalTests/GraphUpdates/GraphUpdatesInMemoryTest.cs @@ -8,144 +8,143 @@ namespace Microsoft.EntityFrameworkCore { - public class GraphUpdatesInMemoryTest - : GraphUpdatesTestBase + public class GraphUpdatesInMemoryTest : GraphUpdatesTestBase { - public GraphUpdatesInMemoryTest(GraphUpdatesInMemoryFixture fixture) + public GraphUpdatesInMemoryTest(InMemoryFixture fixture) : base(fixture) { } - public override void Optional_One_to_one_relationships_are_one_to_one( + public override void Required_many_to_one_dependents_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_One_to_one_relationships_are_one_to_one( + public override void Optional_many_to_one_dependents_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Optional_One_to_one_with_AK_relationships_are_one_to_one( + public override void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_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( + CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Save_required_one_to_one_changed_by_reference( - ChangeMechanism changeMechanism, + public override void Optional_One_to_one_relationships_are_one_to_one( CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Sever_required_one_to_one( - ChangeMechanism changeMechanism, + public override void Required_One_to_one_relationships_are_one_to_one( CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_many_to_one_dependents_are_cascade_deleted_in_store( - CascadeTiming? cascadeDeleteTiming, + public override void Save_required_one_to_one_changed_by_reference( + ChangeMechanism changeMechanism, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_one_to_one_are_cascade_deleted_in_store( - CascadeTiming? cascadeDeleteTiming, + public override void Sever_required_one_to_one( + ChangeMechanism changeMechanism, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_non_PK_one_to_one_are_cascade_deleted_in_store( + public override void Required_one_to_one_are_cascade_deleted_in_store( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_many_to_one_dependents_with_alternate_key_are_cascade_deleted_in_store( + public override void Required_non_PK_one_to_one_are_cascade_deleted_in_store( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + public override void Optional_one_to_one_are_orphaned_in_store( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_non_PK_one_to_one_with_alternate_key_are_cascade_deleted_in_store( + public override void Required_one_to_one_are_cascade_detached_when_Added( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Optional_many_to_one_dependents_are_orphaned_in_store( + public override void Required_non_PK_one_to_one_are_cascade_detached_when_Added( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Optional_one_to_one_are_orphaned_in_store( - CascadeTiming? cascadeDeleteTiming, + public override void Optional_One_to_one_with_AK_relationships_are_one_to_one( CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Optional_many_to_one_dependents_with_alternate_key_are_orphaned_in_store( - CascadeTiming? cascadeDeleteTiming, + public override void Required_One_to_one_with_AK_relationships_are_one_to_one( CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Optional_one_to_one_with_alternate_key_are_orphaned_in_store( + public override void Required_one_to_one_with_alternate_key_are_cascade_deleted_in_store( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - 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_deleted_in_store( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_one_to_one_are_cascade_detached_when_Added( + public override void Optional_one_to_one_with_alternate_key_are_orphaned_in_store( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { // FK uniqueness not enforced in in-memory database } - public override void Required_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) { // FK uniqueness not enforced in in-memory database } - public override void Required_non_PK_one_to_one_are_cascade_detached_when_Added( + public override void Required_one_to_one_with_alternate_key_are_cascade_detached_when_Added( CascadeTiming? cascadeDeleteTiming, CascadeTiming? deleteOrphansTiming) { @@ -162,8 +161,10 @@ protected override void ExecuteWithStrategyInTransaction( Fixture.Reseed(); } - public class GraphUpdatesInMemoryFixture : GraphUpdatesFixtureBase + public class InMemoryFixture : GraphUpdatesFixtureBase { + protected override string StoreName { get; } = "GraphUpdatesTest"; + protected override ITestStoreFactory TestStoreFactory => InMemoryTestStoreFactory.Instance; public override DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder builder) diff --git a/test/EFCore.InMemory.FunctionalTests/ProxyGraphUpdatesInMemoryTest.cs b/test/EFCore.InMemory.FunctionalTests/GraphUpdates/ProxyGraphUpdatesInMemoryTest.cs similarity index 100% rename from test/EFCore.InMemory.FunctionalTests/ProxyGraphUpdatesInMemoryTest.cs rename to test/EFCore.InMemory.FunctionalTests/GraphUpdates/ProxyGraphUpdatesInMemoryTest.cs diff --git a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj index 6eebd827c4e..7f6694ea961 100644 --- a/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj +++ b/test/EFCore.Specification.Tests/EFCore.Specification.Tests.csproj @@ -26,4 +26,8 @@ + + + + diff --git a/test/EFCore.Specification.Tests/GraphUpdatesFixtureBase.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs similarity index 99% rename from test/EFCore.Specification.Tests/GraphUpdatesFixtureBase.cs rename to test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs index 3ccb8abc4b9..8e707292dbf 100644 --- a/test/EFCore.Specification.Tests/GraphUpdatesFixtureBase.cs +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBase.cs @@ -20,11 +20,15 @@ // ReSharper disable NonReadonlyMemberInGetHashCode namespace Microsoft.EntityFrameworkCore { - public abstract partial class GraphUpdatesTestBase + public abstract partial class GraphUpdatesTestBase : IClassFixture + where TFixture : GraphUpdatesTestBase.GraphUpdatesFixtureBase, new() { + protected GraphUpdatesTestBase(TFixture fixture) => Fixture = fixture; + + protected TFixture Fixture { get; } + public abstract class GraphUpdatesFixtureBase : SharedStoreFixtureBase { - protected override string StoreName { get; } = "GraphUpdatesChangedTest"; public readonly Guid RootAK = Guid.NewGuid(); public virtual bool ForceClientNoAction => false; public virtual bool NoStoreCascades => false; @@ -586,9 +590,9 @@ public virtual EntityState DetermineState(EntityEntry entry) } } - private static void Add(IEnumerable collection, T item) => ((ICollection)collection).Add(item); + protected static void Add(IEnumerable collection, T item) => ((ICollection)collection).Add(item); - private static void Remove(IEnumerable collection, T item) => ((ICollection)collection).Remove(item); + protected static void Remove(IEnumerable collection, T item) => ((ICollection)collection).Remove(item); [Flags] public enum ChangeMechanism @@ -682,7 +686,7 @@ protected Root LoadRequiredCompositeGraph(DbContext context) .Single(IsTheRoot); } - private static void AssertEntries(IReadOnlyList expectedEntries, IReadOnlyList actualEntries) + protected static void AssertEntries(IReadOnlyList expectedEntries, IReadOnlyList actualEntries) { var newEntities = new HashSet(actualEntries.Select(ne => ne.Entity)); var missingEntities = expectedEntries.Select(e => e.Entity).Where(e => !newEntities.Contains(e)).ToList(); @@ -690,7 +694,7 @@ private static void AssertEntries(IReadOnlyList expectedEntries, IR Assert.Equal(expectedEntries.Count, actualEntries.Count); } - private static void AssertKeys(Root expected, Root actual) + protected static void AssertKeys(Root expected, Root actual) { Assert.Equal(expected.Id, actual.Id); @@ -813,7 +817,7 @@ private static void AssertKeys(Root expected, Root actual) e => new { e.Id, e.ParentAlternateId })); } - private static void AssertNavigations(Root root) + protected static void AssertNavigations(Root root) { foreach (var child in root.RequiredChildren) { @@ -896,7 +900,7 @@ private static void AssertNavigations(Root root) } } - private static void AssertPossiblyNullNavigations(Root root) + protected static void AssertPossiblyNullNavigations(Root root) { foreach (var child in root.RequiredChildren) { diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseMiscellaneous.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseMiscellaneous.cs new file mode 100644 index 00000000000..7204fc1758b --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseMiscellaneous.cs @@ -0,0 +1,759 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; + +// ReSharper disable AccessToDisposedClosure +// ReSharper disable PossibleMultipleEnumeration +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class GraphUpdatesTestBase + where TFixture : GraphUpdatesTestBase.GraphUpdatesFixtureBase, new() + { + [ConditionalTheory] + [InlineData((int)ChangeMechanism.Fk)] + [InlineData((int)ChangeMechanism.Dependent)] + [InlineData((int)(ChangeMechanism.Dependent | ChangeMechanism.Fk))] + public virtual void Changes_to_Added_relationships_are_picked_up(ChangeMechanism changeMechanism) + { + var id = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var entity = new OptionalSingle1(); + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + entity.RootId = 5545; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + entity.Root = new Root(); + } + + context.Add(entity); + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + entity.RootId = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + entity.Root = null; + } + + context.ChangeTracker.DetectChanges(); + + Assert.Null(entity.RootId); + Assert.Null(entity.Root); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + id = entity.Id; + }, + context => + { + var entity = context.Set().Include(e => e.Root).Single(e => e.Id == id); + + Assert.Null(entity.Root); + Assert.Null(entity.RootId); + }); + } + + [ConditionalTheory] + [InlineData(false, CascadeTiming.OnSaveChanges)] + [InlineData(false, CascadeTiming.Immediate)] + [InlineData(false, CascadeTiming.Never)] + [InlineData(false, null)] + [InlineData(true, CascadeTiming.OnSaveChanges)] + [InlineData(true, CascadeTiming.Immediate)] + [InlineData(true, CascadeTiming.Never)] + [InlineData(true, null)] + public virtual void New_FK_is_not_cleared_on_old_dependent_delete( + bool loadNewParent, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var childId = 0; + int? newFk = 0; + + ExecuteWithStrategyInTransaction( + context => + { + 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); + + removedId = removed.Id; + childId = child.Id; + + newFk = context.Set().AsNoTracking().Single(e => e.Id != removed.Id).Id; + + var newParent = loadNewParent ? context.Set().Find(newFk) : null; + + child.ParentId = newFk; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(newFk, child.ParentId); + + if (loadNewParent) + { + Assert.Same(newParent, child.Parent); + Assert.Contains(child, newParent.Children); + } + else + { + Assert.Null((child.Parent)); + } + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + Assert.Null(context.Set().Find(removedId)); + + var child = context.Set().Find(childId); + var newParent = loadNewParent ? context.Set().Find(newFk) : null; + + Assert.Equal(newFk, child.ParentId); + + if (loadNewParent) + { + Assert.Same(newParent, child.Parent); + Assert.Contains(child, newParent.Children); + } + else + { + Assert.Null((child.Parent)); + } + + Assert.False(context.ChangeTracker.HasChanges()); + } + }); + } + + [ConditionalTheory] + [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(); + + Assert.False(context.ChangeTracker.HasChanges()); + + existing.Parent = null; + existing.ParentId = null; + ((ICollection)root.OptionalChildren).Remove(existing); + + context.Entry(existing).State = EntityState.Deleted; + + Assert.True(context.ChangeTracker.HasChanges()); + + var queried = context.Set().ToList(); + + Assert.Null(existing.Parent); + Assert.Null(existing.ParentId); + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(existing, root.OptionalChildren); + + Assert.Equal(2, queried.Count); + Assert.Contains(existing, queried); + } + + [ConditionalFact] + public virtual void Notification_entities_can_have_indexes() + { + ExecuteWithStrategyInTransaction( + context => + { + var produce = new Produce { Name = "Apple", BarCode = 77 }; + context.Add(produce); + + Assert.Equal(EntityState.Added, context.Entry(produce).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Unchanged, context.Entry(produce).State); + Assert.NotEqual(Guid.Empty, context.Entry(produce).Property(e => e.ProduceId).OriginalValue); + Assert.Equal(77, context.Entry(produce).Property(e => e.BarCode).OriginalValue); + + context.Remove(produce); + Assert.Equal(EntityState.Deleted, context.Entry(produce).State); + Assert.NotEqual(Guid.Empty, context.Entry(produce).Property(e => e.ProduceId).OriginalValue); + Assert.Equal(77, context.Entry(produce).Property(e => e.BarCode).OriginalValue); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(produce).State); + }); + } + + [ConditionalFact] + public virtual void Resetting_a_deleted_reference_fixes_up_again() + { + ExecuteWithStrategyInTransaction( + context => + { + var bloog = context.Set().Include(e => e.Poosts).Single(); + var poost1 = bloog.Poosts.First(); + var poost2 = bloog.Poosts.Skip(1).First(); + + Assert.Equal(2, bloog.Poosts.Count()); + Assert.Same(bloog, poost1.Bloog); + Assert.Same(bloog, poost2.Bloog); + + context.Remove(bloog); + + Assert.True(context.ChangeTracker.HasChanges()); + + Assert.Equal(2, bloog.Poosts.Count()); + + if (Fixture.ForceClientNoAction) + { + Assert.Same(bloog, poost1.Bloog); + Assert.Same(bloog, poost2.Bloog); + } + else + { + Assert.Null(poost1.Bloog); + Assert.Null(poost2.Bloog); + } + + poost1.Bloog = bloog; + + Assert.Equal(2, bloog.Poosts.Count()); + + if (Fixture.ForceClientNoAction) + { + Assert.Same(bloog, poost1.Bloog); + Assert.Same(bloog, poost2.Bloog); + } + else + { + Assert.Same(bloog, poost1.Bloog); + Assert.Null(poost2.Bloog); + } + + poost1.Bloog = null; + + Assert.Equal(2, bloog.Poosts.Count()); + + if (Fixture.ForceClientNoAction) + { + Assert.Null(poost1.Bloog); + Assert.Same(bloog, poost2.Bloog); + } + else + { + Assert.Null(poost1.Bloog); + Assert.Null(poost2.Bloog); + } + + if (!Fixture.ForceClientNoAction) + { + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(2, bloog.Poosts.Count()); + Assert.Null(poost1.Bloog); + Assert.Null(poost2.Bloog); + } + }); + } + + [ConditionalFact] + public virtual void Detaching_principal_entity_will_remove_references_to_it() + { + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadOptionalGraph(context); + LoadRequiredGraph(context); + LoadOptionalAkGraph(context); + LoadRequiredAkGraph(context); + LoadRequiredCompositeGraph(context); + LoadRequiredNonPkGraph(context); + LoadOptionalOneToManyGraph(context); + LoadRequiredNonPkAkGraph(context); + + var optionalSingle = root.OptionalSingle; + var requiredSingle = root.RequiredSingle; + var optionalSingleAk = root.OptionalSingleAk; + var optionalSingleDerived = root.OptionalSingleDerived; + var requiredSingleAk = root.RequiredSingleAk; + var optionalSingleAkDerived = root.OptionalSingleAkDerived; + var optionalSingleMoreDerived = root.OptionalSingleMoreDerived; + var requiredNonPkSingle = root.RequiredNonPkSingle; + var optionalSingleAkMoreDerived = root.OptionalSingleAkMoreDerived; + var requiredNonPkSingleAk = root.RequiredNonPkSingleAk; + var requiredNonPkSingleDerived = root.RequiredNonPkSingleDerived; + var requiredNonPkSingleAkDerived = root.RequiredNonPkSingleAkDerived; + var requiredNonPkSingleMoreDerived = root.RequiredNonPkSingleMoreDerived; + var requiredNonPkSingleAkMoreDerived = root.RequiredNonPkSingleAkMoreDerived; + + Assert.Same(root, optionalSingle.Root); + Assert.Same(root, requiredSingle.Root); + Assert.Same(root, optionalSingleAk.Root); + Assert.Same(root, optionalSingleDerived.DerivedRoot); + Assert.Same(root, requiredSingleAk.Root); + Assert.Same(root, optionalSingleAkDerived.DerivedRoot); + Assert.Same(root, optionalSingleMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingle.Root); + Assert.Same(root, optionalSingleAkMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingleAk.Root); + Assert.Same(root, requiredNonPkSingleDerived.DerivedRoot); + Assert.Same(root, requiredNonPkSingleAkDerived.DerivedRoot); + Assert.Same(root, requiredNonPkSingleMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingleAkMoreDerived.MoreDerivedRoot); + + Assert.True(root.OptionalChildren.All(e => e.Parent == root)); + Assert.True(root.RequiredChildren.All(e => e.Parent == root)); + Assert.True(root.OptionalChildrenAk.All(e => e.Parent == root)); + Assert.True(root.RequiredChildrenAk.All(e => e.Parent == root)); + Assert.True(root.RequiredCompositeChildren.All(e => e.Parent == root)); + + Assert.False(context.ChangeTracker.HasChanges()); + + context.Entry(optionalSingle).State = EntityState.Detached; + context.Entry(requiredSingle).State = EntityState.Detached; + context.Entry(optionalSingleAk).State = EntityState.Detached; + context.Entry(optionalSingleDerived).State = EntityState.Detached; + context.Entry(requiredSingleAk).State = EntityState.Detached; + context.Entry(optionalSingleAkDerived).State = EntityState.Detached; + context.Entry(optionalSingleMoreDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingle).State = EntityState.Detached; + context.Entry(optionalSingleAkMoreDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingleAk).State = EntityState.Detached; + context.Entry(requiredNonPkSingleDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingleAkDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingleMoreDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingleAkMoreDerived).State = EntityState.Detached; + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.NotNull(optionalSingle.Root); + Assert.NotNull(requiredSingle.Root); + Assert.NotNull(optionalSingleAk.Root); + Assert.NotNull(optionalSingleDerived.DerivedRoot); + Assert.NotNull(requiredSingleAk.Root); + Assert.NotNull(optionalSingleAkDerived.DerivedRoot); + Assert.NotNull(optionalSingleMoreDerived.MoreDerivedRoot); + Assert.NotNull(requiredNonPkSingle.Root); + Assert.NotNull(optionalSingleAkMoreDerived.MoreDerivedRoot); + Assert.NotNull(requiredNonPkSingleAk.Root); + Assert.NotNull(requiredNonPkSingleDerived.DerivedRoot); + Assert.NotNull(requiredNonPkSingleAkDerived.DerivedRoot); + Assert.NotNull(requiredNonPkSingleMoreDerived.MoreDerivedRoot); + Assert.NotNull(requiredNonPkSingleAkMoreDerived.MoreDerivedRoot); + + Assert.True(root.OptionalChildren.All(e => e.Parent != null)); + Assert.True(root.RequiredChildren.All(e => e.Parent != null)); + Assert.True(root.OptionalChildrenAk.All(e => e.Parent != null)); + Assert.True(root.RequiredChildrenAk.All(e => e.Parent != null)); + Assert.True(root.RequiredCompositeChildren.All(e => e.Parent != null)); + }); + } + + [ConditionalFact] + public virtual void Detaching_dependent_entity_will_not_remove_references_to_it() + { + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadOptionalGraph(context); + LoadRequiredGraph(context); + LoadOptionalAkGraph(context); + LoadRequiredAkGraph(context); + LoadRequiredCompositeGraph(context); + LoadRequiredNonPkGraph(context); + LoadOptionalOneToManyGraph(context); + LoadRequiredNonPkAkGraph(context); + + var optionalSingle = root.OptionalSingle; + var requiredSingle = root.RequiredSingle; + var optionalSingleAk = root.OptionalSingleAk; + var optionalSingleDerived = root.OptionalSingleDerived; + var requiredSingleAk = root.RequiredSingleAk; + var optionalSingleAkDerived = root.OptionalSingleAkDerived; + var optionalSingleMoreDerived = root.OptionalSingleMoreDerived; + var requiredNonPkSingle = root.RequiredNonPkSingle; + var optionalSingleAkMoreDerived = root.OptionalSingleAkMoreDerived; + var requiredNonPkSingleAk = root.RequiredNonPkSingleAk; + var requiredNonPkSingleDerived = root.RequiredNonPkSingleDerived; + var requiredNonPkSingleAkDerived = root.RequiredNonPkSingleAkDerived; + var requiredNonPkSingleMoreDerived = root.RequiredNonPkSingleMoreDerived; + var requiredNonPkSingleAkMoreDerived = root.RequiredNonPkSingleAkMoreDerived; + + var optionalChildren = root.OptionalChildren; + var requiredChildren = root.RequiredChildren; + var optionalChildrenAk = root.OptionalChildrenAk; + var requiredChildrenAk = root.RequiredChildrenAk; + var requiredCompositeChildren = root.RequiredCompositeChildren; + var optionalChild = optionalChildren.First(); + var requiredChild = requiredChildren.First(); + var optionalChildAk = optionalChildrenAk.First(); + var requieredChildAk = requiredChildrenAk.First(); + var requiredCompositeChild = requiredCompositeChildren.First(); + + Assert.Same(root, optionalSingle.Root); + Assert.Same(root, requiredSingle.Root); + Assert.Same(root, optionalSingleAk.Root); + Assert.Same(root, optionalSingleDerived.DerivedRoot); + Assert.Same(root, requiredSingleAk.Root); + Assert.Same(root, optionalSingleAkDerived.DerivedRoot); + Assert.Same(root, optionalSingleMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingle.Root); + Assert.Same(root, optionalSingleAkMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingleAk.Root); + Assert.Same(root, requiredNonPkSingleDerived.DerivedRoot); + Assert.Same(root, requiredNonPkSingleAkDerived.DerivedRoot); + Assert.Same(root, requiredNonPkSingleMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingleAkMoreDerived.MoreDerivedRoot); + + Assert.True(optionalChildren.All(e => e.Parent == root)); + Assert.True(requiredChildren.All(e => e.Parent == root)); + Assert.True(optionalChildrenAk.All(e => e.Parent == root)); + Assert.True(requiredChildrenAk.All(e => e.Parent == root)); + Assert.True(requiredCompositeChildren.All(e => e.Parent == root)); + + Assert.False(context.ChangeTracker.HasChanges()); + + context.Entry(optionalSingle).State = EntityState.Detached; + context.Entry(requiredSingle).State = EntityState.Detached; + context.Entry(optionalSingleAk).State = EntityState.Detached; + context.Entry(optionalSingleDerived).State = EntityState.Detached; + context.Entry(requiredSingleAk).State = EntityState.Detached; + context.Entry(optionalSingleAkDerived).State = EntityState.Detached; + context.Entry(optionalSingleMoreDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingle).State = EntityState.Detached; + context.Entry(optionalSingleAkMoreDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingleAk).State = EntityState.Detached; + context.Entry(requiredNonPkSingleDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingleAkDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingleMoreDerived).State = EntityState.Detached; + context.Entry(requiredNonPkSingleAkMoreDerived).State = EntityState.Detached; + context.Entry(optionalChild).State = EntityState.Detached; + context.Entry(requiredChild).State = EntityState.Detached; + context.Entry(optionalChildAk).State = EntityState.Detached; + context.Entry(requieredChildAk).State = EntityState.Detached; + + foreach (var overlappingEntry in context.ChangeTracker.Entries()) + { + overlappingEntry.State = EntityState.Detached; + } + + context.Entry(requiredCompositeChild).State = EntityState.Detached; + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Same(root, optionalSingle.Root); + Assert.Same(root, requiredSingle.Root); + Assert.Same(root, optionalSingleAk.Root); + Assert.Same(root, optionalSingleDerived.DerivedRoot); + Assert.Same(root, requiredSingleAk.Root); + Assert.Same(root, optionalSingleAkDerived.DerivedRoot); + Assert.Same(root, optionalSingleMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingle.Root); + Assert.Same(root, optionalSingleAkMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingleAk.Root); + Assert.Same(root, requiredNonPkSingleDerived.DerivedRoot); + Assert.Same(root, requiredNonPkSingleAkDerived.DerivedRoot); + Assert.Same(root, requiredNonPkSingleMoreDerived.MoreDerivedRoot); + Assert.Same(root, requiredNonPkSingleAkMoreDerived.MoreDerivedRoot); + + Assert.True(optionalChildren.All(e => e.Parent == root)); + Assert.True(requiredChildren.All(e => e.Parent == root)); + Assert.True(optionalChildrenAk.All(e => e.Parent == root)); + Assert.True(requiredChildrenAk.All(e => e.Parent == root)); + Assert.True(requiredCompositeChildren.All(e => e.Parent == root)); + + Assert.NotNull(root.OptionalSingle); + Assert.NotNull(root.RequiredSingle); + Assert.NotNull(root.OptionalSingleAk); + Assert.NotNull(root.OptionalSingleDerived); + Assert.NotNull(root.RequiredSingleAk); + Assert.NotNull(root.OptionalSingleAkDerived); + Assert.NotNull(root.OptionalSingleMoreDerived); + Assert.NotNull(root.RequiredNonPkSingle); + Assert.NotNull(root.OptionalSingleAkMoreDerived); + Assert.NotNull(root.RequiredNonPkSingleAk); + Assert.NotNull(root.RequiredNonPkSingleDerived); + Assert.NotNull(root.RequiredNonPkSingleAkDerived); + Assert.NotNull(root.RequiredNonPkSingleMoreDerived); + Assert.NotNull(root.RequiredNonPkSingleAkMoreDerived); + + Assert.Contains(optionalChild, root.OptionalChildren); + Assert.Contains(requiredChild, root.RequiredChildren); + Assert.Contains(optionalChildAk, root.OptionalChildrenAk); + Assert.Contains(requieredChildAk, root.RequiredChildrenAk); + Assert.Contains(requiredCompositeChild, root.RequiredCompositeChildren); + }); + } + + [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; + + 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; + oldId = oldChild.Id; + + context.Remove(oldChild); + + var newChild = new ChildAsAParent(); + parent.ChildAsAParent = newChild; + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + newId = newChild.Id; + Assert.NotEqual(newId, oldId); + + Assert.Equal(newId, parent.ChildAsAParentId); + Assert.Same(newChild, parent.ChildAsAParent); + + Assert.Equal(EntityState.Detached, context.Entry(oldChild).State); + Assert.Equal(EntityState.Unchanged, context.Entry(newChild).State); + Assert.Equal(EntityState.Unchanged, context.Entry(parent).State); + }, + context => + { + var parent = context.Set().Include(p => p.ChildAsAParent).Single(); + + Assert.Equal(newId, parent.ChildAsAParentId); + Assert.Equal(newId, parent.ChildAsAParent.Id); + Assert.Null(context.Set().Find(oldId)); + }); + } + + [ConditionalFact] + public virtual void Sometimes_not_calling_DetectChanges_when_required_does_not_throw_for_null_ref() + { + ExecuteWithStrategyInTransaction( + context => + { + var dependent = context.Set().Single(); + + dependent.BadCustomerId = null; + + var principal = context.Set().Single(); + + principal.Status++; + + Assert.Null(dependent.BadCustomerId); + Assert.Null(dependent.BadCustomer); + Assert.Empty(principal.BadOrders); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(dependent.BadCustomerId); + Assert.Null(dependent.BadCustomer); + Assert.Empty(principal.BadOrders); + }, + context => + { + var dependent = context.Set().Single(); + var principal = context.Set().Single(); + + Assert.Null(dependent.BadCustomerId); + Assert.Null(dependent.BadCustomer); + Assert.Empty(principal.BadOrders); + }); + } + + [ConditionalFact] + public virtual void Can_add_valid_first_dependent_when_multiple_possible_principal_sides() + { + ExecuteWithStrategyInTransaction( + context => + { + var quizTask = new QuizTask(); + quizTask.Choices.Add(new TaskChoice()); + + context.Add(quizTask); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + }, + context => + { + var quizTask = context.Set().Include(e => e.Choices).Single(); + + Assert.Equal(quizTask.Id, quizTask.Choices.Single().QuestTaskId); + + Assert.Same(quizTask.Choices.Single(), context.Set().Single()); + + Assert.Empty(context.Set().Include(e => e.Choices)); + }); + } + + [ConditionalFact] + public virtual void Can_add_valid_second_dependent_when_multiple_possible_principal_sides() + { + ExecuteWithStrategyInTransaction( + context => + { + var hiddenAreaTask = new HiddenAreaTask(); + hiddenAreaTask.Choices.Add(new TaskChoice()); + + context.Add(hiddenAreaTask); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + }, + context => + { + var hiddenAreaTask = context.Set().Include(e => e.Choices).Single(); + + Assert.Equal(hiddenAreaTask.Id, hiddenAreaTask.Choices.Single().QuestTaskId); + + Assert.Same(hiddenAreaTask.Choices.Single(), context.Set().Single()); + + Assert.Empty(context.Set().Include(e => e.Choices)); + }); + } + + [ConditionalFact] + public virtual void Can_add_multiple_dependents_when_multiple_possible_principal_sides() + { + ExecuteWithStrategyInTransaction( + context => + { + var quizTask = new QuizTask(); + quizTask.Choices.Add(new TaskChoice()); + quizTask.Choices.Add(new TaskChoice()); + + context.Add(quizTask); + + var hiddenAreaTask = new HiddenAreaTask(); + hiddenAreaTask.Choices.Add(new TaskChoice()); + hiddenAreaTask.Choices.Add(new TaskChoice()); + + context.Add(hiddenAreaTask); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + }, + context => + { + var quizTask = context.Set().Include(e => e.Choices).Single(); + var hiddenAreaTask = context.Set().Include(e => e.Choices).Single(); + + Assert.Equal(2, quizTask.Choices.Count); + foreach (var quizTaskChoice in quizTask.Choices) + { + Assert.Equal(quizTask.Id, quizTaskChoice.QuestTaskId); + } + + Assert.Equal(2, hiddenAreaTask.Choices.Count); + foreach (var hiddenAreaTaskChoice in hiddenAreaTask.Choices) + { + Assert.Equal(hiddenAreaTask.Id, hiddenAreaTaskChoice.QuestTaskId); + } + + foreach (var taskChoice in context.Set()) + { + Assert.Equal( + 1, + quizTask.Choices.Count(e => e.Id == taskChoice.Id) + + hiddenAreaTask.Choices.Count(e => e.Id == taskChoice.Id)); + } + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToMany.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToMany.cs new file mode 100644 index 00000000000..0231705c82f --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToMany.cs @@ -0,0 +1,1853 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class GraphUpdatesTestBase + where TFixture : GraphUpdatesTestBase.GraphUpdatesFixtureBase, new() + { + [ConditionalTheory] + [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(); + var new1dd = new Optional1MoreDerived(); + var new2a = new Optional2(); + var new2b = new Optional2(); + var new2d = new Optional2Derived(); + var new2dd = new Optional2MoreDerived(); + Root root = null; + IReadOnlyList entries = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalGraph(context); + var existing = root.OptionalChildren.OrderBy(e => e.Id).First(); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (Optional1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (Optional1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2a = context.Set().Single(e => e.Id == new2a.Id); + new2b = context.Set().Single(e => e.Id == new2b.Id); + new2d = (Optional2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (Optional2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Add(existing.Children, new2a); + Add(existing.Children, new2b); + Add(new1d.Children, new2d); + Add(new1dd.Children, new2dd); + Add(root.OptionalChildren, new1); + Add(root.OptionalChildren, new1d); + Add(root.OptionalChildren, new1dd); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new2a.Parent = existing; + new2b.Parent = existing; + new2d.Parent = new1d; + new2dd.Parent = new1dd; + new1.Parent = root; + new1d.Parent = root; + new1dd.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new2a.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; + new2b.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; + new2d.ParentId = context.Entry(new1d).Property(e => e.Id).CurrentValue; + new2dd.ParentId = context.Entry(new1dd).Property(e => e.Id).CurrentValue; + new1.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + new1d.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + new1dd.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Contains(new2a, existing.Children); + Assert.Contains(new2b, existing.Children); + Assert.Contains(new2d, new1d.Children); + Assert.Contains(new2dd, new1dd.Children); + Assert.Contains(new1, root.OptionalChildren); + Assert.Contains(new1d, root.OptionalChildren); + Assert.Contains(new1dd, root.OptionalChildren); + + Assert.Same(existing, new2a.Parent); + Assert.Same(existing, new2b.Parent); + Assert.Same(new1d, new2d.Parent); + Assert.Same(new1dd, new2dd.Parent); + Assert.Same(root, existing.Parent); + Assert.Same(root, new1d.Parent); + Assert.Same(root, new1dd.Parent); + + Assert.Equal(existing.Id, new2a.ParentId); + Assert.Equal(existing.Id, new2b.ParentId); + Assert.Equal(new1d.Id, new2d.ParentId); + Assert.Equal(new1dd.Id, new2dd.ParentId); + Assert.Equal(root.Id, existing.ParentId); + Assert.Equal(root.Id, new1d.ParentId); + Assert.Equal(root.Id, new1dd.ParentId); + + entries = context.ChangeTracker.Entries().ToList(); + }, + context => + { + var loadedRoot = LoadOptionalGraph(context); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + }); + } + + [ConditionalTheory] + [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 { Parent = newRoot }; + var new1d = new Required1Derived { Parent = newRoot }; + var new1dd = new Required1MoreDerived { Parent = newRoot }; + var new2a = new Required2 { Parent = new1 }; + var new2b = new Required2 { Parent = new1 }; + var new2d = new Required2Derived { Parent = new1 }; + var new2dd = new Required2MoreDerived { Parent = new1 }; + Root root = null; + IReadOnlyList entries = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new1d, new1dd, new2a, new2d, new2dd, new2b); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredGraph(context); + var existing = root.RequiredChildren.OrderBy(e => e.Id).First(); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (Required1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (Required1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2a = context.Set().Single(e => e.Id == new2a.Id); + new2b = context.Set().Single(e => e.Id == new2b.Id); + new2d = (Required2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (Required2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + new1.Parent = null; + new1d.Parent = null; + new1dd.Parent = null; + + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Add(existing.Children, new2a); + Add(existing.Children, new2b); + Add(new1d.Children, new2d); + Add(new1dd.Children, new2dd); + Add(root.RequiredChildren, new1); + Add(root.RequiredChildren, new1d); + Add(root.RequiredChildren, new1dd); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new2a.Parent = existing; + new2b.Parent = existing; + new2d.Parent = new1d; + new2dd.Parent = new1dd; + new1.Parent = root; + new1d.Parent = root; + new1dd.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new2a.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; + new2b.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; + new2d.ParentId = context.Entry(new1d).Property(e => e.Id).CurrentValue; + new2dd.ParentId = context.Entry(new1dd).Property(e => e.Id).CurrentValue; + new1.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + new1d.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + new1dd.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Contains(new2a, existing.Children); + Assert.Contains(new2b, existing.Children); + Assert.Contains(new2d, new1d.Children); + Assert.Contains(new2dd, new1dd.Children); + Assert.Contains(new1, root.RequiredChildren); + Assert.Contains(new1d, root.RequiredChildren); + Assert.Contains(new1dd, root.RequiredChildren); + + Assert.Same(existing, new2a.Parent); + Assert.Same(existing, new2b.Parent); + Assert.Same(new1d, new2d.Parent); + Assert.Same(new1dd, new2dd.Parent); + Assert.Same(root, existing.Parent); + Assert.Same(root, new1d.Parent); + Assert.Same(root, new1dd.Parent); + + Assert.Equal(existing.Id, new2a.ParentId); + Assert.Equal(existing.Id, new2b.ParentId); + Assert.Equal(new1d.Id, new2d.ParentId); + Assert.Equal(new1dd.Id, new2dd.ParentId); + Assert.Equal(root.Id, existing.ParentId); + Assert.Equal(root.Id, new1d.ParentId); + Assert.Equal(root.Id, new1dd.ParentId); + + entries = context.ChangeTracker.Entries().ToList(); + }, + context => + { + var loadedRoot = LoadRequiredGraph(context); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + }); + } + + [ConditionalTheory] + [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; + var removed2 = childCollection.First(); + var removed1 = root.OptionalChildren.Skip(1).First(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Remove(childCollection, removed2); + Remove(root.OptionalChildren, removed1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + removed2.Parent = null; + removed1.Parent = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + removed2.ParentId = null; + removed1.ParentId = null; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.DoesNotContain(removed1, root.OptionalChildren); + Assert.DoesNotContain(removed2, childCollection); + + Assert.Null(removed1.Parent); + Assert.Null(removed2.Parent); + Assert.Null(removed1.ParentId); + Assert.Null(removed2.ParentId); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadOptionalGraph(context); + + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + Assert.Single(loadedRoot.OptionalChildren); + Assert.Single(loadedRoot.OptionalChildren.First().Children); + } + }); + } + + [ConditionalTheory] + [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; + List removed1ChildrenIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredGraph(context); + + var childCollection = root.RequiredChildren.First().Children; + var removed2 = childCollection.First(); + var removed1 = root.RequiredChildren.Skip(1).First(); + + removed1Id = removed1.Id; + removed2Id = removed2.Id; + removed1ChildrenIds = removed1.Children.Select(e => e.Id).ToList(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Remove(childCollection, removed2); + Remove(root.RequiredChildren, removed1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + removed2.Parent = null; + removed1.Parent = null; + } + + if (Fixture.ForceClientNoAction + || deleteOrphansTiming == CascadeTiming.Never) + { + Action testCode; + + 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; + } + + testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? () => context.ChangeTracker.DetectChanges() + : deleteOrphansTiming == null + ? () => 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()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades + && deleteOrphansTiming != CascadeTiming.Never) + { + var root = LoadRequiredGraph(context); + + AssertNavigations(root); + + Assert.Single(root.RequiredChildren); + Assert.DoesNotContain(removed1Id, root.RequiredChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removed1Id)); + Assert.Empty(context.Set().Where(e => e.Id == removed2Id)); + Assert.Empty(context.Set().Where(e => removed1ChildrenIds.Contains(e.Id))); + } + }); + } + + [ConditionalTheory] + [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; + var compositeCount = 0; + OptionalAk1 oldParent = null; + OptionalComposite2 oldComposite1 = null; + OptionalComposite2 oldComposite2 = null; + Optional1 newParent = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (!useExistingParent) + { + newParent = new Optional1 + { + CompositeChildren = new ObservableHashSet(ReferenceEqualityComparer.Instance) + }; + + context.Set().Add(newParent); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalOneToManyGraph(context); + + compositeCount = context.Set().Count(); + + oldParent = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); + + oldComposite1 = oldParent.CompositeChildren.OrderBy(e => e.Id).First(); + oldComposite2 = oldParent.CompositeChildren.OrderBy(e => e.Id).Last(); + + if (useExistingParent) + { + newParent = root.OptionalChildren.OrderBy(e => e.Id).Last(); + } + else + { + newParent = context.Set().Single(e => e.Id == newParent.Id); + newParent.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + oldParent.CompositeChildren.Remove(oldComposite1); + newParent.CompositeChildren.Add(oldComposite1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + oldComposite1.Parent = null; + oldComposite1.Parent2 = newParent; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + oldComposite1.ParentId = null; + oldComposite1.Parent2Id = newParent.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Same(oldComposite2, oldParent.CompositeChildren.Single()); + Assert.Same(oldParent, oldComposite2.Parent); + Assert.Equal(oldParent.Id, oldComposite2.ParentId); + Assert.Null(oldComposite2.Parent2); + Assert.Null(oldComposite2.Parent2Id); + + Assert.Same(oldComposite1, newParent.CompositeChildren.Single()); + Assert.Same(newParent, oldComposite1.Parent2); + Assert.Equal(newParent.Id, oldComposite1.Parent2Id); + Assert.Null(oldComposite1.Parent); + Assert.Null(oldComposite1.ParentId); + + entries = context.ChangeTracker.Entries().ToList(); + + Assert.Equal(compositeCount, context.Set().Count()); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadOptionalOneToManyGraph(context); + + Assert.False(context.ChangeTracker.HasChanges()); + + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + oldParent = context.Set().Single(e => e.Id == oldParent.Id); + newParent = context.Set().Single(e => e.Id == newParent.Id); + + oldComposite1 = context.Set().Single(e => e.Id == oldComposite1.Id); + oldComposite2 = context.Set().Single(e => e.Id == oldComposite2.Id); + + Assert.Same(oldComposite2, oldParent.CompositeChildren.Single()); + Assert.Same(oldParent, oldComposite2.Parent); + Assert.Equal(oldParent.Id, oldComposite2.ParentId); + Assert.Null(oldComposite2.Parent2); + Assert.Null(oldComposite2.Parent2Id); + + Assert.Same(oldComposite1, newParent.CompositeChildren.Single()); + Assert.Same(newParent, oldComposite1.Parent2); + Assert.Equal(newParent.Id, oldComposite1.Parent2Id); + Assert.Null(oldComposite1.Parent); + Assert.Null(oldComposite1.ParentId); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + + Assert.Equal(compositeCount, context.Set().Count()); + } + }); + } + + [ConditionalTheory] + [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; + var childCount = 0; + RequiredComposite1 oldParent = null; + OptionalOverlapping2 oldChild1 = null; + OptionalOverlapping2 oldChild2 = null; + RequiredComposite1 newParent = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (!useExistingParent) + { + newParent = new RequiredComposite1 + { + Id = 3, + Parent = context.Set().Single(IsTheRoot), + CompositeChildren = new ObservableHashSet(ReferenceEqualityComparer.Instance) + { + new OptionalOverlapping2 { Id = 5 }, new OptionalOverlapping2 { Id = 6 } + } + }; + + context.Set().Add(newParent); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredCompositeGraph(context); + + childCount = context.Set().Count(); + + oldParent = root.RequiredCompositeChildren.OrderBy(e => e.Id).First(); + + oldChild1 = oldParent.CompositeChildren.OrderBy(e => e.Id).First(); + oldChild2 = oldParent.CompositeChildren.OrderBy(e => e.Id).Last(); + + Assert.Equal(useExistingParent ? 2 : 3, root.RequiredCompositeChildren.Count()); + + if (useExistingParent) + { + newParent = root.RequiredCompositeChildren.OrderBy(e => e.Id).Last(); + } + else + { + newParent = context.Set().Single(e => e.Id == newParent.Id); + newParent.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + oldParent.CompositeChildren.Remove(oldChild1); + newParent.CompositeChildren.Add(oldChild1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + oldChild1.Parent = newParent; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + oldChild1.ParentId = newParent.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Same(oldChild2, oldParent.CompositeChildren.Single()); + Assert.Same(oldParent, oldChild2.Parent); + Assert.Equal(oldParent.Id, oldChild2.ParentId); + Assert.Equal(oldParent.ParentAlternateId, oldChild2.ParentAlternateId); + Assert.Equal(root.AlternateId, oldChild2.ParentAlternateId); + Assert.Same(root, oldChild2.Root); + + Assert.Equal(3, newParent.CompositeChildren.Count); + Assert.Same(oldChild1, newParent.CompositeChildren.Single(e => e.Id == oldChild1.Id)); + Assert.Same(newParent, oldChild1.Parent); + Assert.Equal(newParent.Id, oldChild1.ParentId); + Assert.Equal(oldParent.ParentAlternateId, oldChild1.ParentAlternateId); + Assert.Equal(root.AlternateId, oldChild1.ParentAlternateId); + Assert.Same(root, oldChild1.Root); + + entries = context.ChangeTracker.Entries().ToList(); + + Assert.Equal(childCount, context.Set().Count()); + }, + context => + { + var loadedRoot = LoadRequiredCompositeGraph(context); + + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + oldParent = context.Set().Single(e => e.Id == oldParent.Id); + newParent = context.Set().Single(e => e.Id == newParent.Id); + + oldChild1 = context.Set().Single(e => e.Id == oldChild1.Id); + oldChild2 = context.Set().Single(e => e.Id == oldChild2.Id); + + Assert.Same(oldChild2, oldParent.CompositeChildren.Single()); + Assert.Same(oldParent, oldChild2.Parent); + Assert.Equal(oldParent.Id, oldChild2.ParentId); + Assert.Equal(oldParent.ParentAlternateId, oldChild2.ParentAlternateId); + Assert.Equal(root.AlternateId, oldChild2.ParentAlternateId); + + Assert.Same(oldChild1, newParent.CompositeChildren.Single(e => e.Id == oldChild1.Id)); + Assert.Same(newParent, oldChild1.Parent); + Assert.Equal(newParent.Id, oldChild1.ParentId); + Assert.Equal(oldParent.ParentAlternateId, oldChild1.ParentAlternateId); + Assert.Equal(root.AlternateId, oldChild1.ParentAlternateId); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + + Assert.Equal(childCount, context.Set().Count()); + }); + } + + [ConditionalTheory] + [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(); + + var childCount = context.Set().Count(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + context.Entry(parent).Collection(p => p.CompositeChildren).IsModified = true; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + context.Entry(child).Reference(c => c.Parent).IsModified = true; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + context.Entry(child).Property(c => c.ParentId).IsModified = true; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Same(child, parent.CompositeChildren.OrderBy(e => e.Id).First()); + Assert.Same(parent, child.Parent); + Assert.Equal(parent.Id, child.ParentId); + Assert.Equal(parent.ParentAlternateId, child.ParentAlternateId); + Assert.Equal(root.AlternateId, child.ParentAlternateId); + Assert.Same(root, child.Root); + + Assert.Equal(childCount, context.Set().Count()); + }); + } + + [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_dependents_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredGraph(context); + + Assert.Equal(2, root.RequiredChildren.Count()); + + var removed = root.RequiredChildren.First(); + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + 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.ForceClientNoAction ? EntityState.Unchanged : EntityState.Deleted))); + } + + if (Fixture.ForceClientNoAction) + { + 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.Single(root.RequiredChildren); + 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredGraph(context); + + Assert.Single(root.RequiredChildren); + 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))); + } + }); + } + + [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(); + + Assert.Equal(2, parent.Children.Count()); + var removed = parent.Children.First(); + + removedId = removed.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(parent.Children); + Assert.DoesNotContain(removedId, parent.Children.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + Assert.Same(parent, removed.Parent); + }, + context => + { + var root = LoadRequiredGraph(context); + var parent = root.RequiredChildren.First(); + + Assert.Single(parent.Children); + Assert.DoesNotContain(removedId, parent.Children.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + }); + } + + [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_dependents_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadOptionalGraph(context); + + Assert.Equal(2, root.OptionalChildren.Count()); + + var removed = root.OptionalChildren.First(); + + removedId = removed.Id; + var orphaned = removed.Children.ToList(); + orphanedIds = orphaned.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + 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.ForceClientNoAction ? EntityState.Unchanged : EntityState.Modified))); + } + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + var root = LoadOptionalGraph(context); + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + } + }); + } + + [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(); + + Assert.Equal(2, parent.Children.Count()); + + var removed = parent.Children.First(); + removedId = removed.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(parent.Children); + Assert.DoesNotContain(removedId, parent.Children.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + Assert.Same(parent, removed.Parent); + }, + context => + { + var root = LoadOptionalGraph(context); + var parent = root.OptionalChildren.First(); + + Assert.Single(parent.Children); + Assert.DoesNotContain(removedId, parent.Children.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + }); + } + + [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_dependents_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadRequiredGraph(context).RequiredChildren.First(); + + removedId = removed.Id; + orphanedIds = removed.Children.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = context.Set().Include(e => e.RequiredChildren).Single(IsTheRoot); + context.Set().Load(); + + var removed = root.RequiredChildren.Single(e => e.Id == removedId); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction + || Fixture.NoStoreCascades) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(root.RequiredChildren); + 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.Same(root, removed.Parent); + Assert.Empty(removed.Children); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var root = LoadRequiredGraph(context); + + Assert.Single(root.RequiredChildren); + 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))); + } + }); + } + + [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_dependents_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadOptionalGraph(context).OptionalChildren.First(); + + removedId = removed.Id; + orphanedIds = removed.Children.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = context.Set().Include(e => e.OptionalChildren).Single(IsTheRoot); + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + + var removed = root.OptionalChildren.First(e => e.Id == removedId); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIds.Count, orphaned.Count); + Assert.True(orphaned.All(e => e.ParentId == null)); + + Assert.Same(root, removed.Parent); + Assert.Empty(removed.Children); // Never loaded + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + var root = LoadOptionalGraph(context); + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIds.Count, orphaned.Count); + Assert.True(orphaned.All(e => e.ParentId == null)); + } + }); + } + + [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_dependents_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRequiredGraph(context); + + Assert.Equal(2, root.RequiredChildren.Count()); + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var removed = root.RequiredChildren.First(); + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + 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.ForceClientNoAction + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == expectedState)); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRequiredGraph(context); + + Assert.Single(root.RequiredChildren); + 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))); + } + }); + } + + [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_dependents_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadOptionalGraph(context); + + Assert.Equal(2, root.OptionalChildren.Count()); + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var removed = root.OptionalChildren.First(); + + removedId = removed.Id; + var orphaned = removed.Children.ToList(); + orphanedIds = orphaned.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + 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.ForceClientNoAction + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.True(orphaned.All(e => context.Entry(e).State == expectedState)); + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + root = LoadOptionalGraph(context); + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + } + }); + } + + [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_dependents_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredGraph(context); + + Assert.Equal(2, root.RequiredChildren.Count()); + + var removed = root.RequiredChildren.First(); + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + var added = new Required2(); + Add(removed.Children, added); + + if (context.ChangeTracker.AutoDetectChangesEnabled) + { + 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)); + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + 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.ForceClientNoAction) + { + 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()); + + if (Fixture.ForceClientNoAction) + { + 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.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()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredGraph(context); + + Assert.Single(root.RequiredChildren); + 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))); + } + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToManyAk.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToManyAk.cs new file mode 100644 index 00000000000..95c805982d6 --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToManyAk.cs @@ -0,0 +1,1374 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class GraphUpdatesTestBase + where TFixture : GraphUpdatesTestBase.GraphUpdatesFixtureBase, new() + { + [ConditionalTheory] + [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, + CascadeTiming? deleteOrphansTiming) + { + var new1 = new OptionalAk1 { AlternateId = Guid.NewGuid() }; + var new1d = new OptionalAk1Derived { AlternateId = Guid.NewGuid() }; + var new1dd = new OptionalAk1MoreDerived { AlternateId = Guid.NewGuid() }; + var new2a = new OptionalAk2 { AlternateId = Guid.NewGuid() }; + var new2b = new OptionalAk2 { AlternateId = Guid.NewGuid() }; + var new2ca = new OptionalComposite2(); + var new2cb = new OptionalComposite2(); + var new2d = new OptionalAk2Derived { AlternateId = Guid.NewGuid() }; + var new2dd = new OptionalAk2MoreDerived { AlternateId = Guid.NewGuid() }; + Root root = null; + IReadOnlyList entries = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalAkGraph(context); + var existing = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (OptionalAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (OptionalAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2a = context.Set().Single(e => e.Id == new2a.Id); + new2b = context.Set().Single(e => e.Id == new2b.Id); + new2ca = context.Set().Single(e => e.Id == new2ca.Id); + new2cb = context.Set().Single(e => e.Id == new2cb.Id); + new2d = (OptionalAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (OptionalAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Add(existing.Children, new2a); + Add(existing.Children, new2b); + Add(existing.CompositeChildren, new2ca); + Add(existing.CompositeChildren, new2cb); + Add(new1d.Children, new2d); + Add(new1dd.Children, new2dd); + Add(root.OptionalChildrenAk, new1); + Add(root.OptionalChildrenAk, new1d); + Add(root.OptionalChildrenAk, new1dd); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new2a.Parent = existing; + new2b.Parent = existing; + new2ca.Parent = existing; + new2cb.Parent = existing; + new2d.Parent = new1d; + new2dd.Parent = new1dd; + new1.Parent = root; + new1d.Parent = root; + new1dd.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new2a.ParentId = existing.AlternateId; + new2b.ParentId = existing.AlternateId; + new2ca.ParentId = existing.Id; + new2ca.ParentAlternateId = existing.AlternateId; + new2cb.ParentId = existing.Id; + new2cb.ParentAlternateId = existing.AlternateId; + new2d.ParentId = new1d.AlternateId; + new2dd.ParentId = new1dd.AlternateId; + new1.ParentId = root.AlternateId; + new1d.ParentId = root.AlternateId; + new1dd.ParentId = root.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Contains(new2a, existing.Children); + Assert.Contains(new2b, existing.Children); + Assert.Contains(new2ca, existing.CompositeChildren); + Assert.Contains(new2cb, existing.CompositeChildren); + Assert.Contains(new2d, new1d.Children); + Assert.Contains(new2dd, new1dd.Children); + Assert.Contains(new1, root.OptionalChildrenAk); + Assert.Contains(new1d, root.OptionalChildrenAk); + Assert.Contains(new1dd, root.OptionalChildrenAk); + + Assert.Same(existing, new2a.Parent); + Assert.Same(existing, new2b.Parent); + Assert.Same(existing, new2ca.Parent); + Assert.Same(existing, new2cb.Parent); + Assert.Same(new1d, new2d.Parent); + Assert.Same(new1dd, new2dd.Parent); + Assert.Same(root, existing.Parent); + Assert.Same(root, new1d.Parent); + Assert.Same(root, new1dd.Parent); + + Assert.Equal(existing.AlternateId, new2a.ParentId); + Assert.Equal(existing.AlternateId, new2b.ParentId); + Assert.Equal(existing.Id, new2ca.ParentId); + Assert.Equal(existing.Id, new2cb.ParentId); + Assert.Equal(existing.AlternateId, new2ca.ParentAlternateId); + Assert.Equal(existing.AlternateId, new2cb.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.ParentId); + Assert.Equal(new1dd.AlternateId, new2dd.ParentId); + Assert.Equal(root.AlternateId, existing.ParentId); + Assert.Equal(root.AlternateId, new1d.ParentId); + Assert.Equal(root.AlternateId, new1dd.ParentId); + + entries = context.ChangeTracker.Entries().ToList(); + }, + context => + { + var loadedRoot = LoadOptionalAkGraph(context); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + }); + } + + [ConditionalTheory] + [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, + CascadeTiming? deleteOrphansTiming) + { + var newRoot = new Root { AlternateId = Guid.NewGuid() }; + var new1 = new RequiredAk1 { AlternateId = Guid.NewGuid(), Parent = newRoot }; + var new1d = new RequiredAk1Derived { AlternateId = Guid.NewGuid(), Parent = newRoot }; + var new1dd = new RequiredAk1MoreDerived { AlternateId = Guid.NewGuid(), Parent = newRoot }; + var new2a = new RequiredAk2 { AlternateId = Guid.NewGuid(), Parent = new1 }; + var new2b = new RequiredAk2 { AlternateId = Guid.NewGuid(), Parent = new1 }; + var new2ca = new RequiredComposite2 { Parent = new1 }; + var new2cb = new RequiredComposite2 { Parent = new1 }; + var new2d = new RequiredAk2Derived { AlternateId = Guid.NewGuid(), Parent = new1 }; + var new2dd = new RequiredAk2MoreDerived { AlternateId = Guid.NewGuid(), Parent = new1 }; + Root root = null; + IReadOnlyList entries = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredAkGraph(context); + var existing = root.RequiredChildrenAk.OrderBy(e => e.Id).First(); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (RequiredAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (RequiredAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2a = context.Set().Single(e => e.Id == new2a.Id); + new2b = context.Set().Single(e => e.Id == new2b.Id); + new2ca = context.Set().Single(e => e.Id == new2ca.Id); + new2cb = context.Set().Single(e => e.Id == new2cb.Id); + new2d = (RequiredAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (RequiredAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + new1.Parent = null; + new1d.Parent = null; + new1dd.Parent = null; + + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Add(existing.Children, new2a); + Add(existing.Children, new2b); + Add(existing.CompositeChildren, new2ca); + Add(existing.CompositeChildren, new2cb); + Add(new1d.Children, new2d); + Add(new1dd.Children, new2dd); + Add(root.RequiredChildrenAk, new1); + Add(root.RequiredChildrenAk, new1d); + Add(root.RequiredChildrenAk, new1dd); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new2a.Parent = existing; + new2b.Parent = existing; + new2ca.Parent = existing; + new2cb.Parent = existing; + new2d.Parent = new1d; + new2dd.Parent = new1dd; + new1.Parent = root; + new1d.Parent = root; + new1dd.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new2a.ParentId = existing.AlternateId; + new2b.ParentId = existing.AlternateId; + new2ca.ParentId = existing.Id; + new2cb.ParentId = existing.Id; + new2ca.ParentAlternateId = existing.AlternateId; + new2cb.ParentAlternateId = existing.AlternateId; + new2d.ParentId = new1d.AlternateId; + new2dd.ParentId = new1dd.AlternateId; + new1.ParentId = root.AlternateId; + new1d.ParentId = root.AlternateId; + new1dd.ParentId = root.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Contains(new2a, existing.Children); + Assert.Contains(new2b, existing.Children); + Assert.Contains(new2ca, existing.CompositeChildren); + Assert.Contains(new2cb, existing.CompositeChildren); + Assert.Contains(new2d, new1d.Children); + Assert.Contains(new2dd, new1dd.Children); + Assert.Contains(new1, root.RequiredChildrenAk); + Assert.Contains(new1d, root.RequiredChildrenAk); + Assert.Contains(new1dd, root.RequiredChildrenAk); + + Assert.Same(existing, new2a.Parent); + Assert.Same(existing, new2b.Parent); + Assert.Same(existing, new2ca.Parent); + Assert.Same(existing, new2cb.Parent); + Assert.Same(new1d, new2d.Parent); + Assert.Same(new1dd, new2dd.Parent); + Assert.Same(root, existing.Parent); + Assert.Same(root, new1d.Parent); + Assert.Same(root, new1dd.Parent); + + Assert.Equal(existing.AlternateId, new2a.ParentId); + Assert.Equal(existing.AlternateId, new2b.ParentId); + Assert.Equal(existing.Id, new2ca.ParentId); + Assert.Equal(existing.Id, new2cb.ParentId); + Assert.Equal(existing.AlternateId, new2ca.ParentAlternateId); + Assert.Equal(existing.AlternateId, new2cb.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.ParentId); + Assert.Equal(new1dd.AlternateId, new2dd.ParentId); + Assert.Equal(root.AlternateId, existing.ParentId); + Assert.Equal(root.AlternateId, new1d.ParentId); + Assert.Equal(root.AlternateId, new1dd.ParentId); + + entries = context.ChangeTracker.Entries().ToList(); + }, + context => + { + var loadedRoot = LoadRequiredAkGraph(context); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + }); + } + + [ConditionalTheory] + [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(); + var childCollection = firstChild.Children; + var childCompositeCollection = firstChild.CompositeChildren; + var removed2 = childCollection.OrderByDescending(c => c.Id).First(); + var removed1 = root.OptionalChildrenAk.OrderByDescending(c => c.Id).Skip(1).First(); + var removed2c = childCompositeCollection.OrderByDescending(c => c.Id).First(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Remove(childCollection, removed2); + Remove(childCompositeCollection, removed2c); + Remove(root.OptionalChildrenAk, removed1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + removed2.Parent = null; + removed2c.Parent = null; + removed1.Parent = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + removed2.ParentId = null; + removed2c.ParentId = null; + removed1.ParentId = null; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.DoesNotContain(removed1, root.OptionalChildrenAk); + Assert.DoesNotContain(removed2, childCollection); + Assert.DoesNotContain(removed2c, childCompositeCollection); + + Assert.Null(removed1.Parent); + Assert.Null(removed2.Parent); + Assert.Null(removed2c.Parent); + + Assert.Null(removed1.ParentId); + Assert.Null(removed2.ParentId); + Assert.Null(removed2c.ParentId); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadOptionalAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + Assert.Single(loadedRoot.OptionalChildrenAk); + Assert.Single(loadedRoot.OptionalChildrenAk.OrderBy(c => c.Id).First().Children); + } + }); + } + + [ConditionalTheory] + [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; + RequiredComposite2 removed2c = null; + RequiredAk1 removed1 = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredAkGraph(context); + + var firstChild = root.RequiredChildrenAk.OrderByDescending(c => c.Id).First(); + var childCollection = firstChild.Children; + var childCompositeCollection = firstChild.CompositeChildren; + removed2 = childCollection.OrderBy(c => c.Id).First(); + removed2c = childCompositeCollection.OrderBy(c => c.Id).First(); + removed1 = root.RequiredChildrenAk.OrderByDescending(c => c.Id).Skip(1).First(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Remove(childCollection, removed2); + Remove(childCompositeCollection, removed2c); + Remove(root.RequiredChildrenAk, removed1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + removed2.Parent = null; + removed2c.Parent = null; + removed1.Parent = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + if (Fixture.ForceClientNoAction + || deleteOrphansTiming == CascadeTiming.Never) + { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? () => context.ChangeTracker.DetectChanges() + : deleteOrphansTiming == null + ? () => 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()); + + Assert.DoesNotContain(removed1, root.RequiredChildrenAk); + Assert.DoesNotContain(removed2, childCollection); + Assert.DoesNotContain(removed2c, childCompositeCollection); + + Assert.Null(removed1.Parent); + Assert.Null(removed2.Parent); + Assert.Null(removed2c.Parent); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades + && deleteOrphansTiming != CascadeTiming.Never) + { + var loadedRoot = LoadRequiredAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + Assert.False(context.Set().Any(e => e.Id == removed1.Id)); + Assert.False(context.Set().Any(e => e.Id == removed2.Id)); + Assert.False(context.Set().Any(e => e.Id == removed2c.Id)); + + Assert.Single(loadedRoot.RequiredChildrenAk); + Assert.Single(loadedRoot.RequiredChildrenAk.OrderBy(c => c.Id).First().Children); + Assert.Single(loadedRoot.RequiredChildrenAk.OrderBy(c => c.Id).First().CompositeChildren); + } + }); + } + + [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_dependents_with_alternate_key_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadOptionalAkGraph(context); + + Assert.Equal(2, root.OptionalChildrenAk.Count()); + + var removed = root.OptionalChildrenAk.OrderBy(c => c.Id).First(); + + removedId = removed.Id; + var orphaned = removed.Children.ToList(); + orphanedIds = orphaned.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + 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.ForceClientNoAction ? EntityState.Unchanged : EntityState.Modified))); + } + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + var root = LoadOptionalAkGraph(context); + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + } + }); + } + + [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_dependents_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + List orphanedIdCs = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredAkGraph(context); + + Assert.Equal(2, root.RequiredChildrenAk.Count()); + + var removed = root.RequiredChildrenAk.OrderBy(c => c.Id).First(); + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + var cascadeRemovedC = removed.CompositeChildren.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + Assert.Equal(2, orphanedIdCs.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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(); + + if (Fixture.ForceClientNoAction) + { + 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.ForceClientNoAction) + { + 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.Single(root.RequiredChildrenAk); + 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredAkGraph(context); + + Assert.Single(root.RequiredChildrenAk); + 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))); + } + }); + } + + [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_dependents_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + List orphanedIdCs = null; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadRequiredAkGraph(context).RequiredChildrenAk.First(); + + removedId = removed.Id; + orphanedIds = removed.Children.Select(e => e.Id).ToList(); + orphanedIdCs = removed.CompositeChildren.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + Assert.Equal(2, orphanedIdCs.Count); + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = context.Set().Include(e => e.RequiredChildrenAk).Single(IsTheRoot); + context.Set().Load(); + + var removed = root.RequiredChildrenAk.Single(e => e.Id == removedId); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction + || Fixture.NoStoreCascades) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(root.RequiredChildrenAk); + 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.Same(root, removed.Parent); + Assert.Empty(removed.Children); // Never loaded + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var root = LoadRequiredAkGraph(context); + + Assert.Single(root.RequiredChildrenAk); + 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))); + } + }); + } + + [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_dependents_with_alternate_key_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + List orphanedIdCs = null; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadOptionalAkGraph(context).OptionalChildrenAk.OrderBy(c => c.Id).First(); + + removedId = removed.Id; + orphanedIds = removed.Children.Select(e => e.Id).ToList(); + orphanedIdCs = removed.CompositeChildren.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + Assert.Equal(2, orphanedIdCs.Count); + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = context.Set().Include(e => e.OptionalChildrenAk).Single(IsTheRoot); + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + + var removed = root.OptionalChildrenAk.First(e => e.Id == removedId); + + context.Remove(removed); + + foreach (var toOrphan in context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList()) + { + toOrphan.ParentId = null; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIds.Count, orphaned.Count); + Assert.True(orphaned.All(e => e.ParentId == null)); + + var orphanedC = context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIdCs.Count, orphanedC.Count); + Assert.True(orphanedC.All(e => e.ParentId == null)); + + Assert.Same(root, removed.Parent); + Assert.Empty(removed.Children); // Never loaded + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + var root = LoadOptionalAkGraph(context); + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIds.Count, orphaned.Count); + Assert.True(orphaned.All(e => e.ParentId == null)); + + var orphanedC = context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIdCs.Count, orphanedC.Count); + Assert.True(orphanedC.All(e => e.ParentId == null)); + } + }); + } + + [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_dependents_with_alternate_key_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + List orphanedIdCs = null; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadOptionalAkGraph(context); + + Assert.Equal(2, root.OptionalChildrenAk.Count()); + }, + 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; + var orphaned = removed.Children.ToList(); + var orphanedC = removed.CompositeChildren.ToList(); + 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); + + 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.ForceClientNoAction + ? 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()); + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + root = LoadOptionalAkGraph(context); + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + Assert.Equal(orphanedIdCs.Count, context.Set().Count(e => orphanedIdCs.Contains(e.Id))); + } + }); + } + + [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_dependents_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + List orphanedIdCs = null; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRequiredAkGraph(context); + + Assert.Equal(2, root.RequiredChildrenAk.Count()); + }, + 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; + var cascadeRemoved = removed.Children.ToList(); + var cascadeRemovedC = removed.CompositeChildren.ToList(); + 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); + + 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.ForceClientNoAction + ? 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()); + + if (Fixture.ForceClientNoAction) + { + 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRequiredAkGraph(context); + + Assert.Single(root.RequiredChildrenAk); + 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))); + } + }); + } + + [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_dependents_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + List orphanedIds = null; + List orphanedIdCs = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredAkGraph(context); + + Assert.Equal(2, root.RequiredChildrenAk.Count()); + + var removed = root.RequiredChildrenAk.OrderBy(c => c.Id).First(); + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + var cascadeRemovedC = removed.CompositeChildren.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + Assert.Equal(2, orphanedIdCs.Count); + + var added = new RequiredAk2(); + var addedC = new RequiredComposite2(); + Add(removed.Children, added); + Add(removed.CompositeChildren, addedC); + + if (context.ChangeTracker.AutoDetectChangesEnabled) + { + context.ChangeTracker.DetectChanges(); + } + + Assert.Equal(EntityState.Unchanged, 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)); + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + 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.ForceClientNoAction) + { + 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()); + + if (Fixture.ForceClientNoAction) + { + 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.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()); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredAkGraph(context); + + Assert.Single(root.RequiredChildrenAk); + 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))); + } + }); + } + + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOne.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOne.cs new file mode 100644 index 00000000000..9aece524f9e --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOne.cs @@ -0,0 +1,2428 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class GraphUpdatesTestBase + where TFixture : GraphUpdatesTestBase.GraphUpdatesFixtureBase, new() + { + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual void Optional_One_to_one_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) + { + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = context.Set().Single(IsTheRoot); + + Assert.False(context.ChangeTracker.HasChanges()); + + root.OptionalSingle = new OptionalSingle1(); + + Assert.True(context.ChangeTracker.HasChanges()); + + Assert.Throws(() => context.SaveChanges()); + }); + } + + [ConditionalTheory] + [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(); + var new2dd = new OptionalSingle2MoreDerived(); + var new1 = new OptionalSingle1 { Single = new2 }; + var new1d = new OptionalSingle1Derived { Single = new2d }; + var new1dd = new OptionalSingle1MoreDerived { Single = new2dd }; + Root root = null; + IReadOnlyList entries = null; + OptionalSingle1 old1 = null; + OptionalSingle1Derived old1d = null; + OptionalSingle1MoreDerived old1dd = null; + OptionalSingle2 old2 = null; + OptionalSingle2Derived old2d = null; + OptionalSingle2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalGraph(context); + + old1 = root.OptionalSingle; + old1d = root.OptionalSingleDerived; + old1dd = root.OptionalSingleMoreDerived; + old2 = root.OptionalSingle.Single; + old2d = (OptionalSingle2Derived)root.OptionalSingleDerived.Single; + old2dd = (OptionalSingle2MoreDerived)root.OptionalSingleMoreDerived.Single; + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (OptionalSingle1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (OptionalSingle1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2d = (OptionalSingle2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (OptionalSingle2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.OptionalSingle = new1; + root.OptionalSingleDerived = new1d; + root.OptionalSingleMoreDerived = new1dd; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + new1d.DerivedRoot = root; + new1dd.MoreDerivedRoot = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new1.RootId = root.Id; + new1d.DerivedRootId = root.Id; + new1dd.MoreDerivedRootId = root.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.Id, new1.RootId); + Assert.Equal(root.Id, new1d.DerivedRootId); + Assert.Equal(root.Id, new1dd.MoreDerivedRootId); + Assert.Equal(new1.Id, new2.BackId); + Assert.Equal(new1d.Id, new2d.BackId); + Assert.Equal(new1dd.Id, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Equal(old1, old2.Back); + Assert.Equal(old1d, old2d.Back); + Assert.Equal(old1dd, old2dd.Back); + Assert.Null(old1.RootId); + Assert.Null(old1d.DerivedRootId); + Assert.Null(old1dd.MoreDerivedRootId); + Assert.Equal(old1.Id, old2.BackId); + Assert.Equal(old1d.Id, old2d.BackId); + Assert.Equal(old1dd.Id, old2dd.BackId); + + entries = context.ChangeTracker.Entries().ToList(); + }, + context => + { + var loadedRoot = LoadOptionalGraph(context); + + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded1d = context.Set().Single(e => e.Id == old1d.Id); + var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2d = context.Set().Single(e => e.Id == old2d.Id); + var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + + Assert.Null(loaded1.Root); + Assert.Null(loaded1d.Root); + Assert.Null(loaded1dd.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1d, loaded2d.Back); + Assert.Same(loaded1dd, loaded2dd.Back); + Assert.Null(loaded1.RootId); + Assert.Null(loaded1d.RootId); + Assert.Null(loaded1dd.RootId); + Assert.Equal(loaded1.Id, loaded2.BackId); + Assert.Equal(loaded1d.Id, loaded2d.BackId); + Assert.Equal(loaded1dd.Id, loaded2dd.BackId); + }); + } + + [ConditionalTheory] + [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; + OptionalSingle2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalGraph(context); + + old1 = root.OptionalSingle; + old2 = root.OptionalSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.OptionalSingle = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = null; + } + + Assert.False(context.Entry(root).Reference(e => e.OptionalSingle).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Same(old1, old2.Back); + Assert.Null(old1.RootId); + Assert.Equal(old1.Id, old2.BackId); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadOptionalGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + + Assert.Null(loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Null(loaded1.RootId); + Assert.Equal(loaded1.Id, loaded2.BackId); + } + }); + } + + [ConditionalTheory] + [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; + OptionalSingle1 old1 = null; + OptionalSingle2 old2 = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingRoot) + { + context.AddRange(newRoot); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalGraph(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + old1 = root.OptionalSingle; + old2 = root.OptionalSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.OptionalSingle = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = context.Entry(newRoot).Property(e => e.Id).CurrentValue; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.OptionalSingle); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Equal(newRoot.Id, old1.RootId); + Assert.Equal(old1.Id, old2.BackId); + }, + context => + { + var loadedRoot = LoadOptionalGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Equal(newRoot.Id, loaded1.RootId); + Assert.Equal(loaded1.Id, loaded2.BackId); + }); + } + + [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_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadOptionalGraph(context); + + var removed = root.OptionalSingle; + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + + Assert.Equal( + Fixture.ForceClientNoAction ? EntityState.Unchanged : EntityState.Modified, context.Entry(orphaned).State); + } + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + var root = LoadOptionalGraph(context); + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + } + }); + } + + [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; + + var removed = parent.Single; + + removedId = removed.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(parent.Single); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Same(parent, removed.Back); + }, + context => + { + var root = LoadOptionalGraph(context); + var parent = root.OptionalSingle; + + Assert.Null(parent.Single); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + }); + } + + [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_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadOptionalGraph(context).OptionalSingle; + + removedId = removed.Id; + orphanedId = removed.Single.Id; + }, + 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; + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + var root = LoadOptionalGraph(context); + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); + } + }); + } + + [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_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => root = LoadOptionalGraph(context), + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var removed = root.OptionalSingle; + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceClientNoAction + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + root = LoadOptionalGraph(context); + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + } + }); + } + + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual void Required_One_to_one_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) + { + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = context.Set().Single(IsTheRoot); + + Assert.False(context.ChangeTracker.HasChanges()); + + root.RequiredSingle = new RequiredSingle1(); + + Assert.True(context.ChangeTracker.HasChanges()); + + Assert.Throws(() => context.SaveChanges()); + }); + } + + [ConditionalTheory] + [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_required_one_to_one_changed_by_reference( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) + { + // This test is a bit strange because the relationships are PK<->PK, which means + // that an existing entity has to be deleted and then a new entity created that has + // the same key as the existing entry. In other words it is a new incarnation of the same + // entity. EF7 can't track two different instances of the same entity, so this has to be + // done in two steps. + + Root oldRoot = null; + IReadOnlyList entries = null; + RequiredSingle1 old1 = null; + RequiredSingle2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + oldRoot = LoadRequiredGraph(context); + + old1 = oldRoot.RequiredSingle; + old2 = oldRoot.RequiredSingle.Single; + }); + + var new2 = new RequiredSingle2(); + var new1 = new RequiredSingle1 { Single = new2 }; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredGraph(context); + + root.RequiredSingle = null; + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var root = LoadRequiredGraph(context); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredSingle = new1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + context.Add(new1); + new1.Root = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + context.Add(new1); + new1.Id = root.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.Id, new1.Id); + Assert.Equal(new1.Id, new2.Id); + Assert.Same(root, new1.Root); + Assert.Same(new1, new2.Back); + + Assert.Same(oldRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Equal(old1.Id, old2.Id); + + entries = context.ChangeTracker.Entries().ToList(); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var loadedRoot = LoadRequiredGraph(context); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + AssertKeys(oldRoot, loadedRoot); + AssertNavigations(loadedRoot); + } + }); + } + + [ConditionalTheory] + [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(); + var new2dd = new RequiredNonPkSingle2MoreDerived(); + var new1 = new RequiredNonPkSingle1 { Single = new2 }; + var new1d = new RequiredNonPkSingle1Derived { Single = new2d, Root = new Root() }; + var new1dd = new RequiredNonPkSingle1MoreDerived + { + Single = new2dd, + Root = new Root(), + DerivedRoot = new Root() + }; + var newRoot = new Root + { + RequiredNonPkSingle = new1, + RequiredNonPkSingleDerived = new1d, + RequiredNonPkSingleMoreDerived = new1dd + }; + Root root = null; + IReadOnlyList entries = null; + RequiredNonPkSingle1 old1 = null; + RequiredNonPkSingle1Derived old1d = null; + RequiredNonPkSingle1MoreDerived old1dd = null; + RequiredNonPkSingle2 old2 = null; + RequiredNonPkSingle2Derived old2d = null; + RequiredNonPkSingle2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new1d, new1dd, new2, new2d, new2dd); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredNonPkGraph(context); + + old1 = root.RequiredNonPkSingle; + old1d = root.RequiredNonPkSingleDerived; + old1dd = root.RequiredNonPkSingleMoreDerived; + old2 = root.RequiredNonPkSingle.Single; + old2d = (RequiredNonPkSingle2Derived)root.RequiredNonPkSingleDerived.Single; + old2dd = (RequiredNonPkSingle2MoreDerived)root.RequiredNonPkSingleMoreDerived.Single; + + context.Set().Remove(old1d); + context.Set().Remove(old1dd); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (RequiredNonPkSingle1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (RequiredNonPkSingle1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2d = (RequiredNonPkSingle2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (RequiredNonPkSingle2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + + new1d.RootId = old1d.RootId; + new1dd.RootId = old1dd.RootId; + new1dd.DerivedRootId = old1dd.DerivedRootId; + } + else + { + new1d.Root = old1d.Root; + new1dd.Root = old1dd.Root; + new1dd.DerivedRoot = old1dd.DerivedRoot; + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredNonPkSingle = new1; + root.RequiredNonPkSingleDerived = new1d; + root.RequiredNonPkSingleMoreDerived = new1dd; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + new1d.DerivedRoot = root; + new1dd.MoreDerivedRoot = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new1.RootId = root.Id; + new1d.DerivedRootId = root.Id; + new1dd.MoreDerivedRootId = root.Id; + } + + if (Fixture.ForceClientNoAction + || deleteOrphansTiming == CascadeTiming.Never) + { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? () => context.ChangeTracker.DetectChanges() + : deleteOrphansTiming == null + ? () => context.ChangeTracker.CascadeChanges() + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + + Assert.Equal( + 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()); + + Assert.Equal(root.Id, new1.RootId); + Assert.Equal(root.Id, new1d.DerivedRootId); + Assert.Equal(root.Id, new1dd.MoreDerivedRootId); + Assert.Equal(new1.Id, new2.BackId); + Assert.Equal(new1d.Id, new2d.BackId); + Assert.Equal(new1dd.Id, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Null(old2.Back); + Assert.Null(old2d.Back); + Assert.Null(old2dd.Back); + Assert.Equal(old1.Id, old2.BackId); + Assert.Equal(old1d.Id, old2d.BackId); + Assert.Equal(old1dd.Id, old2dd.BackId); + + entries = context.ChangeTracker.Entries().ToList(); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades + && deleteOrphansTiming != CascadeTiming.Never) + { + var loadedRoot = LoadRequiredNonPkGraph(context); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old1d.Id)); + Assert.False(context.Set().Any(e => e.Id == old1dd.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + Assert.False(context.Set().Any(e => e.Id == old2d.Id)); + Assert.False(context.Set().Any(e => e.Id == old2dd.Id)); + } + }); + } + + [ConditionalTheory] + [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( + ChangeMechanism changeMechanism, + CascadeTiming? deleteOrphansTiming) + { + Root root = null; + RequiredSingle1 old1 = null; + RequiredSingle2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredGraph(context); + + old1 = root.RequiredSingle; + old2 = root.RequiredSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredSingle = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + Assert.False(context.Entry(root).Reference(e => e.RequiredSingle).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Equal(old1.Id, old2.Id); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var loadedRoot = LoadRequiredGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + } + }); + } + + [ConditionalTheory] + [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; + RequiredNonPkSingle2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredNonPkGraph(context); + + old1 = root.RequiredNonPkSingle; + old2 = root.RequiredNonPkSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredNonPkSingle = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + if (Fixture.ForceClientNoAction + || deleteOrphansTiming == CascadeTiming.Never) + { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? () => context.ChangeTracker.DetectChanges() + : deleteOrphansTiming == null + ? () => context.ChangeTracker.CascadeChanges() + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + + Assert.Equal( + 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()); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Equal(old1.Id, old2.BackId); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades + && deleteOrphansTiming != CascadeTiming.Never) + { + var loadedRoot = LoadRequiredNonPkGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + } + }); + } + + [ConditionalTheory] + [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(); + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingRoot) + { + context.AddRange(newRoot); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredGraph(context); + + Assert.False(context.ChangeTracker.HasChanges()); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + Assert.Equal( + CoreStrings.KeyReadOnly("Id", typeof(RequiredSingle1).Name), + Assert.Throws( + () => + { + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.RequiredSingle = root.RequiredSingle; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + root.RequiredSingle.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + root.RequiredSingle.Id = newRoot.Id; + } + + newRoot.RequiredSingle = root.RequiredSingle; + + context.SaveChanges(); + }).Message); + }); + } + + [ConditionalTheory] + [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; + RequiredNonPkSingle1 old1 = null; + RequiredNonPkSingle2 old2 = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingRoot) + { + context.AddRange(newRoot); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredNonPkGraph(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + old1 = root.RequiredNonPkSingle; + old2 = root.RequiredNonPkSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.RequiredNonPkSingle = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = context.Entry(newRoot).Property(e => e.Id).CurrentValue; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Equal(newRoot.Id, old1.RootId); + Assert.Equal(old1.Id, old2.BackId); + }, + context => + { + var loadedRoot = LoadRequiredNonPkGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Equal(newRoot.Id, loaded1.RootId); + Assert.Equal(loaded1.Id, loaded2.BackId); + }); + } + + [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_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredGraph(context); + + var removed = root.RequiredSingle; + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + + Assert.Equal( + Fixture.ForceClientNoAction ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); + } + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredGraph(context); + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + + var removed = parent.Single; + + removedId = removed.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(parent.Single); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Same(parent, removed.Back); + }, + context => + { + var root = LoadRequiredGraph(context); + var parent = root.RequiredSingle; + + Assert.Null(parent.Single); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + }); + } + + [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_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredNonPkGraph(context); + + var removed = root.RequiredNonPkSingle; + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + + Assert.Equal( + Fixture.ForceClientNoAction ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); + } + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Null(root.RequiredNonPkSingle); + + 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); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredNonPkGraph(context); + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + + var removed = parent.Single; + + removedId = removed.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(parent.Single); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Same(parent, removed.Back); + }, + context => + { + var root = LoadRequiredNonPkGraph(context); + var parent = root.RequiredNonPkSingle; + + Assert.Null(parent.Single); + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + }); + } + + [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_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadRequiredGraph(context).RequiredSingle; + + removedId = removed.Id; + orphanedId = removed.Single.Id; + }, + 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; + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction + || Fixture.NoStoreCascades) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var root = LoadRequiredGraph(context); + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadRequiredNonPkGraph(context).RequiredNonPkSingle; + + removedId = removed.Id; + orphanedId = removed.Single.Id; + }, + 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; + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction + || Fixture.NoStoreCascades) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(root.RequiredNonPkSingle); + + 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); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var root = LoadRequiredNonPkGraph(context); + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => root = LoadRequiredGraph(context), + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var removed = root.RequiredSingle; + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceClientNoAction + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => root = LoadRequiredGraph(context), + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => root = LoadRequiredNonPkGraph(context), + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var removed = root.RequiredNonPkSingle; + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceClientNoAction + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRequiredNonPkGraph(context); + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredGraph(context); + + var removed = root.RequiredSingle; + + removedId = removed.Id; + var orphaned = removed.Single; + + // Since we're pretending this isn't in the database, make it really not in the database + context.Entry(orphaned).State = EntityState.Deleted; + context.SaveChanges(); + + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + + removed.Single = orphaned; + context.ChangeTracker.DetectChanges(); + orphanedId = orphaned.Id; + + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceClientNoAction + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredGraph(context); + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredNonPkGraph(context); + + var removed = root.RequiredNonPkSingle; + + removedId = removed.Id; + var orphaned = removed.Single; + + // Since we're pretending this isn't in the database, make it really not in the database + context.Entry(orphaned).State = EntityState.Deleted; + context.SaveChanges(); + + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + + removed.Single = orphaned; + context.ChangeTracker.DetectChanges(); + context.Entry(orphaned).State = EntityState.Added; + orphanedId = orphaned.Id; + + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceClientNoAction + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredNonPkGraph(context); + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOneAk.cs b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOneAk.cs new file mode 100644 index 00000000000..c8342b0ad4e --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/GraphUpdatesTestBaseOneToOneAk.cs @@ -0,0 +1,2622 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class GraphUpdatesTestBase + where TFixture : GraphUpdatesTestBase.GraphUpdatesFixtureBase, new() + { + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual void Optional_One_to_one_with_AK_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) + { + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = context.Set().Single(IsTheRoot); + + Assert.False(context.ChangeTracker.HasChanges()); + + root.OptionalSingleAk = new OptionalSingleAk1(); + + Assert.True(context.ChangeTracker.HasChanges()); + + Assert.Throws(() => context.SaveChanges()); + }); + } + + [ConditionalTheory] + [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 { AlternateId = Guid.NewGuid() }; + var new2d = new OptionalSingleAk2Derived { AlternateId = Guid.NewGuid() }; + var new2dd = new OptionalSingleAk2MoreDerived { AlternateId = Guid.NewGuid() }; + var new2c = new OptionalSingleComposite2(); + var new1 = new OptionalSingleAk1 + { + AlternateId = Guid.NewGuid(), + Single = new2, + SingleComposite = new2c + }; + var new1d = new OptionalSingleAk1Derived { AlternateId = Guid.NewGuid(), Single = new2d }; + var new1dd = new OptionalSingleAk1MoreDerived { AlternateId = Guid.NewGuid(), Single = new2dd }; + Root root = null; + IReadOnlyList entries = null; + OptionalSingleAk1 old1 = null; + OptionalSingleAk1Derived old1d = null; + OptionalSingleAk1MoreDerived old1dd = null; + OptionalSingleAk2 old2 = null; + OptionalSingleComposite2 old2c = null; + OptionalSingleAk2Derived old2d = null; + OptionalSingleAk2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalAkGraph(context); + + old1 = root.OptionalSingleAk; + old1d = root.OptionalSingleAkDerived; + old1dd = root.OptionalSingleAkMoreDerived; + old2 = root.OptionalSingleAk.Single; + old2c = root.OptionalSingleAk.SingleComposite; + old2d = (OptionalSingleAk2Derived)root.OptionalSingleAkDerived.Single; + old2dd = (OptionalSingleAk2MoreDerived)root.OptionalSingleAkMoreDerived.Single; + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (OptionalSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (OptionalSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2c = context.Set().Single(e => e.Id == new2c.Id); + new2d = (OptionalSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (OptionalSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.OptionalSingleAk = new1; + root.OptionalSingleAkDerived = new1d; + root.OptionalSingleAkMoreDerived = new1dd; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + new1d.DerivedRoot = root; + new1dd.MoreDerivedRoot = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new1.RootId = root.AlternateId; + new1d.DerivedRootId = root.AlternateId; + new1dd.MoreDerivedRootId = root.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(root.AlternateId, new1d.DerivedRootId); + Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1.Id, new2c.BackId); + Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.BackId); + Assert.Equal(new1dd.AlternateId, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1, new2c.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(old1d, old2d.Back); + Assert.Equal(old1dd, old2dd.Back); + Assert.Null(old1.RootId); + Assert.Null(old1d.DerivedRootId); + Assert.Null(old1dd.MoreDerivedRootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + Assert.Equal(old1d.AlternateId, old2d.BackId); + Assert.Equal(old1dd.AlternateId, old2dd.BackId); + + entries = context.ChangeTracker.Entries().ToList(); + }, + context => + { + var loadedRoot = LoadOptionalAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded1d = context.Set().Single(e => e.Id == old1d.Id); + var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2d = context.Set().Single(e => e.Id == old2d.Id); + var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + + Assert.Null(loaded1.Root); + Assert.Null(loaded1d.Root); + Assert.Null(loaded1dd.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Same(loaded1d, loaded2d.Back); + Assert.Same(loaded1dd, loaded2dd.Back); + Assert.Null(loaded1.RootId); + Assert.Null(loaded1d.RootId); + Assert.Null(loaded1dd.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); + Assert.Equal(loaded1d.AlternateId, loaded2d.BackId); + Assert.Equal(loaded1dd.AlternateId, loaded2dd.BackId); + }); + } + + [ConditionalFact] + public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store() + { + var new2 = new OptionalSingleAk2 { AlternateId = Guid.NewGuid() }; + var new2d = new OptionalSingleAk2Derived { AlternateId = Guid.NewGuid() }; + var new2dd = new OptionalSingleAk2MoreDerived { AlternateId = Guid.NewGuid() }; + var new2c = new OptionalSingleComposite2(); + var new1 = new OptionalSingleAk1 + { + AlternateId = Guid.NewGuid(), + Single = new2, + SingleComposite = new2c + }; + var new1d = new OptionalSingleAk1Derived { AlternateId = Guid.NewGuid(), Single = new2d }; + var new1dd = new OptionalSingleAk1MoreDerived { AlternateId = Guid.NewGuid(), Single = new2dd }; + Root root = null; + IReadOnlyList entries = null; + OptionalSingleAk1 old1 = null; + OptionalSingleAk1Derived old1d = null; + OptionalSingleAk1MoreDerived old1dd = null; + OptionalSingleAk2 old2 = null; + OptionalSingleComposite2 old2c = null; + OptionalSingleAk2Derived old2d = null; + OptionalSingleAk2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadOptionalAkGraph(context); + + old1 = root.OptionalSingleAk; + old1d = root.OptionalSingleAkDerived; + old1dd = root.OptionalSingleAkMoreDerived; + old2 = root.OptionalSingleAk.Single; + old2c = root.OptionalSingleAk.SingleComposite; + old2d = (OptionalSingleAk2Derived)root.OptionalSingleAkDerived.Single; + old2dd = (OptionalSingleAk2MoreDerived)root.OptionalSingleAkMoreDerived.Single; + + using (var context2 = CreateContext()) + { + UseTransaction(context2.Database, context.Database.CurrentTransaction); + var root2 = LoadOptionalAkGraph(context2); + + context2.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); + root2.OptionalSingleAk = new1; + root2.OptionalSingleAkDerived = new1d; + root2.OptionalSingleAkMoreDerived = new1dd; + + Assert.True(context2.ChangeTracker.HasChanges()); + + context2.SaveChanges(); + + Assert.False(context2.ChangeTracker.HasChanges()); + } + + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (OptionalSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (OptionalSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2c = context.Set().Single(e => e.Id == new2c.Id); + new2d = (OptionalSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (OptionalSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(root.AlternateId, new1d.DerivedRootId); + Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1.Id, new2c.BackId); + Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.BackId); + Assert.Equal(new1dd.AlternateId, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1, new2c.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(old1d, old2d.Back); + Assert.Equal(old1dd, old2dd.Back); + Assert.Null(old1.RootId); + Assert.Null(old1d.DerivedRootId); + Assert.Null(old1dd.MoreDerivedRootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + Assert.Equal(old1d.AlternateId, old2d.BackId); + Assert.Equal(old1dd.AlternateId, old2dd.BackId); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(root.AlternateId, new1d.DerivedRootId); + Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1.Id, new2c.BackId); + Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.BackId); + Assert.Equal(new1dd.AlternateId, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1, new2c.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(old1d, old2d.Back); + Assert.Equal(old1dd, old2dd.Back); + Assert.Null(old1.RootId); + Assert.Null(old1d.DerivedRootId); + Assert.Null(old1dd.MoreDerivedRootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + Assert.Equal(old1d.AlternateId, old2d.BackId); + Assert.Equal(old1dd.AlternateId, old2dd.BackId); + + entries = context.ChangeTracker.Entries().ToList(); + }, + context => + { + var loadedRoot = LoadOptionalAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded1d = context.Set().Single(e => e.Id == old1d.Id); + var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2d = context.Set().Single(e => e.Id == old2d.Id); + var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + + Assert.Null(loaded1.Root); + Assert.Null(loaded1d.Root); + Assert.Null(loaded1dd.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Same(loaded1d, loaded2d.Back); + Assert.Same(loaded1dd, loaded2dd.Back); + Assert.Null(loaded1.RootId); + Assert.Null(loaded1d.RootId); + Assert.Null(loaded1dd.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); + Assert.Equal(loaded1d.AlternateId, loaded2d.BackId); + Assert.Equal(loaded1dd.AlternateId, loaded2dd.BackId); + }); + } + + [ConditionalTheory] + [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; + OptionalSingleAk2 old2 = null; + OptionalSingleComposite2 old2c = null; + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalAkGraph(context); + + old1 = root.OptionalSingleAk; + old2 = root.OptionalSingleAk.Single; + old2c = root.OptionalSingleAk.SingleComposite; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.OptionalSingleAk = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = null; + } + + Assert.False(context.Entry(root).Reference(e => e.OptionalSingleAk).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Null(old1.RootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadOptionalAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + Assert.Null(loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Null(loaded1.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); + } + }); + } + + [ConditionalTheory] + [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 { AlternateId = Guid.NewGuid() }; + Root root = null; + OptionalSingleAk1 old1 = null; + OptionalSingleAk2 old2 = null; + OptionalSingleComposite2 old2c = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingRoot) + { + context.Add(newRoot); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadOptionalAkGraph(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + old1 = root.OptionalSingleAk; + old2 = root.OptionalSingleAk.Single; + old2c = root.OptionalSingleAk.SingleComposite; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.OptionalSingleAk = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = newRoot.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.OptionalSingleAk); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(newRoot.AlternateId, old1.RootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + }, + context => + { + var loadedRoot = LoadOptionalAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Equal(newRoot.AlternateId, loaded1.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); + }); + } + + [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_with_alternate_key_are_orphaned( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadOptionalAkGraph(context); + + var removed = root.OptionalSingleAk; + + removedId = removed.Id; + var orphaned = removed.Single; + var orphanedC = removed.SingleComposite; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; + + context.Remove(removed); + + 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.ForceClientNoAction) + { + 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.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + var root = LoadOptionalAkGraph(context); + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); + } + }); + } + + [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_with_alternate_key_are_orphaned_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadOptionalAkGraph(context).OptionalSingleAk; + + removedId = removed.Id; + orphanedId = removed.Single.Id; + orphanedIdC = removed.SingleComposite.Id; + }, + 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; + var orphaned = removed.Single; + + context.Remove(removed); + + // Cannot have SET NULL action in the store because one of the FK columns + // is not nullable, so need to do this on the EF side. + context.Set().Single(e => e.Id == orphanedIdC).BackId = null; + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); + Assert.Null(context.Set().Single(e => e.Id == orphanedIdC).BackId); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + var root = LoadOptionalAkGraph(context); + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); + Assert.Null(context.Set().Single(e => e.Id == orphanedIdC).BackId); + } + }); + } + + [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_with_alternate_key_are_orphaned_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + var orphanedIdC = 0; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => root = LoadOptionalAkGraph(context), + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var removed = root.OptionalSingleAk; + + removedId = removed.Id; + var orphaned = removed.Single; + var orphanedC = removed.SingleComposite; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).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.ForceClientNoAction + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction) + { + root = LoadOptionalAkGraph(context); + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); + } + }); + } + + [ConditionalTheory] + [InlineData(CascadeTiming.OnSaveChanges)] + [InlineData(CascadeTiming.Immediate)] + [InlineData(CascadeTiming.Never)] + [InlineData(null)] + public virtual void Required_One_to_one_with_AK_relationships_are_one_to_one( + CascadeTiming? deleteOrphansTiming) + { + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = context.Set().Single(IsTheRoot); + + Assert.False(context.ChangeTracker.HasChanges()); + + root.RequiredSingleAk = new RequiredSingleAk1(); + + Assert.True(context.ChangeTracker.HasChanges()); + + Assert.Throws(() => context.SaveChanges()); + }); + } + + [ConditionalTheory] + [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, + CascadeTiming? deleteOrphansTiming) + { + var new2 = new RequiredSingleAk2 { AlternateId = Guid.NewGuid() }; + var new2c = new RequiredSingleComposite2(); + var new1 = new RequiredSingleAk1 + { + AlternateId = Guid.NewGuid(), + Single = new2, + SingleComposite = new2c + }; + var newRoot = new Root { AlternateId = Guid.NewGuid(), RequiredSingleAk = new1 }; + Root root = null; + IReadOnlyList entries = null; + RequiredSingleAk1 old1 = null; + RequiredSingleAk2 old2 = null; + RequiredSingleComposite2 old2c = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new2, new2c); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredAkGraph(context); + + old1 = root.RequiredSingleAk; + old2 = root.RequiredSingleAk.Single; + old2c = root.RequiredSingleAk.SingleComposite; + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2c = context.Set().Single(e => e.Id == new2c.Id); + } + else + { + context.AddRange(new1, new2, new2c); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredSingleAk = new1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + if (Fixture.ForceClientNoAction + || deleteOrphansTiming == CascadeTiming.Never) + { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? () => context.ChangeTracker.DetectChanges() + : deleteOrphansTiming == null + ? () => context.ChangeTracker.CascadeChanges() + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + + Assert.Equal( + 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()); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1.Id, new2c.BackId); + Assert.Equal(new1.AlternateId, new2c.BackAlternateId); + Assert.Same(root, new1.Root); + Assert.Same(new1, new2.Back); + Assert.Same(new1, new2c.Back); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Null(old2c.Back); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.BackAlternateId); + + entries = context.ChangeTracker.Entries().ToList(); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades + && deleteOrphansTiming != CascadeTiming.Never) + { + var loadedRoot = LoadRequiredAkGraph(context); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + Assert.False(context.Set().Any(e => e.Id == old2c.Id)); + } + }); + } + + [ConditionalTheory] + [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, + CascadeTiming? deleteOrphansTiming) + { + var new2 = new RequiredNonPkSingleAk2 { AlternateId = Guid.NewGuid() }; + var new2d = new RequiredNonPkSingleAk2Derived { AlternateId = Guid.NewGuid() }; + var new2dd = new RequiredNonPkSingleAk2MoreDerived { AlternateId = Guid.NewGuid() }; + var new1 = new RequiredNonPkSingleAk1 { AlternateId = Guid.NewGuid(), Single = new2 }; + var new1d = new RequiredNonPkSingleAk1Derived + { + AlternateId = Guid.NewGuid(), + Single = new2d, + Root = new Root() + }; + var new1dd = new RequiredNonPkSingleAk1MoreDerived + { + AlternateId = Guid.NewGuid(), + Single = new2dd, + Root = new Root(), + DerivedRoot = new Root() + }; + var newRoot = new Root + { + AlternateId = Guid.NewGuid(), + RequiredNonPkSingleAk = new1, + RequiredNonPkSingleAkDerived = new1d, + RequiredNonPkSingleAkMoreDerived = new1dd + }; + Root root = null; + IReadOnlyList entries = null; + RequiredNonPkSingleAk1 old1 = null; + RequiredNonPkSingleAk1Derived old1d = null; + RequiredNonPkSingleAk1MoreDerived old1dd = null; + RequiredNonPkSingleAk2 old2 = null; + RequiredNonPkSingleAk2Derived old2d = null; + RequiredNonPkSingleAk2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new1d, new1dd, new2, new2d, new2dd); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredNonPkAkGraph(context); + + old1 = root.RequiredNonPkSingleAk; + old1d = root.RequiredNonPkSingleAkDerived; + old1dd = root.RequiredNonPkSingleAkMoreDerived; + old2 = root.RequiredNonPkSingleAk.Single; + old2d = (RequiredNonPkSingleAk2Derived)root.RequiredNonPkSingleAkDerived.Single; + old2dd = (RequiredNonPkSingleAk2MoreDerived)root.RequiredNonPkSingleAkMoreDerived.Single; + + context.Set().Remove(old1d); + context.Set().Remove(old1dd); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (RequiredNonPkSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (RequiredNonPkSingleAk1MoreDerived)context.Set() + .Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2d = (RequiredNonPkSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (RequiredNonPkSingleAk2MoreDerived)context.Set() + .Single(e => e.Id == new2dd.Id); + + new1d.RootId = old1d.RootId; + new1dd.RootId = old1dd.RootId; + new1dd.DerivedRootId = old1dd.DerivedRootId; + } + else + { + new1d.Root = old1d.Root; + new1dd.Root = old1dd.Root; + new1dd.DerivedRoot = old1dd.DerivedRoot; + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredNonPkSingleAk = new1; + root.RequiredNonPkSingleAkDerived = new1d; + root.RequiredNonPkSingleAkMoreDerived = new1dd; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + new1d.DerivedRoot = root; + new1dd.MoreDerivedRoot = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new1.RootId = root.AlternateId; + new1d.DerivedRootId = root.AlternateId; + new1dd.MoreDerivedRootId = root.AlternateId; + } + + if (Fixture.ForceClientNoAction + || deleteOrphansTiming == CascadeTiming.Never) + { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? () => context.ChangeTracker.DetectChanges() + : deleteOrphansTiming == null + ? () => context.ChangeTracker.CascadeChanges() + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + + Assert.Equal( + 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()); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(root.AlternateId, new1d.DerivedRootId); + Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1d.AlternateId, new2d.BackId); + Assert.Equal(new1dd.AlternateId, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Null(old2.Back); + Assert.Null(old2d.Back); + Assert.Null(old2dd.Back); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1d.AlternateId, old2d.BackId); + Assert.Equal(old1dd.AlternateId, old2dd.BackId); + + entries = context.ChangeTracker.Entries().ToList(); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades + && deleteOrphansTiming != CascadeTiming.Never) + { + var loadedRoot = LoadRequiredNonPkAkGraph(context); + + AssertEntries(entries, context.ChangeTracker.Entries().ToList()); + AssertKeys(root, loadedRoot); + AssertNavigations(loadedRoot); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old1d.Id)); + Assert.False(context.Set().Any(e => e.Id == old1dd.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + Assert.False(context.Set().Any(e => e.Id == old2d.Id)); + Assert.False(context.Set().Any(e => e.Id == old2dd.Id)); + } + }); + } + + [ConditionalTheory] + [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; + RequiredSingleAk2 old2 = null; + RequiredSingleComposite2 old2c = null; + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredAkGraph(context); + + old1 = root.RequiredSingleAk; + old2 = root.RequiredSingleAk.Single; + old2c = root.RequiredSingleAk.SingleComposite; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredSingleAk = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + if (Fixture.ForceClientNoAction + || deleteOrphansTiming == CascadeTiming.Never) + { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? () => context.ChangeTracker.DetectChanges() + : deleteOrphansTiming == null + ? () => context.ChangeTracker.CascadeChanges() + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + + Assert.Equal( + 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()); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Null(old2c.Back); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.BackAlternateId); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades + && deleteOrphansTiming != CascadeTiming.Never) + { + var loadedRoot = LoadRequiredAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + Assert.False(context.Set().Any(e => e.Id == old2c.Id)); + } + }); + } + + [ConditionalTheory] + [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; + RequiredNonPkSingleAk2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredNonPkAkGraph(context); + + old1 = root.RequiredNonPkSingleAk; + old2 = root.RequiredNonPkSingleAk.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredNonPkSingleAk = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + if (Fixture.ForceClientNoAction + || deleteOrphansTiming == CascadeTiming.Never) + { + var testCode = deleteOrphansTiming == CascadeTiming.Immediate + ? () => context.ChangeTracker.DetectChanges() + : deleteOrphansTiming == null + ? () => context.ChangeTracker.CascadeChanges() + : (Action)(() => context.SaveChanges()); + + var message = Assert.Throws(testCode).Message; + + Assert.Equal( + 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()); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Equal(old1.AlternateId, old2.BackId); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades + && deleteOrphansTiming != CascadeTiming.Never) + { + var loadedRoot = LoadRequiredNonPkAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + } + }); + } + + [ConditionalTheory] + [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 { AlternateId = Guid.NewGuid() }; + Root root = null; + RequiredSingleAk1 old1 = null; + RequiredSingleAk2 old2 = null; + RequiredSingleComposite2 old2c = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingRoot) + { + context.Add(newRoot); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredAkGraph(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + old1 = root.RequiredSingleAk; + old2 = root.RequiredSingleAk.Single; + old2c = root.RequiredSingleAk.SingleComposite; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.RequiredSingleAk = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = newRoot.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.RequiredSingleAk); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(newRoot.AlternateId, old1.RootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.BackAlternateId); + }, + context => + { + var loadedRoot = LoadRequiredAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Equal(newRoot.AlternateId, loaded1.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.BackAlternateId); + }); + } + + [ConditionalTheory] + [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 { AlternateId = Guid.NewGuid() }; + Root root = null; + RequiredNonPkSingleAk1 old1 = null; + RequiredNonPkSingleAk2 old2 = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (useExistingRoot) + { + context.Add(newRoot); + context.SaveChanges(); + } + }, + context => + { + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + root = LoadRequiredNonPkAkGraph(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + old1 = root.RequiredNonPkSingleAk; + old2 = root.RequiredNonPkSingleAk.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.RequiredNonPkSingleAk = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = newRoot.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Equal(newRoot.AlternateId, old1.RootId); + Assert.Equal(old1.AlternateId, old2.BackId); + }, + context => + { + var loadedRoot = LoadRequiredNonPkAkGraph(context); + + AssertKeys(root, loadedRoot); + AssertPossiblyNullNavigations(loadedRoot); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Equal(newRoot.AlternateId, loaded1.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + }); + } + + [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_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredAkGraph(context); + + var removed = root.RequiredSingleAk; + + removedId = removed.Id; + var orphaned = removed.Single; + var orphanedC = removed.SingleComposite; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; + + context.Remove(removed); + + 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.ForceClientNoAction) + { + 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.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); + + Assert.Null(root.RequiredSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredAkGraph(context); + + 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)); + } + }); + } + + [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_with_alternate_key_are_cascade_deleted( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredNonPkAkGraph(context); + + var removed = root.RequiredNonPkSingleAk; + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + + Assert.Equal( + Fixture.ForceClientNoAction ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); + } + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Null(root.RequiredNonPkSingleAk); + + 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); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredNonPkAkGraph(context); + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadRequiredAkGraph(context).RequiredSingleAk; + + removedId = removed.Id; + orphanedId = removed.Single.Id; + orphanedIdC = removed.SingleComposite.Id; + }, + 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; + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction + || Fixture.NoStoreCascades) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(root.RequiredSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var root = LoadRequiredAkGraph(context); + + 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)); + } + }); + } + + [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_with_alternate_key_are_cascade_deleted_in_store( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var removed = LoadRequiredNonPkAkGraph(context).RequiredNonPkSingleAk; + + removedId = removed.Id; + orphanedId = removed.Single.Id; + }, + 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; + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == null) + { + context.ChangeTracker.CascadeChanges(); + } + + if (Fixture.ForceClientNoAction + || Fixture.NoStoreCascades) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(root.RequiredNonPkSingleAk); + + 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); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && !Fixture.NoStoreCascades) + { + var root = LoadRequiredNonPkAkGraph(context); + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + var orphanedIdC = 0; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => root = LoadRequiredAkGraph(context), + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var removed = root.RequiredSingleAk; + + removedId = removed.Id; + var orphaned = removed.Single; + var orphanedC = removed.SingleComposite; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).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.ForceClientNoAction + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.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); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRequiredAkGraph(context); + + 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)); + } + }); + } + + [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_with_alternate_key_are_cascade_deleted_starting_detached( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + Root root = null; + + ExecuteWithStrategyInTransaction( + context => root = LoadRequiredNonPkAkGraph(context), + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var removed = root.RequiredNonPkSingleAk; + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceClientNoAction + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRequiredNonPkAkGraph(context); + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredAkGraph(context); + + var removed = root.RequiredSingleAk; + removedId = removed.Id; + + var orphaned = removed.Single; + var orphanedC = removed.SingleComposite; + + // Since we're pretending these aren't in the database, make them really not in the database + context.Entry(orphaned).State = EntityState.Deleted; + context.Entry(orphanedC).State = EntityState.Deleted; + context.SaveChanges(); + + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); + + removed.Single = orphaned; + removed.SingleComposite = orphanedC; + context.ChangeTracker.DetectChanges(); + context.Entry(orphaned).State = EntityState.Added; + context.Entry(orphanedC).State = EntityState.Added; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; + + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + Assert.Equal(EntityState.Added, context.Entry(orphanedC).State); + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).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.ForceClientNoAction + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + Assert.Equal(expectedState, context.Entry(orphanedC).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.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); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredAkGraph(context); + + 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)); + } + }); + } + + [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_with_alternate_key_are_cascade_detached_when_Added( + CascadeTiming? cascadeDeleteTiming, + CascadeTiming? deleteOrphansTiming) + { + var removedId = 0; + var orphanedId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; + + var root = LoadRequiredNonPkAkGraph(context); + + var removed = root.RequiredNonPkSingleAk; + + removedId = removed.Id; + var orphaned = removed.Single; + + // Since we're pretending this isn't in the database, make it really not in the database + context.Entry(orphaned).State = EntityState.Deleted; + context.SaveChanges(); + + Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); + + removed.Single = orphaned; + context.ChangeTracker.DetectChanges(); + context.Entry(orphaned).State = EntityState.Added; + orphanedId = orphaned.Id; + + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.Remove(removed); + + Assert.Equal(EntityState.Deleted, context.Entry(removed).State); + + if (cascadeDeleteTiming == null) + { + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.ChangeTracker.CascadeChanges(); + } + + var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate + || cascadeDeleteTiming == null) + && !Fixture.ForceClientNoAction + ? EntityState.Detached + : EntityState.Added; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (Fixture.ForceClientNoAction) + { + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (!Fixture.ForceClientNoAction + && cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRequiredNonPkAkGraph(context); + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/ProxyGraphUpdatesFixtureBase.cs b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesFixtureBase.cs similarity index 99% rename from test/EFCore.Specification.Tests/ProxyGraphUpdatesFixtureBase.cs rename to test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesFixtureBase.cs index 199fb2a8e15..3e112d23211 100644 --- a/test/EFCore.Specification.Tests/ProxyGraphUpdatesFixtureBase.cs +++ b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesFixtureBase.cs @@ -10,11 +10,17 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; namespace Microsoft.EntityFrameworkCore { - public abstract partial class ProxyGraphUpdatesTestBase + public abstract partial class ProxyGraphUpdatesTestBase : IClassFixture + where TFixture : ProxyGraphUpdatesTestBase.ProxyGraphUpdatesFixtureBase, new() { + protected ProxyGraphUpdatesTestBase(TFixture fixture) => Fixture = fixture; + + protected TFixture Fixture { get; } + protected abstract bool DoesLazyLoading { get; } protected abstract bool DoesChangeTracking { get; } diff --git a/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseMiscellaneous.cs b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseMiscellaneous.cs new file mode 100644 index 00000000000..06aea751390 --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseMiscellaneous.cs @@ -0,0 +1,81 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class ProxyGraphUpdatesTestBase + where TFixture : ProxyGraphUpdatesTestBase.ProxyGraphUpdatesFixtureBase, new() + { + [ConditionalFact] + public virtual void No_fixup_to_Deleted_entities() + { + using var context = CreateContext(); + + var root = LoadRoot(context); + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + } + + var existing = root.OptionalChildren.OrderBy(e => e.Id).First(); + + existing.Parent = null; + existing.ParentId = null; + ((ICollection)root.OptionalChildren).Remove(existing); + + context.Entry(existing).State = EntityState.Deleted; + + var queried = context.Set().ToList(); + + Assert.Null(existing.Parent); + Assert.Null(existing.ParentId); + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(existing, root.OptionalChildren); + + Assert.Equal(2, queried.Count); + Assert.Contains(existing, queried); + } + + [ConditionalFact] + public virtual void Sometimes_not_calling_DetectChanges_when_required_does_not_throw_for_null_ref() + { + ExecuteWithStrategyInTransaction( + context => + { + var dependent = context.Set().Single(); + + dependent.BadCustomerId = null; + + var principal = context.Set().Single(); + + principal.Status++; + + Assert.Null(dependent.BadCustomerId); + Assert.Null(dependent.BadCustomer); + Assert.Empty(principal.BadOrders); + + context.SaveChanges(); + + Assert.Null(dependent.BadCustomerId); + Assert.Null(dependent.BadCustomer); + Assert.Empty(principal.BadOrders); + }, + context => + { + var dependent = context.Set().Single(); + var principal = context.Set().Single(); + + Assert.Null(dependent.BadCustomerId); + Assert.Null(dependent.BadCustomer); + Assert.Empty(principal.BadOrders); + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToMany.cs b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToMany.cs new file mode 100644 index 00000000000..ead633a9d19 --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToMany.cs @@ -0,0 +1,1392 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Internal; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class ProxyGraphUpdatesTestBase : IClassFixture + where TFixture : ProxyGraphUpdatesTestBase.ProxyGraphUpdatesFixtureBase, new() + { + [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) + { + Optional1 new1 = null; + Optional1Derived new1d = null; + Optional1MoreDerived new1dd = null; + Optional2 new2a = null; + Optional2 new2b = null; + Optional2Derived new2d = null; + Optional2MoreDerived new2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + new1 = context.CreateProxy(); + new1d = context.CreateProxy(); + new1dd = context.CreateProxy(); + new2a = context.CreateProxy(); + new2b = context.CreateProxy(); + new2d = context.CreateProxy(); + new2dd = context.CreateProxy(); + + if (useExistingEntities) + { + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + } + + var existing = root.OptionalChildren.OrderBy(e => e.Id).First(); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (Optional1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (Optional1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2a = context.Set().Single(e => e.Id == new2a.Id); + new2b = context.Set().Single(e => e.Id == new2b.Id); + new2d = (Optional2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (Optional2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Add(existing.Children, new2a); + Add(existing.Children, new2b); + Add(new1d.Children, new2d); + Add(new1dd.Children, new2dd); + Add(root.OptionalChildren, new1); + Add(root.OptionalChildren, new1d); + Add(root.OptionalChildren, new1dd); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new2a.Parent = existing; + new2b.Parent = existing; + new2d.Parent = new1d; + new2dd.Parent = new1dd; + new1.Parent = root; + new1d.Parent = root; + new1dd.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new2a.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; + new2b.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; + new2d.ParentId = context.Entry(new1d).Property(e => e.Id).CurrentValue; + new2dd.ParentId = context.Entry(new1dd).Property(e => e.Id).CurrentValue; + new1.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + new1d.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + new1dd.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Contains(new2a, existing.Children); + Assert.Contains(new2b, existing.Children); + Assert.Contains(new2d, new1d.Children); + Assert.Contains(new2dd, new1dd.Children); + Assert.Contains(new1, root.OptionalChildren); + Assert.Contains(new1d, root.OptionalChildren); + Assert.Contains(new1dd, root.OptionalChildren); + + Assert.Same(existing, new2a.Parent); + Assert.Same(existing, new2b.Parent); + Assert.Same(new1d, new2d.Parent); + Assert.Same(new1dd, new2dd.Parent); + Assert.Same(root, existing.Parent); + Assert.Same(root, new1d.Parent); + Assert.Same(root, new1dd.Parent); + + Assert.Equal(existing.Id, new2a.ParentId); + Assert.Equal(existing.Id, new2b.ParentId); + Assert.Equal(new1d.Id, new2d.ParentId); + Assert.Equal(new1dd.Id, new2dd.ParentId); + Assert.Equal(root.Id, existing.ParentId); + Assert.Equal(root.Id, new1d.ParentId); + Assert.Equal(root.Id, new1dd.ParentId); + }); + } + + [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) + { + Root newRoot; + Required1 new1 = null; + Required1Derived new1d = null; + Required1MoreDerived new1dd = null; + Required2 new2a = null; + Required2 new2b = null; + Required2Derived new2d = null; + Required2MoreDerived new2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + newRoot = context.CreateProxy(); + new1 = context.CreateProxy(e => e.Parent = newRoot); + new1d = context.CreateProxy(e => e.Parent = newRoot); + new1dd = context.CreateProxy(e => e.Parent = newRoot); + new2a = context.CreateProxy(e => e.Parent = new1); + new2b = context.CreateProxy(e => e.Parent = new1); + new2d = context.CreateProxy(e => e.Parent = new1); + new2dd = context.CreateProxy(e => e.Parent = new1); + + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new1d, new1dd, new2a, new2d, new2dd, new2b); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + var existing = root.RequiredChildren.OrderBy(e => e.Id).First(); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (Required1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (Required1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2a = context.Set().Single(e => e.Id == new2a.Id); + new2b = context.Set().Single(e => e.Id == new2b.Id); + new2d = (Required2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (Required2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + new1.Parent = null; + new1d.Parent = null; + new1dd.Parent = null; + + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Add(existing.Children, new2a); + Add(existing.Children, new2b); + Add(new1d.Children, new2d); + Add(new1dd.Children, new2dd); + Add(root.RequiredChildren, new1); + Add(root.RequiredChildren, new1d); + Add(root.RequiredChildren, new1dd); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new2a.Parent = existing; + new2b.Parent = existing; + new2d.Parent = new1d; + new2dd.Parent = new1dd; + new1.Parent = root; + new1d.Parent = root; + new1dd.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new2a.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; + new2b.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; + new2d.ParentId = context.Entry(new1d).Property(e => e.Id).CurrentValue; + new2dd.ParentId = context.Entry(new1dd).Property(e => e.Id).CurrentValue; + new1.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + new1d.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + new1dd.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Contains(new2a, existing.Children); + Assert.Contains(new2b, existing.Children); + Assert.Contains(new2d, new1d.Children); + Assert.Contains(new2dd, new1dd.Children); + Assert.Contains(new1, root.RequiredChildren); + Assert.Contains(new1d, root.RequiredChildren); + Assert.Contains(new1dd, root.RequiredChildren); + + Assert.Same(existing, new2a.Parent); + Assert.Same(existing, new2b.Parent); + Assert.Same(new1d, new2d.Parent); + Assert.Same(new1dd, new2dd.Parent); + Assert.Same(root, existing.Parent); + Assert.Same(root, new1d.Parent); + Assert.Same(root, new1dd.Parent); + + Assert.Equal(existing.Id, new2a.ParentId); + Assert.Equal(existing.Id, new2b.ParentId); + Assert.Equal(new1d.Id, new2d.ParentId); + Assert.Equal(new1dd.Id, new2dd.ParentId); + Assert.Equal(root.Id, existing.ParentId); + Assert.Equal(root.Id, new1d.ParentId); + Assert.Equal(root.Id, new1dd.ParentId); + }); + } + + [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) + { + Root root; + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + context.Entry(root.OptionalChildren.First()).Collection(e => e.Children).Load(); + } + + var childCollection = root.OptionalChildren.First().Children; + var removed2 = childCollection.First(); + var removed1 = root.OptionalChildren.Skip(1).First(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Remove(childCollection, removed2); + Remove(root.OptionalChildren, removed1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + removed2.Parent = null; + removed1.Parent = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + removed2.ParentId = null; + removed1.ParentId = null; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.DoesNotContain(removed1, root.OptionalChildren); + Assert.DoesNotContain(removed2, childCollection); + + Assert.Null(removed1.Parent); + Assert.Null(removed2.Parent); + Assert.Null(removed1.ParentId); + Assert.Null(removed2.ParentId); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(loadedRoot).Collection(e => e.OptionalChildren).Load(); + context.Entry(loadedRoot.OptionalChildren.First()).Collection(e => e.Children).Load(); + } + + Assert.Single(loadedRoot.OptionalChildren); + Assert.Single(loadedRoot.OptionalChildren.First().Children); + } + }); + } + + [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) + { + var removed1Id = 0; + var removed2Id = 0; + List removed1ChildrenIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + context.Entry(root.RequiredChildren.First()).Collection(e => e.Children).Load(); + } + + var childCollection = root.RequiredChildren.First().Children; + var removed2 = childCollection.First(); + var removed1 = root.RequiredChildren.Skip(1).First(); + + removed1Id = removed1.Id; + removed2Id = removed2.Id; + removed1ChildrenIds = removed1.Children.Select(e => e.Id).ToList(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Remove(childCollection, removed2); + Remove(root.RequiredChildren, removed1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + removed2.Parent = null; + removed1.Parent = null; + } + + 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()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + Assert.Single(root.RequiredChildren); + Assert.DoesNotContain(removed1Id, root.RequiredChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removed1Id)); + Assert.Empty(context.Set().Where(e => e.Id == removed2Id)); + Assert.Empty(context.Set().Where(e => removed1ChildrenIds.Contains(e.Id))); + }); + } + + [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) + { + var compositeCount = 0; + OptionalAk1 oldParent = null; + OptionalComposite2 oldComposite1 = null; + OptionalComposite2 oldComposite2 = null; + Optional1 newParent = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (!useExistingParent) + { + newParent = context.CreateProxy( + e => e.CompositeChildren = new ObservableHashSet(ReferenceEqualityComparer.Instance)); + + context.Set().Add(newParent); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + compositeCount = context.Set().Count(); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + } + + oldParent = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); + + if (!DoesLazyLoading) + { + context.Entry(oldParent).Collection(e => e.CompositeChildren).Load(); + } + + oldComposite1 = oldParent.CompositeChildren.OrderBy(e => e.Id).First(); + oldComposite2 = oldParent.CompositeChildren.OrderBy(e => e.Id).Last(); + + if (useExistingParent) + { + newParent = root.OptionalChildren.OrderBy(e => e.Id).Last(); + } + else + { + newParent = context.Set().Single(e => e.Id == newParent.Id); + newParent.Parent = root; + } + + if (!DoesLazyLoading) + { + context.Entry(newParent).Collection(e => e.CompositeChildren).Load(); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + oldParent.CompositeChildren.Remove(oldComposite1); + newParent.CompositeChildren.Add(oldComposite1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + oldComposite1.Parent = null; + oldComposite1.Parent2 = newParent; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + oldComposite1.ParentId = null; + oldComposite1.Parent2Id = newParent.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Same(oldComposite2, oldParent.CompositeChildren.Single()); + Assert.Same(oldParent, oldComposite2.Parent); + Assert.Equal(oldParent.Id, oldComposite2.ParentId); + Assert.Null(oldComposite2.Parent2); + Assert.Null(oldComposite2.Parent2Id); + + Assert.Same(oldComposite1, newParent.CompositeChildren.Single()); + Assert.Same(newParent, oldComposite1.Parent2); + Assert.Equal(newParent.Id, oldComposite1.Parent2Id); + Assert.Null(oldComposite1.Parent); + Assert.Null(oldComposite1.ParentId); + + Assert.Equal(compositeCount, context.Set().Count()); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadRoot(context); + + oldParent = context.Set().Single(e => e.Id == oldParent.Id); + newParent = context.Set().Single(e => e.Id == newParent.Id); + + oldComposite1 = context.Set().Single(e => e.Id == oldComposite1.Id); + oldComposite2 = context.Set().Single(e => e.Id == oldComposite2.Id); + + Assert.Same(oldComposite2, oldParent.CompositeChildren.Single()); + Assert.Same(oldParent, oldComposite2.Parent); + Assert.Equal(oldParent.Id, oldComposite2.ParentId); + Assert.Null(oldComposite2.Parent2); + Assert.Null(oldComposite2.Parent2Id); + + Assert.Same(oldComposite1, newParent.CompositeChildren.Single()); + Assert.Same(newParent, oldComposite1.Parent2); + Assert.Equal(newParent.Id, oldComposite1.Parent2Id); + Assert.Null(oldComposite1.Parent); + Assert.Null(oldComposite1.ParentId); + + Assert.Equal(compositeCount, context.Set().Count()); + } + }); + } + + [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) + { + Root root = null; + var childCount = 0; + RequiredComposite1 oldParent = null; + OptionalOverlapping2 oldChild1 = null; + OptionalOverlapping2 oldChild2 = null; + RequiredComposite1 newParent = null; + + ExecuteWithStrategyInTransaction( + context => + { + if (!useExistingParent) + { + newParent = context.CreateProxy( + e => + { + e.Id = 3; + e.Parent = context.Set().Single(IsTheRoot); + e.CompositeChildren = new ObservableHashSet(ReferenceEqualityComparer.Instance) + { + context.CreateProxy(e => e.Id = 5), + context.CreateProxy(e => e.Id = 6) + }; + }); + + context.Set().Add(newParent); + context.SaveChanges(); + } + }, + context => + { + root = LoadRoot(context); + + childCount = context.Set().Count(); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredCompositeChildren).Load(); + } + + oldParent = root.RequiredCompositeChildren.OrderBy(e => e.Id).First(); + + if (!DoesLazyLoading) + { + context.Entry(oldParent).Collection(e => e.CompositeChildren).Load(); + } + + oldChild1 = oldParent.CompositeChildren.OrderBy(e => e.Id).First(); + oldChild2 = oldParent.CompositeChildren.OrderBy(e => e.Id).Last(); + + Assert.Equal(useExistingParent ? 2 : 3, root.RequiredCompositeChildren.Count()); + + if (useExistingParent) + { + newParent = root.RequiredCompositeChildren.OrderBy(e => e.Id).Last(); + } + else + { + newParent = context.Set().Single(e => e.Id == newParent.Id); + newParent.Parent = root; + } + + if (!DoesLazyLoading) + { + context.Entry(newParent).Collection(e => e.CompositeChildren).Load(); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + oldParent.CompositeChildren.Remove(oldChild1); + newParent.CompositeChildren.Add(oldChild1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + oldChild1.Parent = newParent; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + oldChild1.ParentId = newParent.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Same(oldChild2, oldParent.CompositeChildren.Single()); + Assert.Same(oldParent, oldChild2.Parent); + Assert.Equal(oldParent.Id, oldChild2.ParentId); + Assert.Equal(oldParent.ParentAlternateId, oldChild2.ParentAlternateId); + Assert.Equal(root.AlternateId, oldChild2.ParentAlternateId); + Assert.Same(root, oldChild2.Root); + + Assert.Equal(3, newParent.CompositeChildren.Count); + Assert.Same(oldChild1, newParent.CompositeChildren.Single(e => e.Id == oldChild1.Id)); + Assert.Same(newParent, oldChild1.Parent); + Assert.Equal(newParent.Id, oldChild1.ParentId); + Assert.Equal(oldParent.ParentAlternateId, oldChild1.ParentAlternateId); + Assert.Equal(root.AlternateId, oldChild1.ParentAlternateId); + Assert.Same(root, oldChild1.Root); + + Assert.Equal(childCount, context.Set().Count()); + }, + context => + { + var loadedRoot = LoadRoot(context); + + oldParent = context.Set().Single(e => e.Id == oldParent.Id); + newParent = context.Set().Single(e => e.Id == newParent.Id); + + oldChild1 = context.Set().Single(e => e.Id == oldChild1.Id); + oldChild2 = context.Set().Single(e => e.Id == oldChild2.Id); + + if (!DoesLazyLoading) + { + context.Entry(oldParent).Collection(e => e.CompositeChildren).Load(); + context.Entry(newParent).Collection(e => e.CompositeChildren).Load(); + } + + Assert.Same(oldChild2, oldParent.CompositeChildren.Single()); + Assert.Same(oldParent, oldChild2.Parent); + Assert.Equal(oldParent.Id, oldChild2.ParentId); + Assert.Equal(oldParent.ParentAlternateId, oldChild2.ParentAlternateId); + Assert.Equal(root.AlternateId, oldChild2.ParentAlternateId); + + Assert.Same(oldChild1, newParent.CompositeChildren.Single(e => e.Id == oldChild1.Id)); + Assert.Same(newParent, oldChild1.Parent); + Assert.Equal(newParent.Id, oldChild1.ParentId); + Assert.Equal(oldParent.ParentAlternateId, oldChild1.ParentAlternateId); + Assert.Equal(root.AlternateId, oldChild1.ParentAlternateId); + + Assert.Equal(childCount, context.Set().Count()); + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + Assert.Equal(2, root.RequiredChildren.Count()); + + var removed = root.RequiredChildren.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + } + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Single(root.RequiredChildren); + 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + Assert.Single(root.RequiredChildren); + 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))); + } + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + } + + Assert.Equal(2, root.OptionalChildren.Count()); + + var removed = root.OptionalChildren.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Children.ToList(); + orphanedIds = orphaned.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + } + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + }); + } + + [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; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + var removed = root.RequiredChildren.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + } + + removedId = removed.Id; + orphanedIds = removed.Children.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + }, + context => + { + 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); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(root.RequiredChildren); + 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.Same(root, removed.Parent); + Assert.Empty(removed.Children); + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + Assert.Single(root.RequiredChildren); + 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))); + }); + } + + [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; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + } + + var removed = root.OptionalChildren.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + } + + removedId = removed.Id; + orphanedIds = removed.Children.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + }, + context => + { + 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); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIds.Count, orphaned.Count); + Assert.True(orphaned.All(e => e.ParentId == null)); + + Assert.Same(root, removed.Parent); + Assert.Empty(removed.Children); // Never loaded + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + } + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIds.Count, orphaned.Count); + Assert.True(orphaned.All(e => e.ParentId == null)); + }); + } + + [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; + Root root = null; + Required1 removed = null; + List cascadeRemoved = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + removed = root.RequiredChildren.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + } + + cascadeRemoved = removed.Children.ToList(); + + Assert.Equal(2, root.RequiredChildren.Count()); + }, + context => + { + 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); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.True(cascadeRemoved.All(e => context.Entry(e).State == expectedState)); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + Assert.Single(root.RequiredChildren); + 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))); + } + }); + } + + [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; + Root root = null; + Optional1 removed = null; + List orphaned = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + } + + removed = root.OptionalChildren.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + } + + orphaned = removed.Children.ToList(); + + Assert.Equal(2, root.OptionalChildren.Count()); + }, + context => + { + 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); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.True(orphaned.All(e => context.Entry(e).State == expectedState)); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + }, + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildren).Load(); + } + + Assert.Single(root.OptionalChildren); + Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + Assert.Equal(2, root.RequiredChildren.Count()); + + var removed = root.RequiredChildren.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + } + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + var added = context.CreateProxy(); + Add(removed.Children, added); + + if (context.ChangeTracker.AutoDetectChangesEnabled + && !DoesChangeTracking) + { + 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)); + + context.Remove(removed); + + 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()); + + 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.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()); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildren).Load(); + } + + Assert.Single(root.RequiredChildren); + 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))); + } + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToManyAk.cs b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToManyAk.cs new file mode 100644 index 00000000000..2e5ecb7e85b --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToManyAk.cs @@ -0,0 +1,1249 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class ProxyGraphUpdatesTestBase : IClassFixture + where TFixture : ProxyGraphUpdatesTestBase.ProxyGraphUpdatesFixtureBase, new() + { + [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_with_alternate_key( + ChangeMechanism changeMechanism, bool useExistingEntities) + { + OptionalAk1 new1 = null; + OptionalAk1Derived new1d = null; + OptionalAk1MoreDerived new1dd = null; + OptionalAk2 new2a = null; + OptionalAk2 new2b = null; + OptionalComposite2 new2ca = null; + OptionalComposite2 new2cb = null; + OptionalAk2Derived new2d = null; + OptionalAk2MoreDerived new2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + new1 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new1d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new1dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2a = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2b = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2ca = context.CreateProxy(); + new2cb = context.CreateProxy(); + new2d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + + if (useExistingEntities) + { + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + } + + var existing = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (OptionalAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (OptionalAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2a = context.Set().Single(e => e.Id == new2a.Id); + new2b = context.Set().Single(e => e.Id == new2b.Id); + new2ca = context.Set().Single(e => e.Id == new2ca.Id); + new2cb = context.Set().Single(e => e.Id == new2cb.Id); + new2d = (OptionalAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (OptionalAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Add(existing.Children, new2a); + Add(existing.Children, new2b); + Add(existing.CompositeChildren, new2ca); + Add(existing.CompositeChildren, new2cb); + Add(new1d.Children, new2d); + Add(new1dd.Children, new2dd); + Add(root.OptionalChildrenAk, new1); + Add(root.OptionalChildrenAk, new1d); + Add(root.OptionalChildrenAk, new1dd); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new2a.Parent = existing; + new2b.Parent = existing; + new2ca.Parent = existing; + new2cb.Parent = existing; + new2d.Parent = new1d; + new2dd.Parent = new1dd; + new1.Parent = root; + new1d.Parent = root; + new1dd.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new2a.ParentId = existing.AlternateId; + new2b.ParentId = existing.AlternateId; + new2ca.ParentId = existing.Id; + new2ca.ParentAlternateId = existing.AlternateId; + new2cb.ParentId = existing.Id; + new2cb.ParentAlternateId = existing.AlternateId; + new2d.ParentId = new1d.AlternateId; + new2dd.ParentId = new1dd.AlternateId; + new1.ParentId = root.AlternateId; + new1d.ParentId = root.AlternateId; + new1dd.ParentId = root.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Contains(new2a, existing.Children); + Assert.Contains(new2b, existing.Children); + Assert.Contains(new2ca, existing.CompositeChildren); + Assert.Contains(new2cb, existing.CompositeChildren); + Assert.Contains(new2d, new1d.Children); + Assert.Contains(new2dd, new1dd.Children); + Assert.Contains(new1, root.OptionalChildrenAk); + Assert.Contains(new1d, root.OptionalChildrenAk); + Assert.Contains(new1dd, root.OptionalChildrenAk); + + Assert.Same(existing, new2a.Parent); + Assert.Same(existing, new2b.Parent); + Assert.Same(existing, new2ca.Parent); + Assert.Same(existing, new2cb.Parent); + Assert.Same(new1d, new2d.Parent); + Assert.Same(new1dd, new2dd.Parent); + Assert.Same(root, existing.Parent); + Assert.Same(root, new1d.Parent); + Assert.Same(root, new1dd.Parent); + + Assert.Equal(existing.AlternateId, new2a.ParentId); + Assert.Equal(existing.AlternateId, new2b.ParentId); + Assert.Equal(existing.Id, new2ca.ParentId); + Assert.Equal(existing.Id, new2cb.ParentId); + Assert.Equal(existing.AlternateId, new2ca.ParentAlternateId); + Assert.Equal(existing.AlternateId, new2cb.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.ParentId); + Assert.Equal(new1dd.AlternateId, new2dd.ParentId); + Assert.Equal(root.AlternateId, existing.ParentId); + Assert.Equal(root.AlternateId, new1d.ParentId); + Assert.Equal(root.AlternateId, new1dd.ParentId); + }); + } + + [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_with_alternate_key( + ChangeMechanism changeMechanism, bool useExistingEntities) + { + Root newRoot; + RequiredAk1 new1 = null; + RequiredAk1Derived new1d = null; + RequiredAk1MoreDerived new1dd = null; + RequiredAk2 new2a = null; + RequiredAk2 new2b = null; + RequiredComposite2 new2ca = null; + RequiredComposite2 new2cb = null; + RequiredAk2Derived new2d = null; + RequiredAk2MoreDerived new2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + newRoot = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new1 = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.Parent = newRoot; + }); + new1d = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.Parent = newRoot; + }); + new1dd = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.Parent = newRoot; + }); + new2a = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.Parent = new1; + }); + new2b = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.Parent = new1; + }); + new2ca = context.CreateProxy(e => e.Parent = new1); + new2cb = context.CreateProxy(e => e.Parent = new1); + new2d = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.Parent = new1; + }); + new2dd = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.Parent = new1; + }); + + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + var existing = root.RequiredChildrenAk.OrderBy(e => e.Id).First(); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (RequiredAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (RequiredAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2a = context.Set().Single(e => e.Id == new2a.Id); + new2b = context.Set().Single(e => e.Id == new2b.Id); + new2ca = context.Set().Single(e => e.Id == new2ca.Id); + new2cb = context.Set().Single(e => e.Id == new2cb.Id); + new2d = (RequiredAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (RequiredAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + new1.Parent = null; + new1d.Parent = null; + new1dd.Parent = null; + + context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Add(existing.Children, new2a); + Add(existing.Children, new2b); + Add(existing.CompositeChildren, new2ca); + Add(existing.CompositeChildren, new2cb); + Add(new1d.Children, new2d); + Add(new1dd.Children, new2dd); + Add(root.RequiredChildrenAk, new1); + Add(root.RequiredChildrenAk, new1d); + Add(root.RequiredChildrenAk, new1dd); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new2a.Parent = existing; + new2b.Parent = existing; + new2ca.Parent = existing; + new2cb.Parent = existing; + new2d.Parent = new1d; + new2dd.Parent = new1dd; + new1.Parent = root; + new1d.Parent = root; + new1dd.Parent = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new2a.ParentId = existing.AlternateId; + new2b.ParentId = existing.AlternateId; + new2ca.ParentId = existing.Id; + new2cb.ParentId = existing.Id; + new2ca.ParentAlternateId = existing.AlternateId; + new2cb.ParentAlternateId = existing.AlternateId; + new2d.ParentId = new1d.AlternateId; + new2dd.ParentId = new1dd.AlternateId; + new1.ParentId = root.AlternateId; + new1d.ParentId = root.AlternateId; + new1dd.ParentId = root.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Contains(new2a, existing.Children); + Assert.Contains(new2b, existing.Children); + Assert.Contains(new2ca, existing.CompositeChildren); + Assert.Contains(new2cb, existing.CompositeChildren); + Assert.Contains(new2d, new1d.Children); + Assert.Contains(new2dd, new1dd.Children); + Assert.Contains(new1, root.RequiredChildrenAk); + Assert.Contains(new1d, root.RequiredChildrenAk); + Assert.Contains(new1dd, root.RequiredChildrenAk); + + Assert.Same(existing, new2a.Parent); + Assert.Same(existing, new2b.Parent); + Assert.Same(existing, new2ca.Parent); + Assert.Same(existing, new2cb.Parent); + Assert.Same(new1d, new2d.Parent); + Assert.Same(new1dd, new2dd.Parent); + Assert.Same(root, existing.Parent); + Assert.Same(root, new1d.Parent); + Assert.Same(root, new1dd.Parent); + + Assert.Equal(existing.AlternateId, new2a.ParentId); + Assert.Equal(existing.AlternateId, new2b.ParentId); + Assert.Equal(existing.Id, new2ca.ParentId); + Assert.Equal(existing.Id, new2cb.ParentId); + Assert.Equal(existing.AlternateId, new2ca.ParentAlternateId); + Assert.Equal(existing.AlternateId, new2cb.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.ParentId); + Assert.Equal(new1dd.AlternateId, new2dd.ParentId); + Assert.Equal(root.AlternateId, existing.ParentId); + Assert.Equal(root.AlternateId, new1d.ParentId); + Assert.Equal(root.AlternateId, new1dd.ParentId); + }); + } + + [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) + { + Root root; + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + context.Entry(root.OptionalChildrenAk.First()).Collection(e => e.Children).Load(); + context.Entry(root.OptionalChildrenAk.First()).Collection(e => e.CompositeChildren).Load(); + } + + var childCollection = root.OptionalChildrenAk.First().Children; + var childCompositeCollection = root.OptionalChildrenAk.First().CompositeChildren; + var removed2 = childCollection.First(); + var removed1 = root.OptionalChildrenAk.Skip(1).First(); + var removed2c = childCompositeCollection.First(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Remove(childCollection, removed2); + Remove(childCompositeCollection, removed2c); + Remove(root.OptionalChildrenAk, removed1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + removed2.Parent = null; + removed2c.Parent = null; + removed1.Parent = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + removed2.ParentId = null; + removed2c.ParentId = null; + removed1.ParentId = null; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.DoesNotContain(removed1, root.OptionalChildrenAk); + Assert.DoesNotContain(removed2, childCollection); + Assert.DoesNotContain(removed2c, childCompositeCollection); + + Assert.Null(removed1.Parent); + Assert.Null(removed2.Parent); + Assert.Null(removed2c.Parent); + + Assert.Null(removed1.ParentId); + Assert.Null(removed2.ParentId); + Assert.Null(removed2c.ParentId); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(loadedRoot).Collection(e => e.OptionalChildrenAk).Load(); + context.Entry(loadedRoot.OptionalChildrenAk.First()).Collection(e => e.Children).Load(); + } + + Assert.Single(loadedRoot.OptionalChildrenAk); + Assert.Single(loadedRoot.OptionalChildrenAk.First().Children); + } + }); + } + + [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) + { + Root root = null; + RequiredAk2 removed2 = null; + RequiredComposite2 removed2c = null; + RequiredAk1 removed1 = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + context.Entry(root.RequiredChildrenAk.First()).Collection(e => e.Children).Load(); + context.Entry(root.RequiredChildrenAk.First()).Collection(e => e.CompositeChildren).Load(); + } + + var childCollection = root.RequiredChildrenAk.First().Children; + var childCompositeCollection = root.RequiredChildrenAk.First().CompositeChildren; + removed2 = childCollection.First(); + removed2c = childCompositeCollection.First(); + removed1 = root.RequiredChildrenAk.Skip(1).First(); + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + Remove(childCollection, removed2); + Remove(childCompositeCollection, removed2c); + Remove(root.RequiredChildrenAk, removed1); + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + removed2.Parent = null; + removed2c.Parent = null; + removed1.Parent = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.DoesNotContain(removed1, root.RequiredChildrenAk); + Assert.DoesNotContain(removed2, childCollection); + Assert.DoesNotContain(removed2c, childCompositeCollection); + + Assert.Null(removed1.Parent); + Assert.Null(removed2.Parent); + Assert.Null(removed2c.Parent); + }, + context => + { + var loadedRoot = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(loadedRoot).Collection(e => e.RequiredChildrenAk).Load(); + context.Entry(loadedRoot.RequiredChildrenAk.First()).Collection(e => e.Children).Load(); + context.Entry(loadedRoot.RequiredChildrenAk.First()).Collection(e => e.CompositeChildren).Load(); + } + + Assert.False(context.Set().Any(e => e.Id == removed1.Id)); + Assert.False(context.Set().Any(e => e.Id == removed2.Id)); + Assert.False(context.Set().Any(e => e.Id == removed2c.Id)); + + Assert.Single(loadedRoot.RequiredChildrenAk); + Assert.Single(loadedRoot.RequiredChildrenAk.First().Children); + Assert.Single(loadedRoot.RequiredChildrenAk.First().CompositeChildren); + }); + } + + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + } + + Assert.Equal(2, root.OptionalChildrenAk.Count()); + + var removed = root.OptionalChildrenAk.First(); + context.Entry(removed).Collection(e => e.CompositeChildren).Load(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Children.ToList(); + orphanedIds = orphaned.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + + Assert.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + } + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + }); + } + + [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; + List orphanedIdCs = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + Assert.Equal(2, root.RequiredChildrenAk.Count()); + + var removed = root.RequiredChildrenAk.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + context.Entry(removed).Collection(e => e.CompositeChildren).Load(); + } + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + var cascadeRemovedC = removed.CompositeChildren.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + Assert.Equal(2, orphanedIdCs.Count); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Single(root.RequiredChildrenAk); + 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + Assert.Single(root.RequiredChildrenAk); + 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))); + } + }); + } + + [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; + List orphanedIdCs = null; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + var removed = root.RequiredChildrenAk.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + context.Entry(removed).Collection(e => e.CompositeChildren).Load(); + } + + removedId = removed.Id; + orphanedIds = removed.Children.Select(e => e.Id).ToList(); + orphanedIdCs = removed.CompositeChildren.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + Assert.Equal(2, orphanedIdCs.Count); + }, + context => + { + 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); + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(root.RequiredChildrenAk); + 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.Same(root, removed.Parent); + Assert.Empty(removed.Children); // Never loaded + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + Assert.Single(root.RequiredChildrenAk); + 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))); + }); + } + + [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; + List orphanedIdCs = null; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + } + + var removed = root.OptionalChildrenAk.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + context.Entry(removed).Collection(e => e.CompositeChildren).Load(); + } + + removedId = removed.Id; + orphanedIds = removed.Children.Select(e => e.Id).ToList(); + orphanedIdCs = removed.CompositeChildren.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + Assert.Equal(2, orphanedIdCs.Count); + }, + context => + { + 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); + + context.Remove(removed); + + foreach (var toOrphan in context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList()) + { + toOrphan.ParentId = null; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIds.Count, orphaned.Count); + Assert.True(orphaned.All(e => e.ParentId == null)); + + var orphanedC = context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIdCs.Count, orphanedC.Count); + Assert.True(orphanedC.All(e => e.ParentId == null)); + + Assert.Same(root, removed.Parent); + Assert.Empty(removed.Children); // Never loaded + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + } + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + + var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIds.Count, orphaned.Count); + Assert.True(orphaned.All(e => e.ParentId == null)); + + var orphanedC = context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList(); + Assert.Equal(orphanedIdCs.Count, orphanedC.Count); + Assert.True(orphanedC.All(e => e.ParentId == null)); + }); + } + + [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; + List orphanedIdCs = null; + Root root = null; + OptionalAk1 removed = null; + List orphaned = null; + List orphanedC = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + } + + removed = root.OptionalChildrenAk.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + context.Entry(removed).Collection(e => e.CompositeChildren).Load(); + } + + orphaned = removed.Children.ToList(); + orphanedC = removed.CompositeChildren.ToList(); + + Assert.Equal(2, root.OptionalChildrenAk.Count()); + }, + context => + { + 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); + + 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()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + }, + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); + } + + Assert.Single(root.OptionalChildrenAk); + Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); + Assert.Equal(orphanedIdCs.Count, context.Set().Count(e => orphanedIdCs.Contains(e.Id))); + }); + } + + [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; + List orphanedIdCs = null; + Root root = null; + RequiredAk1 removed = null; + List cascadeRemoved = null; + List cascadeRemovedC = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + removed = root.RequiredChildrenAk.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + context.Entry(removed).Collection(e => e.CompositeChildren).Load(); + } + + cascadeRemoved = removed.Children.ToList(); + cascadeRemovedC = removed.CompositeChildren.ToList(); + + Assert.Equal(2, root.RequiredChildrenAk.Count()); + }, + context => + { + 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); + + 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()); + + 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.Same(root, removed.Parent); + Assert.Equal(2, removed.Children.Count()); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + Assert.Single(root.RequiredChildrenAk); + 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))); + } + }); + } + + [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; + List orphanedIdCs = null; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + Assert.Equal(2, root.RequiredChildrenAk.Count()); + + var removed = root.RequiredChildrenAk.First(); + + if (!DoesLazyLoading) + { + context.Entry(removed).Collection(e => e.Children).Load(); + context.Entry(removed).Collection(e => e.CompositeChildren).Load(); + } + + removedId = removed.Id; + var cascadeRemoved = removed.Children.ToList(); + var cascadeRemovedC = removed.CompositeChildren.ToList(); + orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); + orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); + + Assert.Equal(2, orphanedIds.Count); + Assert.Equal(2, orphanedIdCs.Count); + + var added = context.CreateProxy(); + var addedC = context.CreateProxy(); + Add(removed.Children, added); + Add(removed.CompositeChildren, addedC); + + if (context.ChangeTracker.AutoDetectChangesEnabled + && !DoesChangeTracking) + { + context.ChangeTracker.DetectChanges(); + } + + Assert.Equal(EntityState.Unchanged, 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)); + + context.Remove(removed); + + 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()); + + 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.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()); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); + } + + Assert.Single(root.RequiredChildrenAk); + 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))); + } + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToOne.cs b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToOne.cs new file mode 100644 index 00000000000..a1ebd0673c5 --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToOne.cs @@ -0,0 +1,1967 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class ProxyGraphUpdatesTestBase : IClassFixture + where TFixture : ProxyGraphUpdatesTestBase.ProxyGraphUpdatesFixtureBase, new() + { + [ConditionalFact] + public virtual void Optional_one_to_one_relationships_are_one_to_one() + { + ExecuteWithStrategyInTransaction( + context => + { + var root = context.Set().Single(IsTheRoot); + + root.OptionalSingle = context.CreateProxy(); + + Assert.Throws(() => context.SaveChanges()); + }); + } + + [ConditionalFact] + public virtual void Required_one_to_one_relationships_are_one_to_one() + { + ExecuteWithStrategyInTransaction( + context => + { + var root = context.Set().Single(IsTheRoot); + + root.RequiredSingle = context.CreateProxy(); + + Assert.Throws(() => context.SaveChanges()); + }); + } + + [ConditionalFact] + public virtual void Optional_one_to_one_with_AK_relationships_are_one_to_one() + { + ExecuteWithStrategyInTransaction( + context => + { + var root = context.Set().Single(IsTheRoot); + + root.OptionalSingleAk = context.CreateProxy(); + + Assert.Throws(() => context.SaveChanges()); + }); + } + + [ConditionalFact] + public virtual void Required_one_to_one_with_AK_relationships_are_one_to_one() + { + ExecuteWithStrategyInTransaction( + context => + { + var root = context.Set().Single(IsTheRoot); + + root.RequiredSingleAk = context.CreateProxy(); + + Assert.Throws(() => context.SaveChanges()); + }); + } + + [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) + { + OptionalSingle2 new2 = null; + OptionalSingle2Derived new2d = null; + OptionalSingle2MoreDerived new2dd = null; + OptionalSingle1 new1 = null; + OptionalSingle1Derived new1d = null; + OptionalSingle1MoreDerived new1dd = null; + OptionalSingle1 old1 = null; + OptionalSingle1Derived old1d = null; + OptionalSingle1MoreDerived old1dd = null; + OptionalSingle2 old2 = null; + OptionalSingle2Derived old2d = null; + OptionalSingle2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + new2 = context.CreateProxy(); + new2d = context.CreateProxy(); + new2dd = context.CreateProxy(); + new1 = context.CreateProxy(e => e.Single = new2); + new1d = context.CreateProxy(e => e.Single = new2d); + new1dd = context.CreateProxy(e => e.Single = new2dd); + + if (useExistingEntities) + { + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingle).Load(); + context.Entry(root).Reference(e => e.OptionalSingleDerived).Load(); + context.Entry(root).Reference(e => e.OptionalSingleMoreDerived).Load(); + } + + old1 = root.OptionalSingle; + old1d = root.OptionalSingleDerived; + old1dd = root.OptionalSingleMoreDerived; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1d).Reference(e => e.Single).Load(); + context.Entry(old1dd).Reference(e => e.Single).Load(); + } + + old2 = root.OptionalSingle.Single; + old2d = (OptionalSingle2Derived)root.OptionalSingleDerived.Single; + old2dd = (OptionalSingle2MoreDerived)root.OptionalSingleMoreDerived.Single; + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (OptionalSingle1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (OptionalSingle1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2d = (OptionalSingle2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (OptionalSingle2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.OptionalSingle = new1; + root.OptionalSingleDerived = new1d; + root.OptionalSingleMoreDerived = new1dd; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + new1d.DerivedRoot = root; + new1dd.MoreDerivedRoot = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new1.RootId = root.Id; + new1d.DerivedRootId = root.Id; + new1dd.MoreDerivedRootId = root.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.Id, new1.RootId); + Assert.Equal(root.Id, new1d.DerivedRootId); + Assert.Equal(root.Id, new1dd.MoreDerivedRootId); + Assert.Equal(new1.Id, new2.BackId); + Assert.Equal(new1d.Id, new2d.BackId); + Assert.Equal(new1dd.Id, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Equal(old1, old2.Back); + Assert.Equal(old1d, old2d.Back); + Assert.Equal(old1dd, old2dd.Back); + Assert.Null(old1.RootId); + Assert.Null(old1d.DerivedRootId); + Assert.Null(old1dd.MoreDerivedRootId); + Assert.Equal(old1.Id, old2.BackId); + Assert.Equal(old1d.Id, old2d.BackId); + Assert.Equal(old1dd.Id, old2dd.BackId); + }, + context => + { + LoadRoot(context); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded1d = context.Set().Single(e => e.Id == old1d.Id); + var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2d = context.Set().Single(e => e.Id == old2d.Id); + var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); + + Assert.Null(loaded1.Root); + Assert.Null(loaded1d.Root); + Assert.Null(loaded1dd.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1d, loaded2d.Back); + Assert.Same(loaded1dd, loaded2dd.Back); + Assert.Null(loaded1.RootId); + Assert.Null(loaded1d.RootId); + Assert.Null(loaded1dd.RootId); + Assert.Equal(loaded1.Id, loaded2.BackId); + Assert.Equal(loaded1d.Id, loaded2d.BackId); + Assert.Equal(loaded1dd.Id, loaded2dd.BackId); + }); + } + + [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 Save_required_one_to_one_changed_by_reference(ChangeMechanism changeMechanism) + { + RequiredSingle1 old1 = null; + RequiredSingle2 old2 = null; + Root oldRoot; + RequiredSingle2 new2 = null; + RequiredSingle1 new1 = null; + ExecuteWithStrategyInTransaction( + context => + { + oldRoot = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(oldRoot).Reference(e => e.RequiredSingle).Load(); + } + + old1 = oldRoot.RequiredSingle; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + } + + old2 = oldRoot.RequiredSingle.Single; + + context.Entry(oldRoot).State = EntityState.Detached; + context.Entry(old1).State = EntityState.Detached; + context.Entry(old2).State = EntityState.Detached; + + new2 = context.CreateProxy(); + new1 = context.CreateProxy(e => e.Single = new2); + }); + + + ExecuteWithStrategyInTransaction( + context => + { + var root = context.Set().Include(e => e.RequiredSingle.Single).Single(IsTheRoot); + + context.Entry(root.RequiredSingle.Single).State = EntityState.Deleted; + context.Entry(root.RequiredSingle).State = EntityState.Deleted; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredSingle = new1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + context.Add(new1); + new1.Root = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + context.Add(new1); + new1.Id = root.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.Id, new1.Id); + Assert.Equal(new1.Id, new2.Id); + Assert.Same(root, new1.Root); + Assert.Same(new1, new2.Back); + + Assert.NotNull(old1.Root); + Assert.Same(old1, old2.Back); + Assert.Equal(old1.Id, old2.Id); + }); + } + + [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) + { + RequiredNonPkSingle2 new2 = null; + RequiredNonPkSingle2Derived new2d = null; + RequiredNonPkSingle2MoreDerived new2dd = null; + RequiredNonPkSingle1 new1 = null; + RequiredNonPkSingle1Derived new1d = null; + RequiredNonPkSingle1MoreDerived new1dd = null; + Root newRoot; + RequiredNonPkSingle1 old1 = null; + RequiredNonPkSingle1Derived old1d = null; + RequiredNonPkSingle1MoreDerived old1dd = null; + RequiredNonPkSingle2 old2 = null; + RequiredNonPkSingle2Derived old2d = null; + RequiredNonPkSingle2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + new2 = context.CreateProxy(); + new2d = context.CreateProxy(); + new2dd = context.CreateProxy(); + new1 = context.CreateProxy(e => e.Single = new2); + new1d = context.CreateProxy( + e => + { + e.Single = new2d; + e.Root = context.CreateProxy(); + }); + new1dd = context.CreateProxy( + e => + { + e.Single = new2dd; + e.Root = context.CreateProxy(); + e.DerivedRoot = context.CreateProxy(); + }); + newRoot = context.CreateProxy( + e => + { + e.RequiredNonPkSingle = new1; + e.RequiredNonPkSingleDerived = new1d; + e.RequiredNonPkSingleMoreDerived = new1dd; + }); + + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new1d, new1dd, new2, new2d, new2dd); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + context.Entry(root).Reference(e => e.RequiredNonPkSingleDerived).Load(); + context.Entry(root).Reference(e => e.RequiredNonPkSingleMoreDerived).Load(); + } + + old1 = root.RequiredNonPkSingle; + old1d = root.RequiredNonPkSingleDerived; + old1dd = root.RequiredNonPkSingleMoreDerived; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1d).Reference(e => e.Single).Load(); + context.Entry(old1dd).Reference(e => e.Single).Load(); + context.Entry(old1d).Reference(e => e.Root).Load(); + context.Entry(old1dd).Reference(e => e.Root).Load(); + context.Entry(old1dd).Reference(e => e.DerivedRoot).Load(); + } + + old2 = root.RequiredNonPkSingle.Single; + old2d = (RequiredNonPkSingle2Derived)root.RequiredNonPkSingleDerived.Single; + old2dd = (RequiredNonPkSingle2MoreDerived)root.RequiredNonPkSingleMoreDerived.Single; + + context.Set().Remove(old1d); + context.Set().Remove(old1dd); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (RequiredNonPkSingle1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (RequiredNonPkSingle1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2d = (RequiredNonPkSingle2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (RequiredNonPkSingle2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + + new1d.RootId = old1d.RootId; + new1dd.RootId = old1dd.RootId; + new1dd.DerivedRootId = old1dd.DerivedRootId; + } + else + { + new1d.Root = old1d.Root; + new1dd.Root = old1dd.Root; + new1dd.DerivedRoot = old1dd.DerivedRoot; + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredNonPkSingle = new1; + root.RequiredNonPkSingleDerived = new1d; + root.RequiredNonPkSingleMoreDerived = new1dd; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + new1d.DerivedRoot = root; + new1dd.MoreDerivedRoot = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new1.RootId = root.Id; + new1d.DerivedRootId = root.Id; + new1dd.MoreDerivedRootId = root.Id; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.Id, new1.RootId); + Assert.Equal(root.Id, new1d.DerivedRootId); + Assert.Equal(root.Id, new1dd.MoreDerivedRootId); + Assert.Equal(new1.Id, new2.BackId); + Assert.Equal(new1d.Id, new2d.BackId); + Assert.Equal(new1dd.Id, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Null(old2.Back); + Assert.Null(old2d.Back); + Assert.Null(old2dd.Back); + Assert.Equal(old1.Id, old2.BackId); + Assert.Equal(old1d.Id, old2d.BackId); + Assert.Equal(old1dd.Id, old2dd.BackId); + }, + context => + { + var loadedRoot = LoadRoot(context); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old1d.Id)); + Assert.False(context.Set().Any(e => e.Id == old1dd.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + Assert.False(context.Set().Any(e => e.Id == old2d.Id)); + Assert.False(context.Set().Any(e => e.Id == old2dd.Id)); + }); + } + + [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) + { + Root root; + OptionalSingle1 old1 = null; + OptionalSingle2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingle).Load(); + } + + old1 = root.OptionalSingle; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + } + + old2 = root.OptionalSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.OptionalSingle = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = null; + } + + Assert.False(context.Entry(root).Reference(e => e.OptionalSingle).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Same(old1, old2.Back); + Assert.Null(old1.RootId); + Assert.Equal(old1.Id, old2.BackId); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + LoadRoot(context); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + + Assert.Null(loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Null(loaded1.RootId); + Assert.Equal(loaded1.Id, loaded2.BackId); + } + }); + } + + [ConditionalTheory] + [InlineData((int)ChangeMechanism.Dependent)] + [InlineData((int)ChangeMechanism.Principal)] + [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] + public virtual void Sever_required_one_to_one(ChangeMechanism changeMechanism) + { + Root root = null; + RequiredSingle1 old1 = null; + RequiredSingle2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + old1 = root.RequiredSingle; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + } + + old2 = root.RequiredSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredSingle = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + Assert.False(context.Entry(root).Reference(e => e.RequiredSingle).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Equal(old1.Id, old2.Id); + }, + context => + { + LoadRoot(context); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + }); + } + + [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) + { + Root root; + RequiredNonPkSingle1 old1 = null; + RequiredNonPkSingle2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + old1 = root.RequiredNonPkSingle; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + } + + old2 = root.RequiredNonPkSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredNonPkSingle = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + 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()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Equal(old1.Id, old2.BackId); + }, + context => + { + LoadRoot(context); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + }); + } + + [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) + { + Root newRoot = null; + Root root; + OptionalSingle1 old1 = null; + OptionalSingle2 old2 = null; + + ExecuteWithStrategyInTransaction( + context => + { + newRoot = context.CreateProxy(); + + if (useExistingRoot) + { + context.AddRange(newRoot); + context.SaveChanges(); + } + }, + context => + { + root = LoadRoot(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingle).Load(); + } + + old1 = root.OptionalSingle; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + } + + old2 = root.OptionalSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.OptionalSingle = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = context.Entry(newRoot).Property(e => e.Id).CurrentValue; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.OptionalSingle); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Equal(newRoot.Id, old1.RootId); + Assert.Equal(old1.Id, old2.BackId); + }, + context => + { + var loadedRoot = LoadRoot(context); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Equal(newRoot.Id, loaded1.RootId); + Assert.Equal(loaded1.Id, loaded2.BackId); + }); + } + + [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) + { + Root newRoot = null; + + ExecuteWithStrategyInTransaction( + context => + { + newRoot = context.CreateProxy(); + + if (useExistingRoot) + { + context.AddRange(newRoot); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + Assert.Equal( + CoreStrings.KeyReadOnly("Id", typeof(RequiredSingle1).Name), + Assert.Throws( + () => + { + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.RequiredSingle = root.RequiredSingle; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + root.RequiredSingle.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + root.RequiredSingle.Id = newRoot.Id; + } + + newRoot.RequiredSingle = root.RequiredSingle; + + context.SaveChanges(); + }).Message); + }); + } + + [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) + { + Root newRoot = null; + Root root; + RequiredNonPkSingle1 old1 = null; + RequiredNonPkSingle2 old2 = null; + + ExecuteWithStrategyInTransaction( + context => + { + newRoot = context.CreateProxy(); + + if (useExistingRoot) + { + context.AddRange(newRoot); + context.SaveChanges(); + } + }, + context => + { + root = LoadRoot(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + old1 = root.RequiredNonPkSingle; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + } + + old2 = root.RequiredNonPkSingle.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.RequiredNonPkSingle = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = context.Entry(newRoot).Property(e => e.Id).CurrentValue; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Equal(newRoot.Id, old1.RootId); + Assert.Equal(old1.Id, old2.BackId); + }, + context => + { + var loadedRoot = LoadRoot(context); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Equal(newRoot.Id, loaded1.RootId); + Assert.Equal(loaded1.Id, loaded2.BackId); + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingle).Load(); + } + + var removed = root.OptionalSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + }, + context => + { + var root = LoadRoot(context); + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + var removed = root.RequiredSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + var removed = root.RequiredNonPkSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Null(root.RequiredNonPkSingle); + + 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); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + var removed = root.RequiredSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + orphanedId = removed.Single.Id; + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredSingle).Single(IsTheRoot); + + var removed = root.RequiredSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + var removed = root.RequiredNonPkSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + orphanedId = removed.Single.Id; + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredNonPkSingle).Single(IsTheRoot); + + var removed = root.RequiredNonPkSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + if (cascadeDeleteTiming == CascadeTiming.Never) + { + Assert.Throws(() => context.SaveChanges()); + } + else + { + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + Assert.Null(root.RequiredNonPkSingle); + + 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); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingle).Load(); + } + + var removed = root.OptionalSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + orphanedId = removed.Single.Id; + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.OptionalSingle).Single(IsTheRoot); + + var removed = root.OptionalSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingle).Load(); + } + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); + }); + } + + [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; + Root root = null; + OptionalSingle1 removed = null; + OptionalSingle2 orphaned = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingle).Load(); + } + + removed = root.OptionalSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + orphaned = removed.Single; + }, + context => + { + 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); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Modified + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + }, + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingle).Load(); + } + + Assert.Null(root.OptionalSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + }); + } + + [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; + Root root = null; + RequiredSingle1 removed = null; + RequiredSingle2 orphaned = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + removed = root.RequiredSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + orphaned = removed.Single; + }, + context => + { + 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); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + Root root = null; + RequiredNonPkSingle1 removed = null; + RequiredNonPkSingle2 orphaned = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + removed = root.RequiredNonPkSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + orphaned = removed.Single; + }, + context => + { + 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); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + var removed = root.RequiredSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Entry(orphaned).State = EntityState.Added; + + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.Remove(removed); + + 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()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingle).Load(); + } + + Assert.Null(root.RequiredSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + var removed = root.RequiredNonPkSingle; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Entry(orphaned).State = EntityState.Added; + + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.Remove(removed); + + 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()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); + } + + Assert.Null(root.RequiredNonPkSingle); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToOneAk.cs b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToOneAk.cs new file mode 100644 index 00000000000..4f1673eff57 --- /dev/null +++ b/test/EFCore.Specification.Tests/GraphUpdates/ProxyGraphUpdatesTestBaseOneToOneAk.cs @@ -0,0 +1,2298 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Xunit; + +// ReSharper disable InconsistentNaming +// ReSharper disable AccessToModifiedClosure +// ReSharper disable PossibleMultipleEnumeration +namespace Microsoft.EntityFrameworkCore +{ + public abstract partial class ProxyGraphUpdatesTestBase : IClassFixture + where TFixture : ProxyGraphUpdatesTestBase.ProxyGraphUpdatesFixtureBase, new() + { + [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) + { + OptionalSingleAk2 new2 = null; + OptionalSingleAk2Derived new2d = null; + OptionalSingleAk2MoreDerived new2dd = null; + OptionalSingleComposite2 new2c = null; + OptionalSingleAk1 new1 = null; + OptionalSingleAk1Derived new1d = null; + OptionalSingleAk1MoreDerived new1dd = null; + OptionalSingleAk1 old1 = null; + OptionalSingleAk1Derived old1d = null; + OptionalSingleAk1MoreDerived old1dd = null; + OptionalSingleAk2 old2 = null; + OptionalSingleComposite2 old2c = null; + OptionalSingleAk2Derived old2d = null; + OptionalSingleAk2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + new2 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2c = context.CreateProxy(); + new1 = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2; + e.SingleComposite = new2c; + }); + new1d = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2d; + }); + new1dd = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2dd; + }); + + if (useExistingEntities) + { + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + context.Entry(root).Reference(e => e.OptionalSingleAkDerived).Load(); + context.Entry(root).Reference(e => e.OptionalSingleAkMoreDerived).Load(); + } + + old1 = root.OptionalSingleAk; + old1d = root.OptionalSingleAkDerived; + old1dd = root.OptionalSingleAkMoreDerived; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1).Reference(e => e.SingleComposite).Load(); + context.Entry(old1d).Reference(e => e.Single).Load(); + context.Entry(old1dd).Reference(e => e.Single).Load(); + } + + old2 = root.OptionalSingleAk.Single; + old2c = root.OptionalSingleAk.SingleComposite; + old2d = (OptionalSingleAk2Derived)root.OptionalSingleAkDerived.Single; + old2dd = (OptionalSingleAk2MoreDerived)root.OptionalSingleAkMoreDerived.Single; + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (OptionalSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (OptionalSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2c = context.Set().Single(e => e.Id == new2c.Id); + new2d = (OptionalSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (OptionalSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + } + else + { + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.OptionalSingleAk = new1; + root.OptionalSingleAkDerived = new1d; + root.OptionalSingleAkMoreDerived = new1dd; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + new1d.DerivedRoot = root; + new1dd.MoreDerivedRoot = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new1.RootId = root.AlternateId; + new1d.DerivedRootId = root.AlternateId; + new1dd.MoreDerivedRootId = root.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(root.AlternateId, new1d.DerivedRootId); + Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1.Id, new2c.BackId); + Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.BackId); + Assert.Equal(new1dd.AlternateId, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1, new2c.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(old1d, old2d.Back); + Assert.Equal(old1dd, old2dd.Back); + Assert.Null(old1.RootId); + Assert.Null(old1d.DerivedRootId); + Assert.Null(old1dd.MoreDerivedRootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + Assert.Equal(old1d.AlternateId, old2d.BackId); + Assert.Equal(old1dd.AlternateId, old2dd.BackId); + }, + context => + { + LoadRoot(context); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded1d = context.Set().Single(e => e.Id == old1d.Id); + var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2d = context.Set().Single(e => e.Id == old2d.Id); + var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + Assert.Null(loaded1.Root); + Assert.Null(loaded1d.Root); + Assert.Null(loaded1dd.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Same(loaded1d, loaded2d.Back); + Assert.Same(loaded1dd, loaded2dd.Back); + Assert.Null(loaded1.RootId); + Assert.Null(loaded1d.RootId); + Assert.Null(loaded1dd.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); + Assert.Equal(loaded1d.AlternateId, loaded2d.BackId); + Assert.Equal(loaded1dd.AlternateId, loaded2dd.BackId); + }); + } + + [ConditionalFact] + public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store() + { + OptionalSingleAk2 new2; + OptionalSingleAk2Derived new2d; + OptionalSingleAk2MoreDerived new2dd; + OptionalSingleComposite2 new2c; + OptionalSingleAk1 new1; + OptionalSingleAk1Derived new1d; + OptionalSingleAk1MoreDerived new1dd; + OptionalSingleAk1 old1 = null; + OptionalSingleAk1Derived old1d = null; + OptionalSingleAk1MoreDerived old1dd = null; + OptionalSingleAk2 old2 = null; + OptionalSingleComposite2 old2c = null; + OptionalSingleAk2Derived old2d = null; + OptionalSingleAk2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + new2 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2c = context.CreateProxy(); + new1 = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2; + e.SingleComposite = new2c; + }); + new1d = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2d; + }); + new1dd = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2dd; + }); + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + context.Entry(root).Reference(e => e.OptionalSingleAkDerived).Load(); + context.Entry(root).Reference(e => e.OptionalSingleAkMoreDerived).Load(); + } + + old1 = root.OptionalSingleAk; + old1d = root.OptionalSingleAkDerived; + old1dd = root.OptionalSingleAkMoreDerived; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1).Reference(e => e.SingleComposite).Load(); + context.Entry(old1d).Reference(e => e.Single).Load(); + context.Entry(old1dd).Reference(e => e.Single).Load(); + } + + old2 = root.OptionalSingleAk.Single; + old2c = root.OptionalSingleAk.SingleComposite; + old2d = (OptionalSingleAk2Derived)root.OptionalSingleAkDerived.Single; + old2dd = (OptionalSingleAk2MoreDerived)root.OptionalSingleAkMoreDerived.Single; + + using (var context2 = CreateContext()) + { + UseTransaction(context2.Database, context.Database.CurrentTransaction); + var root2 = context2.Set() + .Include(e => e.OptionalChildrenAk).ThenInclude(e => e.Children) + .Include(e => e.OptionalChildrenAk).ThenInclude(e => e.CompositeChildren) + .Include(e => e.OptionalSingleAk).ThenInclude(e => e.Single) + .Include(e => e.OptionalSingleAk).ThenInclude(e => e.SingleComposite) + .Include(e => e.OptionalSingleAkDerived).ThenInclude(e => e.Single) + .Include(e => e.OptionalSingleAkMoreDerived).ThenInclude(e => e.Single) + .Single(IsTheRoot); + + context2.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); + root2.OptionalSingleAk = new1; + root2.OptionalSingleAkDerived = new1d; + root2.OptionalSingleAkMoreDerived = new1dd; + + context2.SaveChanges(); + } + + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (OptionalSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (OptionalSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2c = context.Set().Single(e => e.Id == new2c.Id); + new2d = (OptionalSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (OptionalSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(root.AlternateId, new1d.DerivedRootId); + Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1.Id, new2c.BackId); + Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.BackId); + Assert.Equal(new1dd.AlternateId, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1, new2c.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(old1d, old2d.Back); + Assert.Equal(old1dd, old2dd.Back); + Assert.Null(old1.RootId); + Assert.Null(old1d.DerivedRootId); + Assert.Null(old1dd.MoreDerivedRootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + Assert.Equal(old1d.AlternateId, old2d.BackId); + Assert.Equal(old1dd.AlternateId, old2dd.BackId); + + context.SaveChanges(); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(root.AlternateId, new1d.DerivedRootId); + Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1.Id, new2c.BackId); + Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); + Assert.Equal(new1d.AlternateId, new2d.BackId); + Assert.Equal(new1dd.AlternateId, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1, new2c.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(old1d, old2d.Back); + Assert.Equal(old1dd, old2dd.Back); + Assert.Null(old1.RootId); + Assert.Null(old1d.DerivedRootId); + Assert.Null(old1dd.MoreDerivedRootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + Assert.Equal(old1d.AlternateId, old2d.BackId); + Assert.Equal(old1dd.AlternateId, old2dd.BackId); + }, + context => + { + LoadRoot(context); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded1d = context.Set().Single(e => e.Id == old1d.Id); + var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2d = context.Set().Single(e => e.Id == old2d.Id); + var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + Assert.Null(loaded1.Root); + Assert.Null(loaded1d.Root); + Assert.Null(loaded1dd.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Same(loaded1d, loaded2d.Back); + Assert.Same(loaded1dd, loaded2dd.Back); + Assert.Null(loaded1.RootId); + Assert.Null(loaded1d.RootId); + Assert.Null(loaded1dd.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); + Assert.Equal(loaded1d.AlternateId, loaded2d.BackId); + Assert.Equal(loaded1dd.AlternateId, loaded2dd.BackId); + }); + } + + [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)] + public virtual void Save_required_one_to_one_changed_by_reference_with_alternate_key( + ChangeMechanism changeMechanism, bool useExistingEntities) + { + RequiredSingleAk2 new2 = null; + RequiredSingleComposite2 new2c = null; + RequiredSingleAk1 new1 = null;; + Root newRoot; + RequiredSingleAk1 old1 = null; + RequiredSingleAk2 old2 = null; + RequiredSingleComposite2 old2c = null; + + ExecuteWithStrategyInTransaction( + context => + { + new2 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2c = context.CreateProxy(); + new1 = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2; + e.SingleComposite = new2c; + }); + newRoot = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.RequiredSingleAk = new1; + }); + + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new2, new2c); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + old1 = root.RequiredSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1).Reference(e => e.SingleComposite).Load(); + } + + old2 = root.RequiredSingleAk.Single; + old2c = root.RequiredSingleAk.SingleComposite; + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2c = context.Set().Single(e => e.Id == new2c.Id); + } + else + { + context.AddRange(new1, new2, new2c); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredSingleAk = new1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1.Id, new2c.BackId); + Assert.Equal(new1.AlternateId, new2c.BackAlternateId); + Assert.Same(root, new1.Root); + Assert.Same(new1, new2.Back); + Assert.Same(new1, new2c.Back); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Null(old2c.Back); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.BackAlternateId); + }, + context => + { + LoadRoot(context); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + Assert.False(context.Set().Any(e => e.Id == old2c.Id)); + }); + } + + [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_with_alternate_key( + ChangeMechanism changeMechanism, bool useExistingEntities) + { + RequiredNonPkSingleAk2 new2 = null; + RequiredNonPkSingleAk2Derived new2d = null; + RequiredNonPkSingleAk2MoreDerived new2dd = null; + RequiredNonPkSingleAk1 new1 = null; + RequiredNonPkSingleAk1Derived new1d = null; + RequiredNonPkSingleAk1MoreDerived new1dd = null; + Root newRoot; + RequiredNonPkSingleAk1 old1 = null; + RequiredNonPkSingleAk1Derived old1d = null; + RequiredNonPkSingleAk1MoreDerived old1dd = null; + RequiredNonPkSingleAk2 old2 = null; + RequiredNonPkSingleAk2Derived old2d = null; + RequiredNonPkSingleAk2MoreDerived old2dd = null; + + ExecuteWithStrategyInTransaction( + context => + { + new2 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new2dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + new1 = context.CreateProxy(e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2; + }); + new1d = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2d; + e.Root = context.CreateProxy(); + }); + new1dd = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.Single = new2dd; + e.Root = context.CreateProxy(); + e.DerivedRoot = context.CreateProxy(); + }); + newRoot = context.CreateProxy( + e => + { + e.AlternateId = Guid.NewGuid(); + e.RequiredNonPkSingleAk = new1; + e.RequiredNonPkSingleAkDerived = new1d; + e.RequiredNonPkSingleAkMoreDerived = new1dd; + }); + + if (useExistingEntities) + { + context.AddRange(newRoot, new1, new1d, new1dd, new2, new2d, new2dd); + context.SaveChanges(); + } + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + context.Entry(root).Reference(e => e.RequiredNonPkSingleAkDerived).Load(); + context.Entry(root).Reference(e => e.RequiredNonPkSingleAkMoreDerived).Load(); + } + + old1 = root.RequiredNonPkSingleAk; + old1d = root.RequiredNonPkSingleAkDerived; + old1dd = root.RequiredNonPkSingleAkMoreDerived; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1d).Reference(e => e.Single).Load(); + context.Entry(old1dd).Reference(e => e.Single).Load(); + context.Entry(old1d).Reference(e => e.Root).Load(); + context.Entry(old1dd).Reference(e => e.Root).Load(); + context.Entry(old1dd).Reference(e => e.DerivedRoot).Load(); + } + + old2 = root.RequiredNonPkSingleAk.Single; + old2d = (RequiredNonPkSingleAk2Derived)root.RequiredNonPkSingleAkDerived.Single; + old2dd = (RequiredNonPkSingleAk2MoreDerived)root.RequiredNonPkSingleAkMoreDerived.Single; + + context.Set().Remove(old1d); + context.Set().Remove(old1dd); + + if (useExistingEntities) + { + new1 = context.Set().Single(e => e.Id == new1.Id); + new1d = (RequiredNonPkSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); + new1dd = (RequiredNonPkSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); + new2 = context.Set().Single(e => e.Id == new2.Id); + new2d = (RequiredNonPkSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); + new2dd = (RequiredNonPkSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); + + new1d.RootId = old1d.RootId; + new1dd.RootId = old1dd.RootId; + new1dd.DerivedRootId = old1dd.DerivedRootId; + } + else + { + new1d.Root = old1d.Root; + new1dd.Root = old1dd.Root; + new1dd.DerivedRoot = old1dd.DerivedRoot; + context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); + } + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredNonPkSingleAk = new1; + root.RequiredNonPkSingleAkDerived = new1d; + root.RequiredNonPkSingleAkMoreDerived = new1dd; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + new1.Root = root; + new1d.DerivedRoot = root; + new1dd.MoreDerivedRoot = root; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + new1.RootId = root.AlternateId; + new1d.DerivedRootId = root.AlternateId; + new1dd.MoreDerivedRootId = root.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(root.AlternateId, new1.RootId); + Assert.Equal(root.AlternateId, new1d.DerivedRootId); + Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); + Assert.Equal(new1.AlternateId, new2.BackId); + Assert.Equal(new1d.AlternateId, new2d.BackId); + Assert.Equal(new1dd.AlternateId, new2dd.BackId); + Assert.Same(root, new1.Root); + Assert.Same(root, new1d.DerivedRoot); + Assert.Same(root, new1dd.MoreDerivedRoot); + Assert.Same(new1, new2.Back); + Assert.Same(new1d, new2d.Back); + Assert.Same(new1dd, new2dd.Back); + + Assert.Null(old1.Root); + Assert.Null(old1d.DerivedRoot); + Assert.Null(old1dd.MoreDerivedRoot); + Assert.Null(old2.Back); + Assert.Null(old2d.Back); + Assert.Null(old2dd.Back); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1d.AlternateId, old2d.BackId); + Assert.Equal(old1dd.AlternateId, old2dd.BackId); + }, + context => + { + var loadedRoot = LoadRoot(context); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old1d.Id)); + Assert.False(context.Set().Any(e => e.Id == old1dd.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + Assert.False(context.Set().Any(e => e.Id == old2d.Id)); + Assert.False(context.Set().Any(e => e.Id == old2dd.Id)); + }); + } + + [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) + { + Root root = null; + OptionalSingleAk1 old1 = null; + OptionalSingleAk2 old2 = null; + OptionalSingleComposite2 old2c = null; + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + } + + old1 = root.OptionalSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1).Reference(e => e.SingleComposite).Load(); + } + + old2 = root.OptionalSingleAk.Single; + old2c = root.OptionalSingleAk.SingleComposite; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.OptionalSingleAk = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = null; + } + + Assert.False(context.Entry(root).Reference(e => e.OptionalSingleAk).IsLoaded); + Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Null(old1.RootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + }, + context => + { + if ((changeMechanism & ChangeMechanism.Fk) == 0) + { + var loadedRoot = LoadRoot(context); + + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + Assert.Null(loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Null(loaded1.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); + } + }); + } + + [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) + { + Root root = null; + RequiredSingleAk1 old1 = null; + RequiredSingleAk2 old2 = null; + RequiredSingleComposite2 old2c = null; + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + old1 = root.RequiredSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1).Reference(e => e.SingleComposite).Load(); + } + + old2 = root.RequiredSingleAk.Single; + old2c = root.RequiredSingleAk.SingleComposite; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredSingleAk = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + 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()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Null(old2c.Back); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.BackAlternateId); + }, + context => + { + var loadedRoot = LoadRoot(context); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + Assert.False(context.Set().Any(e => e.Id == old2c.Id)); + }); + } + + [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) + { + Root root = null; + RequiredNonPkSingleAk1 old1 = null; + RequiredNonPkSingleAk2 old2 = null; + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + old1 = root.RequiredNonPkSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + } + + old2 = root.RequiredNonPkSingleAk.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + root.RequiredNonPkSingleAk = null; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = null; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + throw new ArgumentOutOfRangeException(nameof(changeMechanism)); + } + + if (!DoesChangeTracking) + { + 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()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(old1.Root); + Assert.Null(old2.Back); + Assert.Equal(old1.AlternateId, old2.BackId); + }, + context => + { + var loadedRoot = LoadRoot(context); + + Assert.False(context.Set().Any(e => e.Id == old1.Id)); + Assert.False(context.Set().Any(e => e.Id == old2.Id)); + }); + } + + [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) + { + Root newRoot = null; + Root root; + OptionalSingleAk1 old1 = null; + OptionalSingleAk2 old2 = null; + OptionalSingleComposite2 old2c = null; + + ExecuteWithStrategyInTransaction( + context => + { + newRoot = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + + if (useExistingRoot) + { + context.Add(newRoot); + context.SaveChanges(); + } + }, + context => + { + root = LoadRoot(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + } + + old1 = root.OptionalSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1).Reference(e => e.SingleComposite).Load(); + } + + old2 = root.OptionalSingleAk.Single; + old2c = root.OptionalSingleAk.SingleComposite; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.OptionalSingleAk = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = newRoot.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.OptionalSingleAk); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(newRoot.AlternateId, old1.RootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); + }, + context => + { + LoadRoot(context); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Equal(newRoot.AlternateId, loaded1.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); + }); + } + + [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) + { + Root newRoot = null; + Root root; + RequiredSingleAk1 old1 = null; + RequiredSingleAk2 old2 = null; + RequiredSingleComposite2 old2c = null; + + ExecuteWithStrategyInTransaction( + context => + { + newRoot = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + + if (useExistingRoot) + { + context.Add(newRoot); + context.SaveChanges(); + } + }, + context => + { + root = LoadRoot(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + old1 = root.RequiredSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + context.Entry(old1).Reference(e => e.SingleComposite).Load(); + } + + old2 = root.RequiredSingleAk.Single; + old2c = root.RequiredSingleAk.SingleComposite; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.RequiredSingleAk = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = newRoot.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.RequiredSingleAk); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Same(old1, old2c.Back); + Assert.Equal(newRoot.AlternateId, old1.RootId); + Assert.Equal(old1.AlternateId, old2.BackId); + Assert.Equal(old1.Id, old2c.BackId); + Assert.Equal(old1.AlternateId, old2c.BackAlternateId); + }, + context => + { + var loadedRoot = LoadRoot(context); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + var loaded2c = context.Set().Single(e => e.Id == old2c.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Same(loaded1, loaded2c.Back); + Assert.Equal(newRoot.AlternateId, loaded1.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + Assert.Equal(loaded1.Id, loaded2c.BackId); + Assert.Equal(loaded1.AlternateId, loaded2c.BackAlternateId); + }); + } + + [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) + { + Root newRoot = null; + Root root; + RequiredNonPkSingleAk1 old1 = null; + RequiredNonPkSingleAk2 old2 = null; + + ExecuteWithStrategyInTransaction( + context => + { + newRoot = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); + + if (useExistingRoot) + { + context.Add(newRoot); + context.SaveChanges(); + } + }, + context => + { + root = LoadRoot(context); + + context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + old1 = root.RequiredNonPkSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(old1).Reference(e => e.Single).Load(); + } + + old2 = root.RequiredNonPkSingleAk.Single; + + if ((changeMechanism & ChangeMechanism.Principal) != 0) + { + newRoot.RequiredNonPkSingleAk = old1; + } + + if ((changeMechanism & ChangeMechanism.Dependent) != 0) + { + old1.Root = newRoot; + } + + if ((changeMechanism & ChangeMechanism.Fk) != 0) + { + old1.RootId = newRoot.AlternateId; + } + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Same(newRoot, old1.Root); + Assert.Same(old1, old2.Back); + Assert.Equal(newRoot.AlternateId, old1.RootId); + Assert.Equal(old1.AlternateId, old2.BackId); + }, + context => + { + var loadedRoot = LoadRoot(context); + + newRoot = context.Set().Single(e => e.Id == newRoot.Id); + var loaded1 = context.Set().Single(e => e.Id == old1.Id); + var loaded2 = context.Set().Single(e => e.Id == old2.Id); + + Assert.Same(newRoot, loaded1.Root); + Assert.Same(loaded1, loaded2.Back); + Assert.Equal(newRoot.AlternateId, loaded1.RootId); + Assert.Equal(loaded1.AlternateId, loaded2.BackId); + }); + } + + [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; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + } + + var removed = root.OptionalSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + context.Entry(removed).Reference(e => e.SingleComposite).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + var orphanedC = removed.SingleComposite; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + } + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); + }); + } + + [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; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + var removed = root.RequiredSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + context.Entry(removed).Reference(e => e.SingleComposite).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + var orphanedC = removed.SingleComposite; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); + + Assert.Null(root.RequiredSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + 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)); + } + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + var removed = root.RequiredNonPkSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Null(root.RequiredNonPkSingleAk); + + 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); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + var removed = root.RequiredSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + context.Entry(removed).Reference(e => e.SingleComposite).Load(); + } + + removedId = removed.Id; + orphanedId = removed.Single.Id; + orphanedIdC = removed.SingleComposite.Id; + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredSingleAk).Single(IsTheRoot); + + var removed = root.RequiredSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Null(root.RequiredSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + 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)); + } + }); + } + + [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; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + var removed = root.RequiredNonPkSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + orphanedId = removed.Single.Id; + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.RequiredNonPkSingleAk).Single(IsTheRoot); + + var removed = root.RequiredNonPkSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + var orphaned = removed.Single; + + context.Remove(removed); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Null(root.RequiredNonPkSingleAk); + + 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); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + } + + var removed = root.OptionalSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + context.Entry(removed).Reference(e => e.SingleComposite).Load(); + } + + removedId = removed.Id; + orphanedId = removed.Single.Id; + orphanedIdC = removed.SingleComposite.Id; + }, + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = context.Set().Include(e => e.OptionalSingleAk).Single(IsTheRoot); + + var removed = root.OptionalSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + var orphaned = removed.Single; + + context.Remove(removed); + + // Cannot have SET NULL action in the store because one of the FK columns + // is not nullable, so need to do this on the EF side. + context.Set().Single(e => e.Id == orphanedIdC).BackId = null; + + Assert.True(context.ChangeTracker.HasChanges()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); + Assert.Null(context.Set().Single(e => e.Id == orphanedIdC).BackId); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + }, + context => + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + } + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); + Assert.Null(context.Set().Single(e => e.Id == orphanedIdC).BackId); + }); + } + + [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; + var orphanedIdC = 0; + Root root = null; + OptionalSingleAk1 removed = null; + OptionalSingleAk2 orphaned = null; + OptionalSingleComposite2 orphanedC = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + } + + removed = root.OptionalSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + context.Entry(removed).Reference(e => e.SingleComposite).Load(); + } + + orphaned = removed.Single; + orphanedC = removed.SingleComposite; + }, + context => + { + 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); + + 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()); + + context.SaveChanges(); + + Assert.False(context.ChangeTracker.HasChanges()); + + Assert.Equal(EntityState.Detached, context.Entry(removed).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); + Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + }, + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); + } + + Assert.Null(root.OptionalSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); + Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); + }); + } + + [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; + var orphanedIdC = 0; + Root root = null; + RequiredSingleAk1 removed = null; + RequiredSingleAk2 orphaned = null; + RequiredSingleComposite2 orphanedC = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + removed = root.RequiredSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + context.Entry(removed).Reference(e => e.SingleComposite).Load(); + } + + orphaned = removed.Single; + orphanedC = removed.SingleComposite; + }, + context => + { + 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); + + 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()); + + 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.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); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + 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)); + } + }); + } + + [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; + Root root = null; + RequiredNonPkSingleAk1 removed = null; + RequiredNonPkSingleAk2 orphaned = null; + + ExecuteWithStrategyInTransaction( + context => + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + removed = root.RequiredNonPkSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + orphaned = removed.Single; + }, + context => + { + 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); + + var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate + ? EntityState.Deleted + : EntityState.Unchanged; + + Assert.Equal(expectedState, context.Entry(orphaned).State); + + Assert.True(context.ChangeTracker.HasChanges()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + + [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; + var orphanedIdC = 0; + + ExecuteWithStrategyInTransaction( + context => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + var removed = root.RequiredSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + context.Entry(removed).Reference(e => e.SingleComposite).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + var orphanedC = removed.SingleComposite; + orphanedId = orphaned.Id; + orphanedIdC = orphanedC.Id; + + context.Entry(orphaned).State = EntityState.Added; + context.Entry(orphanedC).State = EntityState.Added; + + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + Assert.Equal(EntityState.Added, context.Entry(orphanedC).State); + + context.Remove(removed); + + 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()); + + 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.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); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); + } + + 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)); + } + }); + } + + [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 => + { + context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + var removed = root.RequiredNonPkSingleAk; + + if (!DoesLazyLoading) + { + context.Entry(removed).Reference(e => e.Single).Load(); + } + + removedId = removed.Id; + var orphaned = removed.Single; + orphanedId = orphaned.Id; + + context.Entry(orphaned).State = EntityState.Added; + + Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); + Assert.Equal(EntityState.Added, context.Entry(orphaned).State); + + context.Remove(removed); + + 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()); + + 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.Equal(EntityState.Detached, context.Entry(orphaned).State); + + Assert.Same(root, removed.Root); + Assert.Same(orphaned, removed.Single); + } + }, + context => + { + if (cascadeDeleteTiming != CascadeTiming.Never) + { + var root = LoadRoot(context); + + if (!DoesLazyLoading) + { + context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); + } + + Assert.Null(root.RequiredNonPkSingleAk); + + Assert.Empty(context.Set().Where(e => e.Id == removedId)); + Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); + } + }); + } + } +} diff --git a/test/EFCore.Specification.Tests/GraphUpdatesTestBase.cs b/test/EFCore.Specification.Tests/GraphUpdatesTestBase.cs deleted file mode 100644 index d4b7ca7460a..00000000000 --- a/test/EFCore.Specification.Tests/GraphUpdatesTestBase.cs +++ /dev/null @@ -1,8920 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; -using Xunit; - -// ReSharper disable AccessToDisposedClosure -// ReSharper disable PossibleMultipleEnumeration -// ReSharper disable InconsistentNaming -// ReSharper disable AccessToModifiedClosure -namespace Microsoft.EntityFrameworkCore -{ - public abstract partial class GraphUpdatesTestBase : IClassFixture - where TFixture : GraphUpdatesTestBase.GraphUpdatesFixtureBase, new() - { - protected GraphUpdatesTestBase(TFixture fixture) => Fixture = fixture; - - protected TFixture Fixture { get; } - - [ConditionalTheory] - [InlineData((int)ChangeMechanism.Fk)] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)(ChangeMechanism.Dependent | ChangeMechanism.Fk))] - public virtual void Changes_to_Added_relationships_are_picked_up(ChangeMechanism changeMechanism) - { - var id = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var entity = new OptionalSingle1(); - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - entity.RootId = 5545; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - entity.Root = new Root(); - } - - context.Add(entity); - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - entity.RootId = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - entity.Root = null; - } - - context.ChangeTracker.DetectChanges(); - - Assert.Null(entity.RootId); - Assert.Null(entity.Root); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - id = entity.Id; - }, - context => - { - var entity = context.Set().Include(e => e.Root).Single(e => e.Id == id); - - Assert.Null(entity.Root); - Assert.Null(entity.RootId); - }); - } - - [ConditionalTheory] - [InlineData(false, CascadeTiming.OnSaveChanges)] - [InlineData(false, CascadeTiming.Immediate)] - [InlineData(false, CascadeTiming.Never)] - [InlineData(false, null)] - [InlineData(true, CascadeTiming.OnSaveChanges)] - [InlineData(true, CascadeTiming.Immediate)] - [InlineData(true, CascadeTiming.Never)] - [InlineData(true, null)] - public virtual void New_FK_is_not_cleared_on_old_dependent_delete( - bool loadNewParent, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var childId = 0; - int? newFk = 0; - - ExecuteWithStrategyInTransaction( - context => - { - 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); - - removedId = removed.Id; - childId = child.Id; - - newFk = context.Set().AsNoTracking().Single(e => e.Id != removed.Id).Id; - - var newParent = loadNewParent ? context.Set().Find(newFk) : null; - - child.ParentId = newFk; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(newFk, child.ParentId); - - if (loadNewParent) - { - Assert.Same(newParent, child.Parent); - Assert.Contains(child, newParent.Children); - } - else - { - Assert.Null((child.Parent)); - } - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - Assert.Null(context.Set().Find(removedId)); - - var child = context.Set().Find(childId); - var newParent = loadNewParent ? context.Set().Find(newFk) : null; - - Assert.Equal(newFk, child.ParentId); - - if (loadNewParent) - { - Assert.Same(newParent, child.Parent); - Assert.Contains(child, newParent.Children); - } - else - { - Assert.Null((child.Parent)); - } - - Assert.False(context.ChangeTracker.HasChanges()); - } - }); - } - - [ConditionalTheory] - [InlineData(CascadeTiming.OnSaveChanges)] - [InlineData(CascadeTiming.Immediate)] - [InlineData(CascadeTiming.Never)] - [InlineData(null)] - public virtual void Optional_One_to_one_relationships_are_one_to_one( - CascadeTiming? deleteOrphansTiming) - { - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = context.Set().Single(IsTheRoot); - - Assert.False(context.ChangeTracker.HasChanges()); - - root.OptionalSingle = new OptionalSingle1(); - - Assert.True(context.ChangeTracker.HasChanges()); - - Assert.Throws(() => context.SaveChanges()); - }); - } - - [ConditionalTheory] - [InlineData(CascadeTiming.OnSaveChanges)] - [InlineData(CascadeTiming.Immediate)] - [InlineData(CascadeTiming.Never)] - [InlineData(null)] - public virtual void Required_One_to_one_relationships_are_one_to_one( - CascadeTiming? deleteOrphansTiming) - { - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = context.Set().Single(IsTheRoot); - - Assert.False(context.ChangeTracker.HasChanges()); - - root.RequiredSingle = new RequiredSingle1(); - - Assert.True(context.ChangeTracker.HasChanges()); - - Assert.Throws(() => context.SaveChanges()); - }); - } - - [ConditionalTheory] - [InlineData(CascadeTiming.OnSaveChanges)] - [InlineData(CascadeTiming.Immediate)] - [InlineData(CascadeTiming.Never)] - [InlineData(null)] - public virtual void Optional_One_to_one_with_AK_relationships_are_one_to_one( - CascadeTiming? deleteOrphansTiming) - { - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = context.Set().Single(IsTheRoot); - - Assert.False(context.ChangeTracker.HasChanges()); - - root.OptionalSingleAk = new OptionalSingleAk1(); - - Assert.True(context.ChangeTracker.HasChanges()); - - Assert.Throws(() => context.SaveChanges()); - }); - } - - [ConditionalTheory] - [InlineData(CascadeTiming.OnSaveChanges)] - [InlineData(CascadeTiming.Immediate)] - [InlineData(CascadeTiming.Never)] - [InlineData(null)] - public virtual void Required_One_to_one_with_AK_relationships_are_one_to_one( - CascadeTiming? deleteOrphansTiming) - { - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = context.Set().Single(IsTheRoot); - - Assert.False(context.ChangeTracker.HasChanges()); - - root.RequiredSingleAk = new RequiredSingleAk1(); - - Assert.True(context.ChangeTracker.HasChanges()); - - Assert.Throws(() => context.SaveChanges()); - }); - } - - [ConditionalTheory] - [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(); - - Assert.False(context.ChangeTracker.HasChanges()); - - existing.Parent = null; - existing.ParentId = null; - ((ICollection)root.OptionalChildren).Remove(existing); - - context.Entry(existing).State = EntityState.Deleted; - - Assert.True(context.ChangeTracker.HasChanges()); - - var queried = context.Set().ToList(); - - Assert.Null(existing.Parent); - Assert.Null(existing.ParentId); - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(existing, root.OptionalChildren); - - Assert.Equal(2, queried.Count); - Assert.Contains(existing, queried); - } - - [ConditionalTheory] - [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(); - var new1dd = new Optional1MoreDerived(); - var new2a = new Optional2(); - var new2b = new Optional2(); - var new2d = new Optional2Derived(); - var new2dd = new Optional2MoreDerived(); - Root root = null; - IReadOnlyList entries = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalGraph(context); - var existing = root.OptionalChildren.OrderBy(e => e.Id).First(); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (Optional1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (Optional1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2a = context.Set().Single(e => e.Id == new2a.Id); - new2b = context.Set().Single(e => e.Id == new2b.Id); - new2d = (Optional2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (Optional2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Add(existing.Children, new2a); - Add(existing.Children, new2b); - Add(new1d.Children, new2d); - Add(new1dd.Children, new2dd); - Add(root.OptionalChildren, new1); - Add(root.OptionalChildren, new1d); - Add(root.OptionalChildren, new1dd); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new2a.Parent = existing; - new2b.Parent = existing; - new2d.Parent = new1d; - new2dd.Parent = new1dd; - new1.Parent = root; - new1d.Parent = root; - new1dd.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new2a.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; - new2b.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; - new2d.ParentId = context.Entry(new1d).Property(e => e.Id).CurrentValue; - new2dd.ParentId = context.Entry(new1dd).Property(e => e.Id).CurrentValue; - new1.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - new1d.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - new1dd.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Contains(new2a, existing.Children); - Assert.Contains(new2b, existing.Children); - Assert.Contains(new2d, new1d.Children); - Assert.Contains(new2dd, new1dd.Children); - Assert.Contains(new1, root.OptionalChildren); - Assert.Contains(new1d, root.OptionalChildren); - Assert.Contains(new1dd, root.OptionalChildren); - - Assert.Same(existing, new2a.Parent); - Assert.Same(existing, new2b.Parent); - Assert.Same(new1d, new2d.Parent); - Assert.Same(new1dd, new2dd.Parent); - Assert.Same(root, existing.Parent); - Assert.Same(root, new1d.Parent); - Assert.Same(root, new1dd.Parent); - - Assert.Equal(existing.Id, new2a.ParentId); - Assert.Equal(existing.Id, new2b.ParentId); - Assert.Equal(new1d.Id, new2d.ParentId); - Assert.Equal(new1dd.Id, new2dd.ParentId); - Assert.Equal(root.Id, existing.ParentId); - Assert.Equal(root.Id, new1d.ParentId); - Assert.Equal(root.Id, new1dd.ParentId); - - entries = context.ChangeTracker.Entries().ToList(); - }, - context => - { - var loadedRoot = LoadOptionalGraph(context); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - }); - } - - [ConditionalTheory] - [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 { Parent = newRoot }; - var new1d = new Required1Derived { Parent = newRoot }; - var new1dd = new Required1MoreDerived { Parent = newRoot }; - var new2a = new Required2 { Parent = new1 }; - var new2b = new Required2 { Parent = new1 }; - var new2d = new Required2Derived { Parent = new1 }; - var new2dd = new Required2MoreDerived { Parent = new1 }; - Root root = null; - IReadOnlyList entries = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new1d, new1dd, new2a, new2d, new2dd, new2b); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredGraph(context); - var existing = root.RequiredChildren.OrderBy(e => e.Id).First(); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (Required1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (Required1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2a = context.Set().Single(e => e.Id == new2a.Id); - new2b = context.Set().Single(e => e.Id == new2b.Id); - new2d = (Required2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (Required2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - new1.Parent = null; - new1d.Parent = null; - new1dd.Parent = null; - - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Add(existing.Children, new2a); - Add(existing.Children, new2b); - Add(new1d.Children, new2d); - Add(new1dd.Children, new2dd); - Add(root.RequiredChildren, new1); - Add(root.RequiredChildren, new1d); - Add(root.RequiredChildren, new1dd); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new2a.Parent = existing; - new2b.Parent = existing; - new2d.Parent = new1d; - new2dd.Parent = new1dd; - new1.Parent = root; - new1d.Parent = root; - new1dd.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new2a.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; - new2b.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; - new2d.ParentId = context.Entry(new1d).Property(e => e.Id).CurrentValue; - new2dd.ParentId = context.Entry(new1dd).Property(e => e.Id).CurrentValue; - new1.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - new1d.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - new1dd.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Contains(new2a, existing.Children); - Assert.Contains(new2b, existing.Children); - Assert.Contains(new2d, new1d.Children); - Assert.Contains(new2dd, new1dd.Children); - Assert.Contains(new1, root.RequiredChildren); - Assert.Contains(new1d, root.RequiredChildren); - Assert.Contains(new1dd, root.RequiredChildren); - - Assert.Same(existing, new2a.Parent); - Assert.Same(existing, new2b.Parent); - Assert.Same(new1d, new2d.Parent); - Assert.Same(new1dd, new2dd.Parent); - Assert.Same(root, existing.Parent); - Assert.Same(root, new1d.Parent); - Assert.Same(root, new1dd.Parent); - - Assert.Equal(existing.Id, new2a.ParentId); - Assert.Equal(existing.Id, new2b.ParentId); - Assert.Equal(new1d.Id, new2d.ParentId); - Assert.Equal(new1dd.Id, new2dd.ParentId); - Assert.Equal(root.Id, existing.ParentId); - Assert.Equal(root.Id, new1d.ParentId); - Assert.Equal(root.Id, new1dd.ParentId); - - entries = context.ChangeTracker.Entries().ToList(); - }, - context => - { - var loadedRoot = LoadRequiredGraph(context); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - }); - } - - [ConditionalTheory] - [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; - var removed2 = childCollection.First(); - var removed1 = root.OptionalChildren.Skip(1).First(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Remove(childCollection, removed2); - Remove(root.OptionalChildren, removed1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - removed2.Parent = null; - removed1.Parent = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - removed2.ParentId = null; - removed1.ParentId = null; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.DoesNotContain(removed1, root.OptionalChildren); - Assert.DoesNotContain(removed2, childCollection); - - Assert.Null(removed1.Parent); - Assert.Null(removed2.Parent); - Assert.Null(removed1.ParentId); - Assert.Null(removed2.ParentId); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadOptionalGraph(context); - - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - Assert.Single(loadedRoot.OptionalChildren); - Assert.Single(loadedRoot.OptionalChildren.First().Children); - } - }); - } - - [ConditionalFact] - public virtual void Notification_entities_can_have_indexes() - { - ExecuteWithStrategyInTransaction( - context => - { - var produce = new Produce { Name = "Apple", BarCode = 77 }; - context.Add(produce); - - Assert.Equal(EntityState.Added, context.Entry(produce).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Unchanged, context.Entry(produce).State); - Assert.NotEqual(Guid.Empty, context.Entry(produce).Property(e => e.ProduceId).OriginalValue); - Assert.Equal(77, context.Entry(produce).Property(e => e.BarCode).OriginalValue); - - context.Remove(produce); - Assert.Equal(EntityState.Deleted, context.Entry(produce).State); - Assert.NotEqual(Guid.Empty, context.Entry(produce).Property(e => e.ProduceId).OriginalValue); - Assert.Equal(77, context.Entry(produce).Property(e => e.BarCode).OriginalValue); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(produce).State); - }); - } - - [ConditionalFact] - public virtual void Resetting_a_deleted_reference_fixes_up_again() - { - ExecuteWithStrategyInTransaction( - context => - { - var bloog = context.Set().Include(e => e.Poosts).Single(); - var poost1 = bloog.Poosts.First(); - var poost2 = bloog.Poosts.Skip(1).First(); - - Assert.Equal(2, bloog.Poosts.Count()); - Assert.Same(bloog, poost1.Bloog); - Assert.Same(bloog, poost2.Bloog); - - context.Remove(bloog); - - Assert.True(context.ChangeTracker.HasChanges()); - - Assert.Equal(2, bloog.Poosts.Count()); - - if (Fixture.ForceClientNoAction) - { - Assert.Same(bloog, poost1.Bloog); - Assert.Same(bloog, poost2.Bloog); - } - else - { - Assert.Null(poost1.Bloog); - Assert.Null(poost2.Bloog); - } - - poost1.Bloog = bloog; - - Assert.Equal(2, bloog.Poosts.Count()); - - if (Fixture.ForceClientNoAction) - { - Assert.Same(bloog, poost1.Bloog); - Assert.Same(bloog, poost2.Bloog); - } - else - { - Assert.Same(bloog, poost1.Bloog); - Assert.Null(poost2.Bloog); - } - - poost1.Bloog = null; - - Assert.Equal(2, bloog.Poosts.Count()); - - if (Fixture.ForceClientNoAction) - { - Assert.Null(poost1.Bloog); - Assert.Same(bloog, poost2.Bloog); - } - else - { - Assert.Null(poost1.Bloog); - Assert.Null(poost2.Bloog); - } - - if (!Fixture.ForceClientNoAction) - { - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(2, bloog.Poosts.Count()); - Assert.Null(poost1.Bloog); - Assert.Null(poost2.Bloog); - } - }); - } - - [ConditionalFact] - public virtual void Detaching_principal_entity_will_remove_references_to_it() - { - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadOptionalGraph(context); - LoadRequiredGraph(context); - LoadOptionalAkGraph(context); - LoadRequiredAkGraph(context); - LoadRequiredCompositeGraph(context); - LoadRequiredNonPkGraph(context); - LoadOptionalOneToManyGraph(context); - LoadRequiredNonPkAkGraph(context); - - var optionalSingle = root.OptionalSingle; - var requiredSingle = root.RequiredSingle; - var optionalSingleAk = root.OptionalSingleAk; - var optionalSingleDerived = root.OptionalSingleDerived; - var requiredSingleAk = root.RequiredSingleAk; - var optionalSingleAkDerived = root.OptionalSingleAkDerived; - var optionalSingleMoreDerived = root.OptionalSingleMoreDerived; - var requiredNonPkSingle = root.RequiredNonPkSingle; - var optionalSingleAkMoreDerived = root.OptionalSingleAkMoreDerived; - var requiredNonPkSingleAk = root.RequiredNonPkSingleAk; - var requiredNonPkSingleDerived = root.RequiredNonPkSingleDerived; - var requiredNonPkSingleAkDerived = root.RequiredNonPkSingleAkDerived; - var requiredNonPkSingleMoreDerived = root.RequiredNonPkSingleMoreDerived; - var requiredNonPkSingleAkMoreDerived = root.RequiredNonPkSingleAkMoreDerived; - - Assert.Same(root, optionalSingle.Root); - Assert.Same(root, requiredSingle.Root); - Assert.Same(root, optionalSingleAk.Root); - Assert.Same(root, optionalSingleDerived.DerivedRoot); - Assert.Same(root, requiredSingleAk.Root); - Assert.Same(root, optionalSingleAkDerived.DerivedRoot); - Assert.Same(root, optionalSingleMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingle.Root); - Assert.Same(root, optionalSingleAkMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingleAk.Root); - Assert.Same(root, requiredNonPkSingleDerived.DerivedRoot); - Assert.Same(root, requiredNonPkSingleAkDerived.DerivedRoot); - Assert.Same(root, requiredNonPkSingleMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingleAkMoreDerived.MoreDerivedRoot); - - Assert.True(root.OptionalChildren.All(e => e.Parent == root)); - Assert.True(root.RequiredChildren.All(e => e.Parent == root)); - Assert.True(root.OptionalChildrenAk.All(e => e.Parent == root)); - Assert.True(root.RequiredChildrenAk.All(e => e.Parent == root)); - Assert.True(root.RequiredCompositeChildren.All(e => e.Parent == root)); - - Assert.False(context.ChangeTracker.HasChanges()); - - context.Entry(optionalSingle).State = EntityState.Detached; - context.Entry(requiredSingle).State = EntityState.Detached; - context.Entry(optionalSingleAk).State = EntityState.Detached; - context.Entry(optionalSingleDerived).State = EntityState.Detached; - context.Entry(requiredSingleAk).State = EntityState.Detached; - context.Entry(optionalSingleAkDerived).State = EntityState.Detached; - context.Entry(optionalSingleMoreDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingle).State = EntityState.Detached; - context.Entry(optionalSingleAkMoreDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingleAk).State = EntityState.Detached; - context.Entry(requiredNonPkSingleDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingleAkDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingleMoreDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingleAkMoreDerived).State = EntityState.Detached; - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.NotNull(optionalSingle.Root); - Assert.NotNull(requiredSingle.Root); - Assert.NotNull(optionalSingleAk.Root); - Assert.NotNull(optionalSingleDerived.DerivedRoot); - Assert.NotNull(requiredSingleAk.Root); - Assert.NotNull(optionalSingleAkDerived.DerivedRoot); - Assert.NotNull(optionalSingleMoreDerived.MoreDerivedRoot); - Assert.NotNull(requiredNonPkSingle.Root); - Assert.NotNull(optionalSingleAkMoreDerived.MoreDerivedRoot); - Assert.NotNull(requiredNonPkSingleAk.Root); - Assert.NotNull(requiredNonPkSingleDerived.DerivedRoot); - Assert.NotNull(requiredNonPkSingleAkDerived.DerivedRoot); - Assert.NotNull(requiredNonPkSingleMoreDerived.MoreDerivedRoot); - Assert.NotNull(requiredNonPkSingleAkMoreDerived.MoreDerivedRoot); - - Assert.True(root.OptionalChildren.All(e => e.Parent != null)); - Assert.True(root.RequiredChildren.All(e => e.Parent != null)); - Assert.True(root.OptionalChildrenAk.All(e => e.Parent != null)); - Assert.True(root.RequiredChildrenAk.All(e => e.Parent != null)); - Assert.True(root.RequiredCompositeChildren.All(e => e.Parent != null)); - }); - } - - [ConditionalFact] - public virtual void Detaching_dependent_entity_will_not_remove_references_to_it() - { - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadOptionalGraph(context); - LoadRequiredGraph(context); - LoadOptionalAkGraph(context); - LoadRequiredAkGraph(context); - LoadRequiredCompositeGraph(context); - LoadRequiredNonPkGraph(context); - LoadOptionalOneToManyGraph(context); - LoadRequiredNonPkAkGraph(context); - - var optionalSingle = root.OptionalSingle; - var requiredSingle = root.RequiredSingle; - var optionalSingleAk = root.OptionalSingleAk; - var optionalSingleDerived = root.OptionalSingleDerived; - var requiredSingleAk = root.RequiredSingleAk; - var optionalSingleAkDerived = root.OptionalSingleAkDerived; - var optionalSingleMoreDerived = root.OptionalSingleMoreDerived; - var requiredNonPkSingle = root.RequiredNonPkSingle; - var optionalSingleAkMoreDerived = root.OptionalSingleAkMoreDerived; - var requiredNonPkSingleAk = root.RequiredNonPkSingleAk; - var requiredNonPkSingleDerived = root.RequiredNonPkSingleDerived; - var requiredNonPkSingleAkDerived = root.RequiredNonPkSingleAkDerived; - var requiredNonPkSingleMoreDerived = root.RequiredNonPkSingleMoreDerived; - var requiredNonPkSingleAkMoreDerived = root.RequiredNonPkSingleAkMoreDerived; - - var optionalChildren = root.OptionalChildren; - var requiredChildren = root.RequiredChildren; - var optionalChildrenAk = root.OptionalChildrenAk; - var requiredChildrenAk = root.RequiredChildrenAk; - var requiredCompositeChildren = root.RequiredCompositeChildren; - var optionalChild = optionalChildren.First(); - var requiredChild = requiredChildren.First(); - var optionalChildAk = optionalChildrenAk.First(); - var requieredChildAk = requiredChildrenAk.First(); - var requiredCompositeChild = requiredCompositeChildren.First(); - - Assert.Same(root, optionalSingle.Root); - Assert.Same(root, requiredSingle.Root); - Assert.Same(root, optionalSingleAk.Root); - Assert.Same(root, optionalSingleDerived.DerivedRoot); - Assert.Same(root, requiredSingleAk.Root); - Assert.Same(root, optionalSingleAkDerived.DerivedRoot); - Assert.Same(root, optionalSingleMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingle.Root); - Assert.Same(root, optionalSingleAkMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingleAk.Root); - Assert.Same(root, requiredNonPkSingleDerived.DerivedRoot); - Assert.Same(root, requiredNonPkSingleAkDerived.DerivedRoot); - Assert.Same(root, requiredNonPkSingleMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingleAkMoreDerived.MoreDerivedRoot); - - Assert.True(optionalChildren.All(e => e.Parent == root)); - Assert.True(requiredChildren.All(e => e.Parent == root)); - Assert.True(optionalChildrenAk.All(e => e.Parent == root)); - Assert.True(requiredChildrenAk.All(e => e.Parent == root)); - Assert.True(requiredCompositeChildren.All(e => e.Parent == root)); - - Assert.False(context.ChangeTracker.HasChanges()); - - context.Entry(optionalSingle).State = EntityState.Detached; - context.Entry(requiredSingle).State = EntityState.Detached; - context.Entry(optionalSingleAk).State = EntityState.Detached; - context.Entry(optionalSingleDerived).State = EntityState.Detached; - context.Entry(requiredSingleAk).State = EntityState.Detached; - context.Entry(optionalSingleAkDerived).State = EntityState.Detached; - context.Entry(optionalSingleMoreDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingle).State = EntityState.Detached; - context.Entry(optionalSingleAkMoreDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingleAk).State = EntityState.Detached; - context.Entry(requiredNonPkSingleDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingleAkDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingleMoreDerived).State = EntityState.Detached; - context.Entry(requiredNonPkSingleAkMoreDerived).State = EntityState.Detached; - context.Entry(optionalChild).State = EntityState.Detached; - context.Entry(requiredChild).State = EntityState.Detached; - context.Entry(optionalChildAk).State = EntityState.Detached; - context.Entry(requieredChildAk).State = EntityState.Detached; - - foreach (var overlappingEntry in context.ChangeTracker.Entries()) - { - overlappingEntry.State = EntityState.Detached; - } - - context.Entry(requiredCompositeChild).State = EntityState.Detached; - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Same(root, optionalSingle.Root); - Assert.Same(root, requiredSingle.Root); - Assert.Same(root, optionalSingleAk.Root); - Assert.Same(root, optionalSingleDerived.DerivedRoot); - Assert.Same(root, requiredSingleAk.Root); - Assert.Same(root, optionalSingleAkDerived.DerivedRoot); - Assert.Same(root, optionalSingleMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingle.Root); - Assert.Same(root, optionalSingleAkMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingleAk.Root); - Assert.Same(root, requiredNonPkSingleDerived.DerivedRoot); - Assert.Same(root, requiredNonPkSingleAkDerived.DerivedRoot); - Assert.Same(root, requiredNonPkSingleMoreDerived.MoreDerivedRoot); - Assert.Same(root, requiredNonPkSingleAkMoreDerived.MoreDerivedRoot); - - Assert.True(optionalChildren.All(e => e.Parent == root)); - Assert.True(requiredChildren.All(e => e.Parent == root)); - Assert.True(optionalChildrenAk.All(e => e.Parent == root)); - Assert.True(requiredChildrenAk.All(e => e.Parent == root)); - Assert.True(requiredCompositeChildren.All(e => e.Parent == root)); - - Assert.NotNull(root.OptionalSingle); - Assert.NotNull(root.RequiredSingle); - Assert.NotNull(root.OptionalSingleAk); - Assert.NotNull(root.OptionalSingleDerived); - Assert.NotNull(root.RequiredSingleAk); - Assert.NotNull(root.OptionalSingleAkDerived); - Assert.NotNull(root.OptionalSingleMoreDerived); - Assert.NotNull(root.RequiredNonPkSingle); - Assert.NotNull(root.OptionalSingleAkMoreDerived); - Assert.NotNull(root.RequiredNonPkSingleAk); - Assert.NotNull(root.RequiredNonPkSingleDerived); - Assert.NotNull(root.RequiredNonPkSingleAkDerived); - Assert.NotNull(root.RequiredNonPkSingleMoreDerived); - Assert.NotNull(root.RequiredNonPkSingleAkMoreDerived); - - Assert.Contains(optionalChild, root.OptionalChildren); - Assert.Contains(requiredChild, root.RequiredChildren); - Assert.Contains(optionalChildAk, root.OptionalChildrenAk); - Assert.Contains(requieredChildAk, root.RequiredChildrenAk); - Assert.Contains(requiredCompositeChild, root.RequiredCompositeChildren); - }); - } - - [ConditionalTheory] - [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; - List removed1ChildrenIds = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredGraph(context); - - var childCollection = root.RequiredChildren.First().Children; - var removed2 = childCollection.First(); - var removed1 = root.RequiredChildren.Skip(1).First(); - - removed1Id = removed1.Id; - removed2Id = removed2.Id; - removed1ChildrenIds = removed1.Children.Select(e => e.Id).ToList(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Remove(childCollection, removed2); - Remove(root.RequiredChildren, removed1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - removed2.Parent = null; - removed1.Parent = null; - } - - if (Fixture.ForceClientNoAction - || deleteOrphansTiming == CascadeTiming.Never) - { - Action testCode; - - 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; - } - - testCode = deleteOrphansTiming == CascadeTiming.Immediate - ? () => context.ChangeTracker.DetectChanges() - : deleteOrphansTiming == null - ? () => 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()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades - && deleteOrphansTiming != CascadeTiming.Never) - { - var root = LoadRequiredGraph(context); - - AssertNavigations(root); - - Assert.Single(root.RequiredChildren); - Assert.DoesNotContain(removed1Id, root.RequiredChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removed1Id)); - Assert.Empty(context.Set().Where(e => e.Id == removed2Id)); - Assert.Empty(context.Set().Where(e => removed1ChildrenIds.Contains(e.Id))); - } - }); - } - - [ConditionalTheory] - [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(); - var new2dd = new OptionalSingle2MoreDerived(); - var new1 = new OptionalSingle1 { Single = new2 }; - var new1d = new OptionalSingle1Derived { Single = new2d }; - var new1dd = new OptionalSingle1MoreDerived { Single = new2dd }; - Root root = null; - IReadOnlyList entries = null; - OptionalSingle1 old1 = null; - OptionalSingle1Derived old1d = null; - OptionalSingle1MoreDerived old1dd = null; - OptionalSingle2 old2 = null; - OptionalSingle2Derived old2d = null; - OptionalSingle2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalGraph(context); - - old1 = root.OptionalSingle; - old1d = root.OptionalSingleDerived; - old1dd = root.OptionalSingleMoreDerived; - old2 = root.OptionalSingle.Single; - old2d = (OptionalSingle2Derived)root.OptionalSingleDerived.Single; - old2dd = (OptionalSingle2MoreDerived)root.OptionalSingleMoreDerived.Single; - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (OptionalSingle1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (OptionalSingle1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2d = (OptionalSingle2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (OptionalSingle2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.OptionalSingle = new1; - root.OptionalSingleDerived = new1d; - root.OptionalSingleMoreDerived = new1dd; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - new1d.DerivedRoot = root; - new1dd.MoreDerivedRoot = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new1.RootId = root.Id; - new1d.DerivedRootId = root.Id; - new1dd.MoreDerivedRootId = root.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.Id, new1.RootId); - Assert.Equal(root.Id, new1d.DerivedRootId); - Assert.Equal(root.Id, new1dd.MoreDerivedRootId); - Assert.Equal(new1.Id, new2.BackId); - Assert.Equal(new1d.Id, new2d.BackId); - Assert.Equal(new1dd.Id, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Equal(old1, old2.Back); - Assert.Equal(old1d, old2d.Back); - Assert.Equal(old1dd, old2dd.Back); - Assert.Null(old1.RootId); - Assert.Null(old1d.DerivedRootId); - Assert.Null(old1dd.MoreDerivedRootId); - Assert.Equal(old1.Id, old2.BackId); - Assert.Equal(old1d.Id, old2d.BackId); - Assert.Equal(old1dd.Id, old2dd.BackId); - - entries = context.ChangeTracker.Entries().ToList(); - }, - context => - { - var loadedRoot = LoadOptionalGraph(context); - - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded1d = context.Set().Single(e => e.Id == old1d.Id); - var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2d = context.Set().Single(e => e.Id == old2d.Id); - var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - - Assert.Null(loaded1.Root); - Assert.Null(loaded1d.Root); - Assert.Null(loaded1dd.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1d, loaded2d.Back); - Assert.Same(loaded1dd, loaded2dd.Back); - Assert.Null(loaded1.RootId); - Assert.Null(loaded1d.RootId); - Assert.Null(loaded1dd.RootId); - Assert.Equal(loaded1.Id, loaded2.BackId); - Assert.Equal(loaded1d.Id, loaded2d.BackId); - Assert.Equal(loaded1dd.Id, loaded2dd.BackId); - }); - } - - [ConditionalTheory] - [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_required_one_to_one_changed_by_reference( - ChangeMechanism changeMechanism, - CascadeTiming? deleteOrphansTiming) - { - // This test is a bit strange because the relationships are PK<->PK, which means - // that an existing entity has to be deleted and then a new entity created that has - // the same key as the existing entry. In other words it is a new incarnation of the same - // entity. EF7 can't track two different instances of the same entity, so this has to be - // done in two steps. - - Root oldRoot = null; - IReadOnlyList entries = null; - RequiredSingle1 old1 = null; - RequiredSingle2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - oldRoot = LoadRequiredGraph(context); - - old1 = oldRoot.RequiredSingle; - old2 = oldRoot.RequiredSingle.Single; - }); - - var new2 = new RequiredSingle2(); - var new1 = new RequiredSingle1 { Single = new2 }; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredGraph(context); - - root.RequiredSingle = null; - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var root = LoadRequiredGraph(context); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredSingle = new1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - context.Add(new1); - new1.Root = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - context.Add(new1); - new1.Id = root.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.Id, new1.Id); - Assert.Equal(new1.Id, new2.Id); - Assert.Same(root, new1.Root); - Assert.Same(new1, new2.Back); - - Assert.Same(oldRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Equal(old1.Id, old2.Id); - - entries = context.ChangeTracker.Entries().ToList(); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var loadedRoot = LoadRequiredGraph(context); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - AssertKeys(oldRoot, loadedRoot); - AssertNavigations(loadedRoot); - } - }); - } - - [ConditionalTheory] - [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(); - var new2dd = new RequiredNonPkSingle2MoreDerived(); - var new1 = new RequiredNonPkSingle1 { Single = new2 }; - var new1d = new RequiredNonPkSingle1Derived { Single = new2d, Root = new Root() }; - var new1dd = new RequiredNonPkSingle1MoreDerived - { - Single = new2dd, - Root = new Root(), - DerivedRoot = new Root() - }; - var newRoot = new Root - { - RequiredNonPkSingle = new1, - RequiredNonPkSingleDerived = new1d, - RequiredNonPkSingleMoreDerived = new1dd - }; - Root root = null; - IReadOnlyList entries = null; - RequiredNonPkSingle1 old1 = null; - RequiredNonPkSingle1Derived old1d = null; - RequiredNonPkSingle1MoreDerived old1dd = null; - RequiredNonPkSingle2 old2 = null; - RequiredNonPkSingle2Derived old2d = null; - RequiredNonPkSingle2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new1d, new1dd, new2, new2d, new2dd); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredNonPkGraph(context); - - old1 = root.RequiredNonPkSingle; - old1d = root.RequiredNonPkSingleDerived; - old1dd = root.RequiredNonPkSingleMoreDerived; - old2 = root.RequiredNonPkSingle.Single; - old2d = (RequiredNonPkSingle2Derived)root.RequiredNonPkSingleDerived.Single; - old2dd = (RequiredNonPkSingle2MoreDerived)root.RequiredNonPkSingleMoreDerived.Single; - - context.Set().Remove(old1d); - context.Set().Remove(old1dd); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (RequiredNonPkSingle1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (RequiredNonPkSingle1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2d = (RequiredNonPkSingle2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (RequiredNonPkSingle2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - - new1d.RootId = old1d.RootId; - new1dd.RootId = old1dd.RootId; - new1dd.DerivedRootId = old1dd.DerivedRootId; - } - else - { - new1d.Root = old1d.Root; - new1dd.Root = old1dd.Root; - new1dd.DerivedRoot = old1dd.DerivedRoot; - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredNonPkSingle = new1; - root.RequiredNonPkSingleDerived = new1d; - root.RequiredNonPkSingleMoreDerived = new1dd; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - new1d.DerivedRoot = root; - new1dd.MoreDerivedRoot = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new1.RootId = root.Id; - new1d.DerivedRootId = root.Id; - new1dd.MoreDerivedRootId = root.Id; - } - - if (Fixture.ForceClientNoAction - || deleteOrphansTiming == CascadeTiming.Never) - { - var testCode = deleteOrphansTiming == CascadeTiming.Immediate - ? () => context.ChangeTracker.DetectChanges() - : deleteOrphansTiming == null - ? () => context.ChangeTracker.CascadeChanges() - : (Action)(() => context.SaveChanges()); - - var message = Assert.Throws(testCode).Message; - - Assert.Equal( - 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()); - - Assert.Equal(root.Id, new1.RootId); - Assert.Equal(root.Id, new1d.DerivedRootId); - Assert.Equal(root.Id, new1dd.MoreDerivedRootId); - Assert.Equal(new1.Id, new2.BackId); - Assert.Equal(new1d.Id, new2d.BackId); - Assert.Equal(new1dd.Id, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Null(old2.Back); - Assert.Null(old2d.Back); - Assert.Null(old2dd.Back); - Assert.Equal(old1.Id, old2.BackId); - Assert.Equal(old1d.Id, old2d.BackId); - Assert.Equal(old1dd.Id, old2dd.BackId); - - entries = context.ChangeTracker.Entries().ToList(); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades - && deleteOrphansTiming != CascadeTiming.Never) - { - var loadedRoot = LoadRequiredNonPkGraph(context); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old1d.Id)); - Assert.False(context.Set().Any(e => e.Id == old1dd.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2d.Id)); - Assert.False(context.Set().Any(e => e.Id == old2dd.Id)); - } - }); - } - - [ConditionalTheory] - [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; - OptionalSingle2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalGraph(context); - - old1 = root.OptionalSingle; - old2 = root.OptionalSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.OptionalSingle = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = null; - } - - Assert.False(context.Entry(root).Reference(e => e.OptionalSingle).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Same(old1, old2.Back); - Assert.Null(old1.RootId); - Assert.Equal(old1.Id, old2.BackId); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadOptionalGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - - Assert.Null(loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Null(loaded1.RootId); - Assert.Equal(loaded1.Id, loaded2.BackId); - } - }); - } - - [ConditionalTheory] - [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( - ChangeMechanism changeMechanism, - CascadeTiming? deleteOrphansTiming) - { - Root root = null; - RequiredSingle1 old1 = null; - RequiredSingle2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredGraph(context); - - old1 = root.RequiredSingle; - old2 = root.RequiredSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredSingle = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - Assert.False(context.Entry(root).Reference(e => e.RequiredSingle).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Equal(old1.Id, old2.Id); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var loadedRoot = LoadRequiredGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - } - }); - } - - [ConditionalTheory] - [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; - RequiredNonPkSingle2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredNonPkGraph(context); - - old1 = root.RequiredNonPkSingle; - old2 = root.RequiredNonPkSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredNonPkSingle = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - if (Fixture.ForceClientNoAction - || deleteOrphansTiming == CascadeTiming.Never) - { - var testCode = deleteOrphansTiming == CascadeTiming.Immediate - ? () => context.ChangeTracker.DetectChanges() - : deleteOrphansTiming == null - ? () => context.ChangeTracker.CascadeChanges() - : (Action)(() => context.SaveChanges()); - - var message = Assert.Throws(testCode).Message; - - Assert.Equal( - 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()); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Equal(old1.Id, old2.BackId); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades - && deleteOrphansTiming != CascadeTiming.Never) - { - var loadedRoot = LoadRequiredNonPkGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - } - }); - } - - [ConditionalTheory] - [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; - OptionalSingle1 old1 = null; - OptionalSingle2 old2 = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingRoot) - { - context.AddRange(newRoot); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalGraph(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - old1 = root.OptionalSingle; - old2 = root.OptionalSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.OptionalSingle = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = context.Entry(newRoot).Property(e => e.Id).CurrentValue; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.OptionalSingle); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Equal(newRoot.Id, old1.RootId); - Assert.Equal(old1.Id, old2.BackId); - }, - context => - { - var loadedRoot = LoadOptionalGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Equal(newRoot.Id, loaded1.RootId); - Assert.Equal(loaded1.Id, loaded2.BackId); - }); - } - - [ConditionalTheory] - [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(); - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingRoot) - { - context.AddRange(newRoot); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredGraph(context); - - Assert.False(context.ChangeTracker.HasChanges()); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - Assert.Equal( - CoreStrings.KeyReadOnly("Id", typeof(RequiredSingle1).Name), - Assert.Throws( - () => - { - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.RequiredSingle = root.RequiredSingle; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - root.RequiredSingle.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - root.RequiredSingle.Id = newRoot.Id; - } - - newRoot.RequiredSingle = root.RequiredSingle; - - context.SaveChanges(); - }).Message); - }); - } - - [ConditionalTheory] - [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; - RequiredNonPkSingle1 old1 = null; - RequiredNonPkSingle2 old2 = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingRoot) - { - context.AddRange(newRoot); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredNonPkGraph(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - old1 = root.RequiredNonPkSingle; - old2 = root.RequiredNonPkSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.RequiredNonPkSingle = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = context.Entry(newRoot).Property(e => e.Id).CurrentValue; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Equal(newRoot.Id, old1.RootId); - Assert.Equal(old1.Id, old2.BackId); - }, - context => - { - var loadedRoot = LoadRequiredNonPkGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Equal(newRoot.Id, loaded1.RootId); - Assert.Equal(loaded1.Id, loaded2.BackId); - }); - } - - [ConditionalTheory] - [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; - var compositeCount = 0; - OptionalAk1 oldParent = null; - OptionalComposite2 oldComposite1 = null; - OptionalComposite2 oldComposite2 = null; - Optional1 newParent = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (!useExistingParent) - { - newParent = new Optional1 - { - CompositeChildren = new ObservableHashSet(ReferenceEqualityComparer.Instance) - }; - - context.Set().Add(newParent); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalOneToManyGraph(context); - - compositeCount = context.Set().Count(); - - oldParent = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); - - oldComposite1 = oldParent.CompositeChildren.OrderBy(e => e.Id).First(); - oldComposite2 = oldParent.CompositeChildren.OrderBy(e => e.Id).Last(); - - if (useExistingParent) - { - newParent = root.OptionalChildren.OrderBy(e => e.Id).Last(); - } - else - { - newParent = context.Set().Single(e => e.Id == newParent.Id); - newParent.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - oldParent.CompositeChildren.Remove(oldComposite1); - newParent.CompositeChildren.Add(oldComposite1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - oldComposite1.Parent = null; - oldComposite1.Parent2 = newParent; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - oldComposite1.ParentId = null; - oldComposite1.Parent2Id = newParent.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Same(oldComposite2, oldParent.CompositeChildren.Single()); - Assert.Same(oldParent, oldComposite2.Parent); - Assert.Equal(oldParent.Id, oldComposite2.ParentId); - Assert.Null(oldComposite2.Parent2); - Assert.Null(oldComposite2.Parent2Id); - - Assert.Same(oldComposite1, newParent.CompositeChildren.Single()); - Assert.Same(newParent, oldComposite1.Parent2); - Assert.Equal(newParent.Id, oldComposite1.Parent2Id); - Assert.Null(oldComposite1.Parent); - Assert.Null(oldComposite1.ParentId); - - entries = context.ChangeTracker.Entries().ToList(); - - Assert.Equal(compositeCount, context.Set().Count()); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadOptionalOneToManyGraph(context); - - Assert.False(context.ChangeTracker.HasChanges()); - - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - oldParent = context.Set().Single(e => e.Id == oldParent.Id); - newParent = context.Set().Single(e => e.Id == newParent.Id); - - oldComposite1 = context.Set().Single(e => e.Id == oldComposite1.Id); - oldComposite2 = context.Set().Single(e => e.Id == oldComposite2.Id); - - Assert.Same(oldComposite2, oldParent.CompositeChildren.Single()); - Assert.Same(oldParent, oldComposite2.Parent); - Assert.Equal(oldParent.Id, oldComposite2.ParentId); - Assert.Null(oldComposite2.Parent2); - Assert.Null(oldComposite2.Parent2Id); - - Assert.Same(oldComposite1, newParent.CompositeChildren.Single()); - Assert.Same(newParent, oldComposite1.Parent2); - Assert.Equal(newParent.Id, oldComposite1.Parent2Id); - Assert.Null(oldComposite1.Parent); - Assert.Null(oldComposite1.ParentId); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - - Assert.Equal(compositeCount, context.Set().Count()); - } - }); - } - - [ConditionalTheory] - [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; - var childCount = 0; - RequiredComposite1 oldParent = null; - OptionalOverlapping2 oldChild1 = null; - OptionalOverlapping2 oldChild2 = null; - RequiredComposite1 newParent = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (!useExistingParent) - { - newParent = new RequiredComposite1 - { - Id = 3, - Parent = context.Set().Single(IsTheRoot), - CompositeChildren = new ObservableHashSet(ReferenceEqualityComparer.Instance) - { - new OptionalOverlapping2 { Id = 5 }, new OptionalOverlapping2 { Id = 6 } - } - }; - - context.Set().Add(newParent); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredCompositeGraph(context); - - childCount = context.Set().Count(); - - oldParent = root.RequiredCompositeChildren.OrderBy(e => e.Id).First(); - - oldChild1 = oldParent.CompositeChildren.OrderBy(e => e.Id).First(); - oldChild2 = oldParent.CompositeChildren.OrderBy(e => e.Id).Last(); - - Assert.Equal(useExistingParent ? 2 : 3, root.RequiredCompositeChildren.Count()); - - if (useExistingParent) - { - newParent = root.RequiredCompositeChildren.OrderBy(e => e.Id).Last(); - } - else - { - newParent = context.Set().Single(e => e.Id == newParent.Id); - newParent.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - oldParent.CompositeChildren.Remove(oldChild1); - newParent.CompositeChildren.Add(oldChild1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - oldChild1.Parent = newParent; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - oldChild1.ParentId = newParent.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Same(oldChild2, oldParent.CompositeChildren.Single()); - Assert.Same(oldParent, oldChild2.Parent); - Assert.Equal(oldParent.Id, oldChild2.ParentId); - Assert.Equal(oldParent.ParentAlternateId, oldChild2.ParentAlternateId); - Assert.Equal(root.AlternateId, oldChild2.ParentAlternateId); - Assert.Same(root, oldChild2.Root); - - Assert.Equal(3, newParent.CompositeChildren.Count); - Assert.Same(oldChild1, newParent.CompositeChildren.Single(e => e.Id == oldChild1.Id)); - Assert.Same(newParent, oldChild1.Parent); - Assert.Equal(newParent.Id, oldChild1.ParentId); - Assert.Equal(oldParent.ParentAlternateId, oldChild1.ParentAlternateId); - Assert.Equal(root.AlternateId, oldChild1.ParentAlternateId); - Assert.Same(root, oldChild1.Root); - - entries = context.ChangeTracker.Entries().ToList(); - - Assert.Equal(childCount, context.Set().Count()); - }, - context => - { - var loadedRoot = LoadRequiredCompositeGraph(context); - - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - oldParent = context.Set().Single(e => e.Id == oldParent.Id); - newParent = context.Set().Single(e => e.Id == newParent.Id); - - oldChild1 = context.Set().Single(e => e.Id == oldChild1.Id); - oldChild2 = context.Set().Single(e => e.Id == oldChild2.Id); - - Assert.Same(oldChild2, oldParent.CompositeChildren.Single()); - Assert.Same(oldParent, oldChild2.Parent); - Assert.Equal(oldParent.Id, oldChild2.ParentId); - Assert.Equal(oldParent.ParentAlternateId, oldChild2.ParentAlternateId); - Assert.Equal(root.AlternateId, oldChild2.ParentAlternateId); - - Assert.Same(oldChild1, newParent.CompositeChildren.Single(e => e.Id == oldChild1.Id)); - Assert.Same(newParent, oldChild1.Parent); - Assert.Equal(newParent.Id, oldChild1.ParentId); - Assert.Equal(oldParent.ParentAlternateId, oldChild1.ParentAlternateId); - Assert.Equal(root.AlternateId, oldChild1.ParentAlternateId); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - - Assert.Equal(childCount, context.Set().Count()); - }); - } - - [ConditionalTheory] - [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(); - - var childCount = context.Set().Count(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - context.Entry(parent).Collection(p => p.CompositeChildren).IsModified = true; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - context.Entry(child).Reference(c => c.Parent).IsModified = true; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - context.Entry(child).Property(c => c.ParentId).IsModified = true; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Same(child, parent.CompositeChildren.OrderBy(e => e.Id).First()); - Assert.Same(parent, child.Parent); - Assert.Equal(parent.Id, child.ParentId); - Assert.Equal(parent.ParentAlternateId, child.ParentAlternateId); - Assert.Equal(root.AlternateId, child.ParentAlternateId); - Assert.Same(root, child.Root); - - Assert.Equal(childCount, context.Set().Count()); - }); - } - - [ConditionalTheory] - [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, - CascadeTiming? deleteOrphansTiming) - { - var new1 = new OptionalAk1 { AlternateId = Guid.NewGuid() }; - var new1d = new OptionalAk1Derived { AlternateId = Guid.NewGuid() }; - var new1dd = new OptionalAk1MoreDerived { AlternateId = Guid.NewGuid() }; - var new2a = new OptionalAk2 { AlternateId = Guid.NewGuid() }; - var new2b = new OptionalAk2 { AlternateId = Guid.NewGuid() }; - var new2ca = new OptionalComposite2(); - var new2cb = new OptionalComposite2(); - var new2d = new OptionalAk2Derived { AlternateId = Guid.NewGuid() }; - var new2dd = new OptionalAk2MoreDerived { AlternateId = Guid.NewGuid() }; - Root root = null; - IReadOnlyList entries = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalAkGraph(context); - var existing = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (OptionalAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (OptionalAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2a = context.Set().Single(e => e.Id == new2a.Id); - new2b = context.Set().Single(e => e.Id == new2b.Id); - new2ca = context.Set().Single(e => e.Id == new2ca.Id); - new2cb = context.Set().Single(e => e.Id == new2cb.Id); - new2d = (OptionalAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (OptionalAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Add(existing.Children, new2a); - Add(existing.Children, new2b); - Add(existing.CompositeChildren, new2ca); - Add(existing.CompositeChildren, new2cb); - Add(new1d.Children, new2d); - Add(new1dd.Children, new2dd); - Add(root.OptionalChildrenAk, new1); - Add(root.OptionalChildrenAk, new1d); - Add(root.OptionalChildrenAk, new1dd); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new2a.Parent = existing; - new2b.Parent = existing; - new2ca.Parent = existing; - new2cb.Parent = existing; - new2d.Parent = new1d; - new2dd.Parent = new1dd; - new1.Parent = root; - new1d.Parent = root; - new1dd.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new2a.ParentId = existing.AlternateId; - new2b.ParentId = existing.AlternateId; - new2ca.ParentId = existing.Id; - new2ca.ParentAlternateId = existing.AlternateId; - new2cb.ParentId = existing.Id; - new2cb.ParentAlternateId = existing.AlternateId; - new2d.ParentId = new1d.AlternateId; - new2dd.ParentId = new1dd.AlternateId; - new1.ParentId = root.AlternateId; - new1d.ParentId = root.AlternateId; - new1dd.ParentId = root.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Contains(new2a, existing.Children); - Assert.Contains(new2b, existing.Children); - Assert.Contains(new2ca, existing.CompositeChildren); - Assert.Contains(new2cb, existing.CompositeChildren); - Assert.Contains(new2d, new1d.Children); - Assert.Contains(new2dd, new1dd.Children); - Assert.Contains(new1, root.OptionalChildrenAk); - Assert.Contains(new1d, root.OptionalChildrenAk); - Assert.Contains(new1dd, root.OptionalChildrenAk); - - Assert.Same(existing, new2a.Parent); - Assert.Same(existing, new2b.Parent); - Assert.Same(existing, new2ca.Parent); - Assert.Same(existing, new2cb.Parent); - Assert.Same(new1d, new2d.Parent); - Assert.Same(new1dd, new2dd.Parent); - Assert.Same(root, existing.Parent); - Assert.Same(root, new1d.Parent); - Assert.Same(root, new1dd.Parent); - - Assert.Equal(existing.AlternateId, new2a.ParentId); - Assert.Equal(existing.AlternateId, new2b.ParentId); - Assert.Equal(existing.Id, new2ca.ParentId); - Assert.Equal(existing.Id, new2cb.ParentId); - Assert.Equal(existing.AlternateId, new2ca.ParentAlternateId); - Assert.Equal(existing.AlternateId, new2cb.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.ParentId); - Assert.Equal(new1dd.AlternateId, new2dd.ParentId); - Assert.Equal(root.AlternateId, existing.ParentId); - Assert.Equal(root.AlternateId, new1d.ParentId); - Assert.Equal(root.AlternateId, new1dd.ParentId); - - entries = context.ChangeTracker.Entries().ToList(); - }, - context => - { - var loadedRoot = LoadOptionalAkGraph(context); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - }); - } - - [ConditionalTheory] - [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, - CascadeTiming? deleteOrphansTiming) - { - var newRoot = new Root { AlternateId = Guid.NewGuid() }; - var new1 = new RequiredAk1 { AlternateId = Guid.NewGuid(), Parent = newRoot }; - var new1d = new RequiredAk1Derived { AlternateId = Guid.NewGuid(), Parent = newRoot }; - var new1dd = new RequiredAk1MoreDerived { AlternateId = Guid.NewGuid(), Parent = newRoot }; - var new2a = new RequiredAk2 { AlternateId = Guid.NewGuid(), Parent = new1 }; - var new2b = new RequiredAk2 { AlternateId = Guid.NewGuid(), Parent = new1 }; - var new2ca = new RequiredComposite2 { Parent = new1 }; - var new2cb = new RequiredComposite2 { Parent = new1 }; - var new2d = new RequiredAk2Derived { AlternateId = Guid.NewGuid(), Parent = new1 }; - var new2dd = new RequiredAk2MoreDerived { AlternateId = Guid.NewGuid(), Parent = new1 }; - Root root = null; - IReadOnlyList entries = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredAkGraph(context); - var existing = root.RequiredChildrenAk.OrderBy(e => e.Id).First(); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (RequiredAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (RequiredAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2a = context.Set().Single(e => e.Id == new2a.Id); - new2b = context.Set().Single(e => e.Id == new2b.Id); - new2ca = context.Set().Single(e => e.Id == new2ca.Id); - new2cb = context.Set().Single(e => e.Id == new2cb.Id); - new2d = (RequiredAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (RequiredAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - new1.Parent = null; - new1d.Parent = null; - new1dd.Parent = null; - - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Add(existing.Children, new2a); - Add(existing.Children, new2b); - Add(existing.CompositeChildren, new2ca); - Add(existing.CompositeChildren, new2cb); - Add(new1d.Children, new2d); - Add(new1dd.Children, new2dd); - Add(root.RequiredChildrenAk, new1); - Add(root.RequiredChildrenAk, new1d); - Add(root.RequiredChildrenAk, new1dd); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new2a.Parent = existing; - new2b.Parent = existing; - new2ca.Parent = existing; - new2cb.Parent = existing; - new2d.Parent = new1d; - new2dd.Parent = new1dd; - new1.Parent = root; - new1d.Parent = root; - new1dd.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new2a.ParentId = existing.AlternateId; - new2b.ParentId = existing.AlternateId; - new2ca.ParentId = existing.Id; - new2cb.ParentId = existing.Id; - new2ca.ParentAlternateId = existing.AlternateId; - new2cb.ParentAlternateId = existing.AlternateId; - new2d.ParentId = new1d.AlternateId; - new2dd.ParentId = new1dd.AlternateId; - new1.ParentId = root.AlternateId; - new1d.ParentId = root.AlternateId; - new1dd.ParentId = root.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Contains(new2a, existing.Children); - Assert.Contains(new2b, existing.Children); - Assert.Contains(new2ca, existing.CompositeChildren); - Assert.Contains(new2cb, existing.CompositeChildren); - Assert.Contains(new2d, new1d.Children); - Assert.Contains(new2dd, new1dd.Children); - Assert.Contains(new1, root.RequiredChildrenAk); - Assert.Contains(new1d, root.RequiredChildrenAk); - Assert.Contains(new1dd, root.RequiredChildrenAk); - - Assert.Same(existing, new2a.Parent); - Assert.Same(existing, new2b.Parent); - Assert.Same(existing, new2ca.Parent); - Assert.Same(existing, new2cb.Parent); - Assert.Same(new1d, new2d.Parent); - Assert.Same(new1dd, new2dd.Parent); - Assert.Same(root, existing.Parent); - Assert.Same(root, new1d.Parent); - Assert.Same(root, new1dd.Parent); - - Assert.Equal(existing.AlternateId, new2a.ParentId); - Assert.Equal(existing.AlternateId, new2b.ParentId); - Assert.Equal(existing.Id, new2ca.ParentId); - Assert.Equal(existing.Id, new2cb.ParentId); - Assert.Equal(existing.AlternateId, new2ca.ParentAlternateId); - Assert.Equal(existing.AlternateId, new2cb.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.ParentId); - Assert.Equal(new1dd.AlternateId, new2dd.ParentId); - Assert.Equal(root.AlternateId, existing.ParentId); - Assert.Equal(root.AlternateId, new1d.ParentId); - Assert.Equal(root.AlternateId, new1dd.ParentId); - - entries = context.ChangeTracker.Entries().ToList(); - }, - context => - { - var loadedRoot = LoadRequiredAkGraph(context); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - }); - } - - [ConditionalTheory] - [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(); - var childCollection = firstChild.Children; - var childCompositeCollection = firstChild.CompositeChildren; - var removed2 = childCollection.OrderByDescending(c => c.Id).First(); - var removed1 = root.OptionalChildrenAk.OrderByDescending(c => c.Id).Skip(1).First(); - var removed2c = childCompositeCollection.OrderByDescending(c => c.Id).First(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Remove(childCollection, removed2); - Remove(childCompositeCollection, removed2c); - Remove(root.OptionalChildrenAk, removed1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - removed2.Parent = null; - removed2c.Parent = null; - removed1.Parent = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - removed2.ParentId = null; - removed2c.ParentId = null; - removed1.ParentId = null; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.DoesNotContain(removed1, root.OptionalChildrenAk); - Assert.DoesNotContain(removed2, childCollection); - Assert.DoesNotContain(removed2c, childCompositeCollection); - - Assert.Null(removed1.Parent); - Assert.Null(removed2.Parent); - Assert.Null(removed2c.Parent); - - Assert.Null(removed1.ParentId); - Assert.Null(removed2.ParentId); - Assert.Null(removed2c.ParentId); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadOptionalAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - Assert.Single(loadedRoot.OptionalChildrenAk); - Assert.Single(loadedRoot.OptionalChildrenAk.OrderBy(c => c.Id).First().Children); - } - }); - } - - [ConditionalTheory] - [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; - RequiredComposite2 removed2c = null; - RequiredAk1 removed1 = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredAkGraph(context); - - var firstChild = root.RequiredChildrenAk.OrderByDescending(c => c.Id).First(); - var childCollection = firstChild.Children; - var childCompositeCollection = firstChild.CompositeChildren; - removed2 = childCollection.OrderBy(c => c.Id).First(); - removed2c = childCompositeCollection.OrderBy(c => c.Id).First(); - removed1 = root.RequiredChildrenAk.OrderByDescending(c => c.Id).Skip(1).First(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Remove(childCollection, removed2); - Remove(childCompositeCollection, removed2c); - Remove(root.RequiredChildrenAk, removed1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - removed2.Parent = null; - removed2c.Parent = null; - removed1.Parent = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - if (Fixture.ForceClientNoAction - || deleteOrphansTiming == CascadeTiming.Never) - { - var testCode = deleteOrphansTiming == CascadeTiming.Immediate - ? () => context.ChangeTracker.DetectChanges() - : deleteOrphansTiming == null - ? () => 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()); - - Assert.DoesNotContain(removed1, root.RequiredChildrenAk); - Assert.DoesNotContain(removed2, childCollection); - Assert.DoesNotContain(removed2c, childCompositeCollection); - - Assert.Null(removed1.Parent); - Assert.Null(removed2.Parent); - Assert.Null(removed2c.Parent); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades - && deleteOrphansTiming != CascadeTiming.Never) - { - var loadedRoot = LoadRequiredAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - Assert.False(context.Set().Any(e => e.Id == removed1.Id)); - Assert.False(context.Set().Any(e => e.Id == removed2.Id)); - Assert.False(context.Set().Any(e => e.Id == removed2c.Id)); - - Assert.Single(loadedRoot.RequiredChildrenAk); - Assert.Single(loadedRoot.RequiredChildrenAk.OrderBy(c => c.Id).First().Children); - Assert.Single(loadedRoot.RequiredChildrenAk.OrderBy(c => c.Id).First().CompositeChildren); - } - }); - } - - [ConditionalTheory] - [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 { AlternateId = Guid.NewGuid() }; - var new2d = new OptionalSingleAk2Derived { AlternateId = Guid.NewGuid() }; - var new2dd = new OptionalSingleAk2MoreDerived { AlternateId = Guid.NewGuid() }; - var new2c = new OptionalSingleComposite2(); - var new1 = new OptionalSingleAk1 - { - AlternateId = Guid.NewGuid(), - Single = new2, - SingleComposite = new2c - }; - var new1d = new OptionalSingleAk1Derived { AlternateId = Guid.NewGuid(), Single = new2d }; - var new1dd = new OptionalSingleAk1MoreDerived { AlternateId = Guid.NewGuid(), Single = new2dd }; - Root root = null; - IReadOnlyList entries = null; - OptionalSingleAk1 old1 = null; - OptionalSingleAk1Derived old1d = null; - OptionalSingleAk1MoreDerived old1dd = null; - OptionalSingleAk2 old2 = null; - OptionalSingleComposite2 old2c = null; - OptionalSingleAk2Derived old2d = null; - OptionalSingleAk2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalAkGraph(context); - - old1 = root.OptionalSingleAk; - old1d = root.OptionalSingleAkDerived; - old1dd = root.OptionalSingleAkMoreDerived; - old2 = root.OptionalSingleAk.Single; - old2c = root.OptionalSingleAk.SingleComposite; - old2d = (OptionalSingleAk2Derived)root.OptionalSingleAkDerived.Single; - old2dd = (OptionalSingleAk2MoreDerived)root.OptionalSingleAkMoreDerived.Single; - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (OptionalSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (OptionalSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2c = context.Set().Single(e => e.Id == new2c.Id); - new2d = (OptionalSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (OptionalSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.OptionalSingleAk = new1; - root.OptionalSingleAkDerived = new1d; - root.OptionalSingleAkMoreDerived = new1dd; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - new1d.DerivedRoot = root; - new1dd.MoreDerivedRoot = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new1.RootId = root.AlternateId; - new1d.DerivedRootId = root.AlternateId; - new1dd.MoreDerivedRootId = root.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(root.AlternateId, new1d.DerivedRootId); - Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1.Id, new2c.BackId); - Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.BackId); - Assert.Equal(new1dd.AlternateId, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1, new2c.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(old1d, old2d.Back); - Assert.Equal(old1dd, old2dd.Back); - Assert.Null(old1.RootId); - Assert.Null(old1d.DerivedRootId); - Assert.Null(old1dd.MoreDerivedRootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - Assert.Equal(old1d.AlternateId, old2d.BackId); - Assert.Equal(old1dd.AlternateId, old2dd.BackId); - - entries = context.ChangeTracker.Entries().ToList(); - }, - context => - { - var loadedRoot = LoadOptionalAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded1d = context.Set().Single(e => e.Id == old1d.Id); - var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2d = context.Set().Single(e => e.Id == old2d.Id); - var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - - Assert.Null(loaded1.Root); - Assert.Null(loaded1d.Root); - Assert.Null(loaded1dd.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Same(loaded1d, loaded2d.Back); - Assert.Same(loaded1dd, loaded2dd.Back); - Assert.Null(loaded1.RootId); - Assert.Null(loaded1d.RootId); - Assert.Null(loaded1dd.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); - Assert.Equal(loaded1d.AlternateId, loaded2d.BackId); - Assert.Equal(loaded1dd.AlternateId, loaded2dd.BackId); - }); - } - - [ConditionalFact] - public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store() - { - var new2 = new OptionalSingleAk2 { AlternateId = Guid.NewGuid() }; - var new2d = new OptionalSingleAk2Derived { AlternateId = Guid.NewGuid() }; - var new2dd = new OptionalSingleAk2MoreDerived { AlternateId = Guid.NewGuid() }; - var new2c = new OptionalSingleComposite2(); - var new1 = new OptionalSingleAk1 - { - AlternateId = Guid.NewGuid(), - Single = new2, - SingleComposite = new2c - }; - var new1d = new OptionalSingleAk1Derived { AlternateId = Guid.NewGuid(), Single = new2d }; - var new1dd = new OptionalSingleAk1MoreDerived { AlternateId = Guid.NewGuid(), Single = new2dd }; - Root root = null; - IReadOnlyList entries = null; - OptionalSingleAk1 old1 = null; - OptionalSingleAk1Derived old1d = null; - OptionalSingleAk1MoreDerived old1dd = null; - OptionalSingleAk2 old2 = null; - OptionalSingleComposite2 old2c = null; - OptionalSingleAk2Derived old2d = null; - OptionalSingleAk2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadOptionalAkGraph(context); - - old1 = root.OptionalSingleAk; - old1d = root.OptionalSingleAkDerived; - old1dd = root.OptionalSingleAkMoreDerived; - old2 = root.OptionalSingleAk.Single; - old2c = root.OptionalSingleAk.SingleComposite; - old2d = (OptionalSingleAk2Derived)root.OptionalSingleAkDerived.Single; - old2dd = (OptionalSingleAk2MoreDerived)root.OptionalSingleAkMoreDerived.Single; - - using (var context2 = CreateContext()) - { - UseTransaction(context2.Database, context.Database.CurrentTransaction); - var root2 = LoadOptionalAkGraph(context2); - - context2.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); - root2.OptionalSingleAk = new1; - root2.OptionalSingleAkDerived = new1d; - root2.OptionalSingleAkMoreDerived = new1dd; - - Assert.True(context2.ChangeTracker.HasChanges()); - - context2.SaveChanges(); - - Assert.False(context2.ChangeTracker.HasChanges()); - } - - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (OptionalSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (OptionalSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2c = context.Set().Single(e => e.Id == new2c.Id); - new2d = (OptionalSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (OptionalSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(root.AlternateId, new1d.DerivedRootId); - Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1.Id, new2c.BackId); - Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.BackId); - Assert.Equal(new1dd.AlternateId, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1, new2c.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(old1d, old2d.Back); - Assert.Equal(old1dd, old2dd.Back); - Assert.Null(old1.RootId); - Assert.Null(old1d.DerivedRootId); - Assert.Null(old1dd.MoreDerivedRootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - Assert.Equal(old1d.AlternateId, old2d.BackId); - Assert.Equal(old1dd.AlternateId, old2dd.BackId); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(root.AlternateId, new1d.DerivedRootId); - Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1.Id, new2c.BackId); - Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.BackId); - Assert.Equal(new1dd.AlternateId, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1, new2c.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(old1d, old2d.Back); - Assert.Equal(old1dd, old2dd.Back); - Assert.Null(old1.RootId); - Assert.Null(old1d.DerivedRootId); - Assert.Null(old1dd.MoreDerivedRootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - Assert.Equal(old1d.AlternateId, old2d.BackId); - Assert.Equal(old1dd.AlternateId, old2dd.BackId); - - entries = context.ChangeTracker.Entries().ToList(); - }, - context => - { - var loadedRoot = LoadOptionalAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded1d = context.Set().Single(e => e.Id == old1d.Id); - var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2d = context.Set().Single(e => e.Id == old2d.Id); - var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - - Assert.Null(loaded1.Root); - Assert.Null(loaded1d.Root); - Assert.Null(loaded1dd.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Same(loaded1d, loaded2d.Back); - Assert.Same(loaded1dd, loaded2dd.Back); - Assert.Null(loaded1.RootId); - Assert.Null(loaded1d.RootId); - Assert.Null(loaded1dd.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); - Assert.Equal(loaded1d.AlternateId, loaded2d.BackId); - Assert.Equal(loaded1dd.AlternateId, loaded2dd.BackId); - }); - } - - [ConditionalTheory] - [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, - CascadeTiming? deleteOrphansTiming) - { - var new2 = new RequiredSingleAk2 { AlternateId = Guid.NewGuid() }; - var new2c = new RequiredSingleComposite2(); - var new1 = new RequiredSingleAk1 - { - AlternateId = Guid.NewGuid(), - Single = new2, - SingleComposite = new2c - }; - var newRoot = new Root { AlternateId = Guid.NewGuid(), RequiredSingleAk = new1 }; - Root root = null; - IReadOnlyList entries = null; - RequiredSingleAk1 old1 = null; - RequiredSingleAk2 old2 = null; - RequiredSingleComposite2 old2c = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new2, new2c); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredAkGraph(context); - - old1 = root.RequiredSingleAk; - old2 = root.RequiredSingleAk.Single; - old2c = root.RequiredSingleAk.SingleComposite; - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2c = context.Set().Single(e => e.Id == new2c.Id); - } - else - { - context.AddRange(new1, new2, new2c); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredSingleAk = new1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - if (Fixture.ForceClientNoAction - || deleteOrphansTiming == CascadeTiming.Never) - { - var testCode = deleteOrphansTiming == CascadeTiming.Immediate - ? () => context.ChangeTracker.DetectChanges() - : deleteOrphansTiming == null - ? () => context.ChangeTracker.CascadeChanges() - : (Action)(() => context.SaveChanges()); - - var message = Assert.Throws(testCode).Message; - - Assert.Equal( - 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()); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1.Id, new2c.BackId); - Assert.Equal(new1.AlternateId, new2c.BackAlternateId); - Assert.Same(root, new1.Root); - Assert.Same(new1, new2.Back); - Assert.Same(new1, new2c.Back); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Null(old2c.Back); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.BackAlternateId); - - entries = context.ChangeTracker.Entries().ToList(); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades - && deleteOrphansTiming != CascadeTiming.Never) - { - var loadedRoot = LoadRequiredAkGraph(context); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2c.Id)); - } - }); - } - - [ConditionalTheory] - [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, - CascadeTiming? deleteOrphansTiming) - { - var new2 = new RequiredNonPkSingleAk2 { AlternateId = Guid.NewGuid() }; - var new2d = new RequiredNonPkSingleAk2Derived { AlternateId = Guid.NewGuid() }; - var new2dd = new RequiredNonPkSingleAk2MoreDerived { AlternateId = Guid.NewGuid() }; - var new1 = new RequiredNonPkSingleAk1 { AlternateId = Guid.NewGuid(), Single = new2 }; - var new1d = new RequiredNonPkSingleAk1Derived - { - AlternateId = Guid.NewGuid(), - Single = new2d, - Root = new Root() - }; - var new1dd = new RequiredNonPkSingleAk1MoreDerived - { - AlternateId = Guid.NewGuid(), - Single = new2dd, - Root = new Root(), - DerivedRoot = new Root() - }; - var newRoot = new Root - { - AlternateId = Guid.NewGuid(), - RequiredNonPkSingleAk = new1, - RequiredNonPkSingleAkDerived = new1d, - RequiredNonPkSingleAkMoreDerived = new1dd - }; - Root root = null; - IReadOnlyList entries = null; - RequiredNonPkSingleAk1 old1 = null; - RequiredNonPkSingleAk1Derived old1d = null; - RequiredNonPkSingleAk1MoreDerived old1dd = null; - RequiredNonPkSingleAk2 old2 = null; - RequiredNonPkSingleAk2Derived old2d = null; - RequiredNonPkSingleAk2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new1d, new1dd, new2, new2d, new2dd); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredNonPkAkGraph(context); - - old1 = root.RequiredNonPkSingleAk; - old1d = root.RequiredNonPkSingleAkDerived; - old1dd = root.RequiredNonPkSingleAkMoreDerived; - old2 = root.RequiredNonPkSingleAk.Single; - old2d = (RequiredNonPkSingleAk2Derived)root.RequiredNonPkSingleAkDerived.Single; - old2dd = (RequiredNonPkSingleAk2MoreDerived)root.RequiredNonPkSingleAkMoreDerived.Single; - - context.Set().Remove(old1d); - context.Set().Remove(old1dd); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (RequiredNonPkSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (RequiredNonPkSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2d = (RequiredNonPkSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (RequiredNonPkSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - - new1d.RootId = old1d.RootId; - new1dd.RootId = old1dd.RootId; - new1dd.DerivedRootId = old1dd.DerivedRootId; - } - else - { - new1d.Root = old1d.Root; - new1dd.Root = old1dd.Root; - new1dd.DerivedRoot = old1dd.DerivedRoot; - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredNonPkSingleAk = new1; - root.RequiredNonPkSingleAkDerived = new1d; - root.RequiredNonPkSingleAkMoreDerived = new1dd; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - new1d.DerivedRoot = root; - new1dd.MoreDerivedRoot = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new1.RootId = root.AlternateId; - new1d.DerivedRootId = root.AlternateId; - new1dd.MoreDerivedRootId = root.AlternateId; - } - - if (Fixture.ForceClientNoAction - || deleteOrphansTiming == CascadeTiming.Never) - { - var testCode = deleteOrphansTiming == CascadeTiming.Immediate - ? () => context.ChangeTracker.DetectChanges() - : deleteOrphansTiming == null - ? () => context.ChangeTracker.CascadeChanges() - : (Action)(() => context.SaveChanges()); - - var message = Assert.Throws(testCode).Message; - - Assert.Equal( - 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()); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(root.AlternateId, new1d.DerivedRootId); - Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1d.AlternateId, new2d.BackId); - Assert.Equal(new1dd.AlternateId, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Null(old2.Back); - Assert.Null(old2d.Back); - Assert.Null(old2dd.Back); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1d.AlternateId, old2d.BackId); - Assert.Equal(old1dd.AlternateId, old2dd.BackId); - - entries = context.ChangeTracker.Entries().ToList(); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades - && deleteOrphansTiming != CascadeTiming.Never) - { - var loadedRoot = LoadRequiredNonPkAkGraph(context); - - AssertEntries(entries, context.ChangeTracker.Entries().ToList()); - AssertKeys(root, loadedRoot); - AssertNavigations(loadedRoot); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old1d.Id)); - Assert.False(context.Set().Any(e => e.Id == old1dd.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2d.Id)); - Assert.False(context.Set().Any(e => e.Id == old2dd.Id)); - } - }); - } - - [ConditionalTheory] - [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; - OptionalSingleAk2 old2 = null; - OptionalSingleComposite2 old2c = null; - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalAkGraph(context); - - old1 = root.OptionalSingleAk; - old2 = root.OptionalSingleAk.Single; - old2c = root.OptionalSingleAk.SingleComposite; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.OptionalSingleAk = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = null; - } - - Assert.False(context.Entry(root).Reference(e => e.OptionalSingleAk).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Null(old1.RootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadOptionalAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - Assert.Null(loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Null(loaded1.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); - } - }); - } - - [ConditionalTheory] - [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; - RequiredSingleAk2 old2 = null; - RequiredSingleComposite2 old2c = null; - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredAkGraph(context); - - old1 = root.RequiredSingleAk; - old2 = root.RequiredSingleAk.Single; - old2c = root.RequiredSingleAk.SingleComposite; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredSingleAk = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - if (Fixture.ForceClientNoAction - || deleteOrphansTiming == CascadeTiming.Never) - { - var testCode = deleteOrphansTiming == CascadeTiming.Immediate - ? () => context.ChangeTracker.DetectChanges() - : deleteOrphansTiming == null - ? () => context.ChangeTracker.CascadeChanges() - : (Action)(() => context.SaveChanges()); - - var message = Assert.Throws(testCode).Message; - - Assert.Equal( - 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()); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Null(old2c.Back); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.BackAlternateId); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades - && deleteOrphansTiming != CascadeTiming.Never) - { - var loadedRoot = LoadRequiredAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2c.Id)); - } - }); - } - - [ConditionalTheory] - [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; - RequiredNonPkSingleAk2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredNonPkAkGraph(context); - - old1 = root.RequiredNonPkSingleAk; - old2 = root.RequiredNonPkSingleAk.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredNonPkSingleAk = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - if (Fixture.ForceClientNoAction - || deleteOrphansTiming == CascadeTiming.Never) - { - var testCode = deleteOrphansTiming == CascadeTiming.Immediate - ? () => context.ChangeTracker.DetectChanges() - : deleteOrphansTiming == null - ? () => context.ChangeTracker.CascadeChanges() - : (Action)(() => context.SaveChanges()); - - var message = Assert.Throws(testCode).Message; - - Assert.Equal( - 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()); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Equal(old1.AlternateId, old2.BackId); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades - && deleteOrphansTiming != CascadeTiming.Never) - { - var loadedRoot = LoadRequiredNonPkAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - } - }); - } - - [ConditionalTheory] - [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 { AlternateId = Guid.NewGuid() }; - Root root = null; - OptionalSingleAk1 old1 = null; - OptionalSingleAk2 old2 = null; - OptionalSingleComposite2 old2c = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingRoot) - { - context.Add(newRoot); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadOptionalAkGraph(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - old1 = root.OptionalSingleAk; - old2 = root.OptionalSingleAk.Single; - old2c = root.OptionalSingleAk.SingleComposite; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.OptionalSingleAk = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = newRoot.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.OptionalSingleAk); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(newRoot.AlternateId, old1.RootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - }, - context => - { - var loadedRoot = LoadOptionalAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Equal(newRoot.AlternateId, loaded1.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); - }); - } - - [ConditionalTheory] - [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 { AlternateId = Guid.NewGuid() }; - Root root = null; - RequiredSingleAk1 old1 = null; - RequiredSingleAk2 old2 = null; - RequiredSingleComposite2 old2c = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingRoot) - { - context.Add(newRoot); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredAkGraph(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - old1 = root.RequiredSingleAk; - old2 = root.RequiredSingleAk.Single; - old2c = root.RequiredSingleAk.SingleComposite; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.RequiredSingleAk = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = newRoot.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.RequiredSingleAk); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(newRoot.AlternateId, old1.RootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.BackAlternateId); - }, - context => - { - var loadedRoot = LoadRequiredAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Equal(newRoot.AlternateId, loaded1.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.BackAlternateId); - }); - } - - [ConditionalTheory] - [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 { AlternateId = Guid.NewGuid() }; - Root root = null; - RequiredNonPkSingleAk1 old1 = null; - RequiredNonPkSingleAk2 old2 = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (useExistingRoot) - { - context.Add(newRoot); - context.SaveChanges(); - } - }, - context => - { - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - root = LoadRequiredNonPkAkGraph(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - old1 = root.RequiredNonPkSingleAk; - old2 = root.RequiredNonPkSingleAk.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.RequiredNonPkSingleAk = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = newRoot.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Equal(newRoot.AlternateId, old1.RootId); - Assert.Equal(old1.AlternateId, old2.BackId); - }, - context => - { - var loadedRoot = LoadRequiredNonPkAkGraph(context); - - AssertKeys(root, loadedRoot); - AssertPossiblyNullNavigations(loadedRoot); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Equal(newRoot.AlternateId, loaded1.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - }); - } - - [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_dependents_are_cascade_deleted( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredGraph(context); - - Assert.Equal(2, root.RequiredChildren.Count()); - - var removed = root.RequiredChildren.First(); - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - 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.ForceClientNoAction ? EntityState.Unchanged : EntityState.Deleted))); - } - - if (Fixture.ForceClientNoAction) - { - 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.Single(root.RequiredChildren); - 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredGraph(context); - - Assert.Single(root.RequiredChildren); - 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))); - } - }); - } - - [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(); - - Assert.Equal(2, parent.Children.Count()); - var removed = parent.Children.First(); - - removedId = removed.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(parent.Children); - Assert.DoesNotContain(removedId, parent.Children.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - Assert.Same(parent, removed.Parent); - }, - context => - { - var root = LoadRequiredGraph(context); - var parent = root.RequiredChildren.First(); - - Assert.Single(parent.Children); - Assert.DoesNotContain(removedId, parent.Children.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - }); - } - - [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_dependents_are_orphaned( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadOptionalGraph(context); - - Assert.Equal(2, root.OptionalChildren.Count()); - - var removed = root.OptionalChildren.First(); - - removedId = removed.Id; - var orphaned = removed.Children.ToList(); - orphanedIds = orphaned.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - 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.ForceClientNoAction ? EntityState.Unchanged : EntityState.Modified))); - } - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - var root = LoadOptionalGraph(context); - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - } - }); - } - - [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(); - - Assert.Equal(2, parent.Children.Count()); - - var removed = parent.Children.First(); - removedId = removed.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(parent.Children); - Assert.DoesNotContain(removedId, parent.Children.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - Assert.Same(parent, removed.Parent); - }, - context => - { - var root = LoadOptionalGraph(context); - var parent = root.OptionalChildren.First(); - - Assert.Single(parent.Children); - Assert.DoesNotContain(removedId, parent.Children.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - }); - } - - [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_are_orphaned( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadOptionalGraph(context); - - var removed = root.OptionalSingle; - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - - Assert.Equal( - Fixture.ForceClientNoAction ? EntityState.Unchanged : EntityState.Modified, context.Entry(orphaned).State); - } - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - var root = LoadOptionalGraph(context); - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - } - }); - } - - [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; - - var removed = parent.Single; - - removedId = removed.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Same(parent, removed.Back); - }, - context => - { - var root = LoadOptionalGraph(context); - var parent = root.OptionalSingle; - - Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - }); - } - - [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_are_cascade_deleted( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredGraph(context); - - var removed = root.RequiredSingle; - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - - Assert.Equal( - Fixture.ForceClientNoAction ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); - } - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredGraph(context); - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - - var removed = parent.Single; - - removedId = removed.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Same(parent, removed.Back); - }, - context => - { - var root = LoadRequiredGraph(context); - var parent = root.RequiredSingle; - - Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - }); - } - - [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_are_cascade_deleted( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredNonPkGraph(context); - - var removed = root.RequiredNonPkSingle; - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - - Assert.Equal( - Fixture.ForceClientNoAction ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); - } - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Null(root.RequiredNonPkSingle); - - 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); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredNonPkGraph(context); - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - - var removed = parent.Single; - - removedId = removed.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Same(parent, removed.Back); - }, - context => - { - var root = LoadRequiredNonPkGraph(context); - var parent = root.RequiredNonPkSingle; - - Assert.Null(parent.Single); - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - }); - } - - [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_dependents_with_alternate_key_are_orphaned( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadOptionalAkGraph(context); - - Assert.Equal(2, root.OptionalChildrenAk.Count()); - - var removed = root.OptionalChildrenAk.OrderBy(c => c.Id).First(); - - removedId = removed.Id; - var orphaned = removed.Children.ToList(); - orphanedIds = orphaned.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - 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.ForceClientNoAction ? EntityState.Unchanged : EntityState.Modified))); - } - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - var root = LoadOptionalAkGraph(context); - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - } - }); - } - - [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_dependents_with_alternate_key_are_cascade_deleted( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - List orphanedIdCs = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredAkGraph(context); - - Assert.Equal(2, root.RequiredChildrenAk.Count()); - - var removed = root.RequiredChildrenAk.OrderBy(c => c.Id).First(); - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - var cascadeRemovedC = removed.CompositeChildren.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - Assert.Equal(2, orphanedIdCs.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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(); - - if (Fixture.ForceClientNoAction) - { - 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.ForceClientNoAction) - { - 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.Single(root.RequiredChildrenAk); - 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredAkGraph(context); - - Assert.Single(root.RequiredChildrenAk); - 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))); - } - }); - } - - [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_with_alternate_key_are_orphaned( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadOptionalAkGraph(context); - - var removed = root.OptionalSingleAk; - - removedId = removed.Id; - var orphaned = removed.Single; - var orphanedC = removed.SingleComposite; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; - - context.Remove(removed); - - 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.ForceClientNoAction) - { - 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.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - var root = LoadOptionalAkGraph(context); - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); - } - }); - } - - [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_with_alternate_key_are_cascade_deleted( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredAkGraph(context); - - var removed = root.RequiredSingleAk; - - removedId = removed.Id; - var orphaned = removed.Single; - var orphanedC = removed.SingleComposite; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; - - context.Remove(removed); - - 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.ForceClientNoAction) - { - 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.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); - - Assert.Null(root.RequiredSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredAkGraph(context); - - 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)); - } - }); - } - - [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_with_alternate_key_are_cascade_deleted( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredNonPkAkGraph(context); - - var removed = root.RequiredNonPkSingleAk; - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - - Assert.Equal( - Fixture.ForceClientNoAction ? EntityState.Unchanged : EntityState.Deleted, context.Entry(orphaned).State); - } - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Null(root.RequiredNonPkSingleAk); - - 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); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredNonPkAkGraph(context); - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_dependents_are_cascade_deleted_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadRequiredGraph(context).RequiredChildren.First(); - - removedId = removed.Id; - orphanedIds = removed.Children.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = context.Set().Include(e => e.RequiredChildren).Single(IsTheRoot); - context.Set().Load(); - - var removed = root.RequiredChildren.Single(e => e.Id == removedId); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction - || Fixture.NoStoreCascades) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(root.RequiredChildren); - 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.Same(root, removed.Parent); - Assert.Empty(removed.Children); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var root = LoadRequiredGraph(context); - - Assert.Single(root.RequiredChildren); - 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))); - } - }); - } - - [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_are_cascade_deleted_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadRequiredGraph(context).RequiredSingle; - - removedId = removed.Id; - orphanedId = removed.Single.Id; - }, - 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; - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction - || Fixture.NoStoreCascades) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var root = LoadRequiredGraph(context); - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_are_cascade_deleted_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadRequiredNonPkGraph(context).RequiredNonPkSingle; - - removedId = removed.Id; - orphanedId = removed.Single.Id; - }, - 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; - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction - || Fixture.NoStoreCascades) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(root.RequiredNonPkSingle); - - 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); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var root = LoadRequiredNonPkGraph(context); - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_dependents_with_alternate_key_are_cascade_deleted_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - List orphanedIdCs = null; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadRequiredAkGraph(context).RequiredChildrenAk.First(); - - removedId = removed.Id; - orphanedIds = removed.Children.Select(e => e.Id).ToList(); - orphanedIdCs = removed.CompositeChildren.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - Assert.Equal(2, orphanedIdCs.Count); - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = context.Set().Include(e => e.RequiredChildrenAk).Single(IsTheRoot); - context.Set().Load(); - - var removed = root.RequiredChildrenAk.Single(e => e.Id == removedId); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction - || Fixture.NoStoreCascades) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(root.RequiredChildrenAk); - 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.Same(root, removed.Parent); - Assert.Empty(removed.Children); // Never loaded - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var root = LoadRequiredAkGraph(context); - - Assert.Single(root.RequiredChildrenAk); - 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))); - } - }); - } - - [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_with_alternate_key_are_cascade_deleted_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadRequiredAkGraph(context).RequiredSingleAk; - - removedId = removed.Id; - orphanedId = removed.Single.Id; - orphanedIdC = removed.SingleComposite.Id; - }, - 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; - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction - || Fixture.NoStoreCascades) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(root.RequiredSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var root = LoadRequiredAkGraph(context); - - 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)); - } - }); - } - - [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_with_alternate_key_are_cascade_deleted_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadRequiredNonPkAkGraph(context).RequiredNonPkSingleAk; - - removedId = removed.Id; - orphanedId = removed.Single.Id; - }, - 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; - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction - || Fixture.NoStoreCascades) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(root.RequiredNonPkSingleAk); - - 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); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && !Fixture.NoStoreCascades) - { - var root = LoadRequiredNonPkAkGraph(context); - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_dependents_are_orphaned_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadOptionalGraph(context).OptionalChildren.First(); - - removedId = removed.Id; - orphanedIds = removed.Children.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = context.Set().Include(e => e.OptionalChildren).Single(IsTheRoot); - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - - var removed = root.OptionalChildren.First(e => e.Id == removedId); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIds.Count, orphaned.Count); - Assert.True(orphaned.All(e => e.ParentId == null)); - - Assert.Same(root, removed.Parent); - Assert.Empty(removed.Children); // Never loaded - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - var root = LoadOptionalGraph(context); - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIds.Count, orphaned.Count); - Assert.True(orphaned.All(e => e.ParentId == null)); - } - }); - } - - [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_are_orphaned_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadOptionalGraph(context).OptionalSingle; - - removedId = removed.Id; - orphanedId = removed.Single.Id; - }, - 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; - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - var root = LoadOptionalGraph(context); - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); - } - }); - } - - [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_dependents_with_alternate_key_are_orphaned_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - List orphanedIdCs = null; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadOptionalAkGraph(context).OptionalChildrenAk.OrderBy(c => c.Id).First(); - - removedId = removed.Id; - orphanedIds = removed.Children.Select(e => e.Id).ToList(); - orphanedIdCs = removed.CompositeChildren.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - Assert.Equal(2, orphanedIdCs.Count); - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = context.Set().Include(e => e.OptionalChildrenAk).Single(IsTheRoot); - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - - var removed = root.OptionalChildrenAk.First(e => e.Id == removedId); - - context.Remove(removed); - - foreach (var toOrphan in context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList()) - { - toOrphan.ParentId = null; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIds.Count, orphaned.Count); - Assert.True(orphaned.All(e => e.ParentId == null)); - - var orphanedC = context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIdCs.Count, orphanedC.Count); - Assert.True(orphanedC.All(e => e.ParentId == null)); - - Assert.Same(root, removed.Parent); - Assert.Empty(removed.Children); // Never loaded - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - var root = LoadOptionalAkGraph(context); - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIds.Count, orphaned.Count); - Assert.True(orphaned.All(e => e.ParentId == null)); - - var orphanedC = context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIdCs.Count, orphanedC.Count); - Assert.True(orphanedC.All(e => e.ParentId == null)); - } - }); - } - - [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_with_alternate_key_are_orphaned_in_store( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var removed = LoadOptionalAkGraph(context).OptionalSingleAk; - - removedId = removed.Id; - orphanedId = removed.Single.Id; - orphanedIdC = removed.SingleComposite.Id; - }, - 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; - var orphaned = removed.Single; - - context.Remove(removed); - - // Cannot have SET NULL action in the store because one of the FK columns - // is not nullable, so need to do this on the EF side. - context.Set().Single(e => e.Id == orphanedIdC).BackId = null; - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); - Assert.Null(context.Set().Single(e => e.Id == orphanedIdC).BackId); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - var root = LoadOptionalAkGraph(context); - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); - Assert.Null(context.Set().Single(e => e.Id == orphanedIdC).BackId); - } - }); - } - - [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_dependents_are_cascade_deleted_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRequiredGraph(context); - - Assert.Equal(2, root.RequiredChildren.Count()); - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var removed = root.RequiredChildren.First(); - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - 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.ForceClientNoAction - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == expectedState)); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRequiredGraph(context); - - Assert.Single(root.RequiredChildren); - 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))); - } - }); - } - - [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_dependents_are_orphaned_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadOptionalGraph(context); - - Assert.Equal(2, root.OptionalChildren.Count()); - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var removed = root.OptionalChildren.First(); - - removedId = removed.Id; - var orphaned = removed.Children.ToList(); - orphanedIds = orphaned.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - 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.ForceClientNoAction - ? EntityState.Modified - : EntityState.Unchanged; - - Assert.True(orphaned.All(e => context.Entry(e).State == expectedState)); - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); - - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - root = LoadOptionalGraph(context); - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - } - }); - } - - [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_are_orphaned_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => root = LoadOptionalGraph(context), - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var removed = root.OptionalSingle; - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - } - - var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate - || cascadeDeleteTiming == null) - && !Fixture.ForceClientNoAction - ? EntityState.Modified - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - root = LoadOptionalGraph(context); - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - } - }); - } - - [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_are_cascade_deleted_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => root = LoadRequiredGraph(context), - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var removed = root.RequiredSingle; - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - } - - var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate - || cascadeDeleteTiming == null) - && !Fixture.ForceClientNoAction - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => root = LoadRequiredGraph(context), - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_are_cascade_deleted_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => root = LoadRequiredNonPkGraph(context), - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var removed = root.RequiredNonPkSingle; - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - } - - var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate - || cascadeDeleteTiming == null) - && !Fixture.ForceClientNoAction - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRequiredNonPkGraph(context); - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_dependents_with_alternate_key_are_orphaned_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - List orphanedIdCs = null; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadOptionalAkGraph(context); - - Assert.Equal(2, root.OptionalChildrenAk.Count()); - }, - 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; - var orphaned = removed.Children.ToList(); - var orphanedC = removed.CompositeChildren.ToList(); - 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); - - 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.ForceClientNoAction - ? 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()); - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - root = LoadOptionalAkGraph(context); - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - Assert.Equal(orphanedIdCs.Count, context.Set().Count(e => orphanedIdCs.Contains(e.Id))); - } - }); - } - - [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_dependents_with_alternate_key_are_cascade_deleted_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - List orphanedIdCs = null; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRequiredAkGraph(context); - - Assert.Equal(2, root.RequiredChildrenAk.Count()); - }, - 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; - var cascadeRemoved = removed.Children.ToList(); - var cascadeRemovedC = removed.CompositeChildren.ToList(); - 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); - - 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.ForceClientNoAction - ? 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()); - - if (Fixture.ForceClientNoAction) - { - 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRequiredAkGraph(context); - - Assert.Single(root.RequiredChildrenAk); - 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))); - } - }); - } - - [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_with_alternate_key_are_orphaned_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - var orphanedIdC = 0; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => root = LoadOptionalAkGraph(context), - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var removed = root.OptionalSingleAk; - - removedId = removed.Id; - var orphaned = removed.Single; - var orphanedC = removed.SingleComposite; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).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.ForceClientNoAction - ? EntityState.Modified - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - Assert.Equal(expectedState, context.Entry(orphanedC).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction) - { - root = LoadOptionalAkGraph(context); - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); - } - }); - } - - [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_with_alternate_key_are_cascade_deleted_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - var orphanedIdC = 0; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => root = LoadRequiredAkGraph(context), - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var removed = root.RequiredSingleAk; - - removedId = removed.Id; - var orphaned = removed.Single; - var orphanedC = removed.SingleComposite; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).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.ForceClientNoAction - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - Assert.Equal(expectedState, context.Entry(orphanedC).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.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); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRequiredAkGraph(context); - - 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)); - } - }); - } - - [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_with_alternate_key_are_cascade_deleted_starting_detached( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - Root root = null; - - ExecuteWithStrategyInTransaction( - context => root = LoadRequiredNonPkAkGraph(context), - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var removed = root.RequiredNonPkSingleAk; - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - } - - var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate - || cascadeDeleteTiming == null) - && !Fixture.ForceClientNoAction - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRequiredNonPkAkGraph(context); - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_dependents_are_cascade_detached_when_Added( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredGraph(context); - - Assert.Equal(2, root.RequiredChildren.Count()); - - var removed = root.RequiredChildren.First(); - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - var added = new Required2(); - Add(removed.Children, added); - - if (context.ChangeTracker.AutoDetectChangesEnabled) - { - 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)); - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - 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.ForceClientNoAction) - { - 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()); - - if (Fixture.ForceClientNoAction) - { - 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.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()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredGraph(context); - - Assert.Single(root.RequiredChildren); - 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))); - } - }); - } - - [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_are_cascade_detached_when_Added( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredGraph(context); - - var removed = root.RequiredSingle; - - removedId = removed.Id; - var orphaned = removed.Single; - - // Since we're pretending this isn't in the database, make it really not in the database - context.Entry(orphaned).State = EntityState.Deleted; - context.SaveChanges(); - - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - - removed.Single = orphaned; - context.ChangeTracker.DetectChanges(); - orphanedId = orphaned.Id; - - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - } - - var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate - || cascadeDeleteTiming == null) - && !Fixture.ForceClientNoAction - ? EntityState.Detached - : EntityState.Added; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredGraph(context); - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_are_cascade_detached_when_Added( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredNonPkGraph(context); - - var removed = root.RequiredNonPkSingle; - - removedId = removed.Id; - var orphaned = removed.Single; - - // Since we're pretending this isn't in the database, make it really not in the database - context.Entry(orphaned).State = EntityState.Deleted; - context.SaveChanges(); - - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - - removed.Single = orphaned; - context.ChangeTracker.DetectChanges(); - context.Entry(orphaned).State = EntityState.Added; - orphanedId = orphaned.Id; - - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - } - - var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate - || cascadeDeleteTiming == null) - && !Fixture.ForceClientNoAction - ? EntityState.Detached - : EntityState.Added; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredNonPkGraph(context); - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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_dependents_with_alternate_key_are_cascade_detached_when_Added( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - List orphanedIds = null; - List orphanedIdCs = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredAkGraph(context); - - Assert.Equal(2, root.RequiredChildrenAk.Count()); - - var removed = root.RequiredChildrenAk.OrderBy(c => c.Id).First(); - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - var cascadeRemovedC = removed.CompositeChildren.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - Assert.Equal(2, orphanedIdCs.Count); - - var added = new RequiredAk2(); - var addedC = new RequiredComposite2(); - Add(removed.Children, added); - Add(removed.CompositeChildren, addedC); - - if (context.ChangeTracker.AutoDetectChangesEnabled) - { - context.ChangeTracker.DetectChanges(); - } - - Assert.Equal(EntityState.Unchanged, 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)); - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - 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.ForceClientNoAction) - { - 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()); - - if (Fixture.ForceClientNoAction) - { - 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.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()); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredAkGraph(context); - - Assert.Single(root.RequiredChildrenAk); - 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))); - } - }); - } - - [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_with_alternate_key_are_cascade_detached_when_Added( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredAkGraph(context); - - var removed = root.RequiredSingleAk; - removedId = removed.Id; - - var orphaned = removed.Single; - var orphanedC = removed.SingleComposite; - - // Since we're pretending these aren't in the database, make them really not in the database - context.Entry(orphaned).State = EntityState.Deleted; - context.Entry(orphanedC).State = EntityState.Deleted; - context.SaveChanges(); - - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); - - removed.Single = orphaned; - removed.SingleComposite = orphanedC; - context.ChangeTracker.DetectChanges(); - context.Entry(orphaned).State = EntityState.Added; - context.Entry(orphanedC).State = EntityState.Added; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; - - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - Assert.Equal(EntityState.Added, context.Entry(orphanedC).State); - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).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.ForceClientNoAction - ? EntityState.Detached - : EntityState.Added; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - Assert.Equal(expectedState, context.Entry(orphanedC).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.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); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredAkGraph(context); - - 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)); - } - }); - } - - [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_with_alternate_key_are_cascade_detached_when_Added( - CascadeTiming? cascadeDeleteTiming, - CascadeTiming? deleteOrphansTiming) - { - var removedId = 0; - var orphanedId = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming ?? CascadeTiming.Never; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming ?? CascadeTiming.Never; - - var root = LoadRequiredNonPkAkGraph(context); - - var removed = root.RequiredNonPkSingleAk; - - removedId = removed.Id; - var orphaned = removed.Single; - - // Since we're pretending this isn't in the database, make it really not in the database - context.Entry(orphaned).State = EntityState.Deleted; - context.SaveChanges(); - - Assert.Equal(EntityState.Detached, context.Entry(orphaned).State); - - removed.Single = orphaned; - context.ChangeTracker.DetectChanges(); - context.Entry(orphaned).State = EntityState.Added; - orphanedId = orphaned.Id; - - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.Remove(removed); - - Assert.Equal(EntityState.Deleted, context.Entry(removed).State); - - if (cascadeDeleteTiming == null) - { - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.ChangeTracker.CascadeChanges(); - } - - var expectedState = (cascadeDeleteTiming == CascadeTiming.Immediate - || cascadeDeleteTiming == null) - && !Fixture.ForceClientNoAction - ? EntityState.Detached - : EntityState.Added; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (Fixture.ForceClientNoAction) - { - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (!Fixture.ForceClientNoAction - && cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRequiredNonPkAkGraph(context); - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - - 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; - oldId = oldChild.Id; - - context.Remove(oldChild); - - var newChild = new ChildAsAParent(); - parent.ChildAsAParent = newChild; - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == null) - { - context.ChangeTracker.CascadeChanges(); - } - - newId = newChild.Id; - Assert.NotEqual(newId, oldId); - - Assert.Equal(newId, parent.ChildAsAParentId); - Assert.Same(newChild, parent.ChildAsAParent); - - Assert.Equal(EntityState.Detached, context.Entry(oldChild).State); - Assert.Equal(EntityState.Unchanged, context.Entry(newChild).State); - Assert.Equal(EntityState.Unchanged, context.Entry(parent).State); - }, - context => - { - var parent = context.Set().Include(p => p.ChildAsAParent).Single(); - - Assert.Equal(newId, parent.ChildAsAParentId); - Assert.Equal(newId, parent.ChildAsAParent.Id); - Assert.Null(context.Set().Find(oldId)); - }); - } - - [ConditionalFact] - public virtual void Sometimes_not_calling_DetectChanges_when_required_does_not_throw_for_null_ref() - { - ExecuteWithStrategyInTransaction( - context => - { - var dependent = context.Set().Single(); - - dependent.BadCustomerId = null; - - var principal = context.Set().Single(); - - principal.Status++; - - Assert.Null(dependent.BadCustomerId); - Assert.Null(dependent.BadCustomer); - Assert.Empty(principal.BadOrders); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(dependent.BadCustomerId); - Assert.Null(dependent.BadCustomer); - Assert.Empty(principal.BadOrders); - }, - context => - { - var dependent = context.Set().Single(); - var principal = context.Set().Single(); - - Assert.Null(dependent.BadCustomerId); - Assert.Null(dependent.BadCustomer); - Assert.Empty(principal.BadOrders); - }); - } - - [ConditionalFact] - public virtual void Can_add_valid_first_dependent_when_multiple_possible_principal_sides() - { - ExecuteWithStrategyInTransaction( - context => - { - var quizTask = new QuizTask(); - quizTask.Choices.Add(new TaskChoice()); - - context.Add(quizTask); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - }, - context => - { - var quizTask = context.Set().Include(e => e.Choices).Single(); - - Assert.Equal(quizTask.Id, quizTask.Choices.Single().QuestTaskId); - - Assert.Same(quizTask.Choices.Single(), context.Set().Single()); - - Assert.Empty(context.Set().Include(e => e.Choices)); - }); - } - - [ConditionalFact] - public virtual void Can_add_valid_second_dependent_when_multiple_possible_principal_sides() - { - ExecuteWithStrategyInTransaction( - context => - { - var hiddenAreaTask = new HiddenAreaTask(); - hiddenAreaTask.Choices.Add(new TaskChoice()); - - context.Add(hiddenAreaTask); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - }, - context => - { - var hiddenAreaTask = context.Set().Include(e => e.Choices).Single(); - - Assert.Equal(hiddenAreaTask.Id, hiddenAreaTask.Choices.Single().QuestTaskId); - - Assert.Same(hiddenAreaTask.Choices.Single(), context.Set().Single()); - - Assert.Empty(context.Set().Include(e => e.Choices)); - }); - } - - [ConditionalFact] - public virtual void Can_add_multiple_dependents_when_multiple_possible_principal_sides() - { - ExecuteWithStrategyInTransaction( - context => - { - var quizTask = new QuizTask(); - quizTask.Choices.Add(new TaskChoice()); - quizTask.Choices.Add(new TaskChoice()); - - context.Add(quizTask); - - var hiddenAreaTask = new HiddenAreaTask(); - hiddenAreaTask.Choices.Add(new TaskChoice()); - hiddenAreaTask.Choices.Add(new TaskChoice()); - - context.Add(hiddenAreaTask); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - }, - context => - { - var quizTask = context.Set().Include(e => e.Choices).Single(); - var hiddenAreaTask = context.Set().Include(e => e.Choices).Single(); - - Assert.Equal(2, quizTask.Choices.Count); - foreach (var quizTaskChoice in quizTask.Choices) - { - Assert.Equal(quizTask.Id, quizTaskChoice.QuestTaskId); - } - - Assert.Equal(2, hiddenAreaTask.Choices.Count); - foreach (var hiddenAreaTaskChoice in hiddenAreaTask.Choices) - { - Assert.Equal(hiddenAreaTask.Id, hiddenAreaTaskChoice.QuestTaskId); - } - - foreach (var taskChoice in context.Set()) - { - Assert.Equal( - 1, - quizTask.Choices.Count(e => e.Id == taskChoice.Id) - + hiddenAreaTask.Choices.Count(e => e.Id == taskChoice.Id)); - } - }); - } - } -} diff --git a/test/EFCore.Specification.Tests/ProxyGraphUpdatesTestBase.cs b/test/EFCore.Specification.Tests/ProxyGraphUpdatesTestBase.cs deleted file mode 100644 index 95dc46b8c7c..00000000000 --- a/test/EFCore.Specification.Tests/ProxyGraphUpdatesTestBase.cs +++ /dev/null @@ -1,6922 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.EntityFrameworkCore.ChangeTracking; -using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Infrastructure; -using Microsoft.EntityFrameworkCore.Internal; -using Xunit; - -// ReSharper disable InconsistentNaming -// ReSharper disable AccessToModifiedClosure -// ReSharper disable PossibleMultipleEnumeration -namespace Microsoft.EntityFrameworkCore -{ - public abstract partial class ProxyGraphUpdatesTestBase : IClassFixture - where TFixture : ProxyGraphUpdatesTestBase.ProxyGraphUpdatesFixtureBase, new() - { - protected ProxyGraphUpdatesTestBase(TFixture fixture) => Fixture = fixture; - - protected TFixture Fixture { get; } - - [ConditionalFact] - public virtual void Optional_one_to_one_relationships_are_one_to_one() - { - ExecuteWithStrategyInTransaction( - context => - { - var root = context.Set().Single(IsTheRoot); - - root.OptionalSingle = context.CreateProxy(); - - Assert.Throws(() => context.SaveChanges()); - }); - } - - [ConditionalFact] - public virtual void Required_one_to_one_relationships_are_one_to_one() - { - ExecuteWithStrategyInTransaction( - context => - { - var root = context.Set().Single(IsTheRoot); - - root.RequiredSingle = context.CreateProxy(); - - Assert.Throws(() => context.SaveChanges()); - }); - } - - [ConditionalFact] - public virtual void Optional_one_to_one_with_AK_relationships_are_one_to_one() - { - ExecuteWithStrategyInTransaction( - context => - { - var root = context.Set().Single(IsTheRoot); - - root.OptionalSingleAk = context.CreateProxy(); - - Assert.Throws(() => context.SaveChanges()); - }); - } - - [ConditionalFact] - public virtual void Required_one_to_one_with_AK_relationships_are_one_to_one() - { - ExecuteWithStrategyInTransaction( - context => - { - var root = context.Set().Single(IsTheRoot); - - root.RequiredSingleAk = context.CreateProxy(); - - Assert.Throws(() => context.SaveChanges()); - }); - } - - [ConditionalFact] - public virtual void No_fixup_to_Deleted_entities() - { - using var context = CreateContext(); - - var root = LoadRoot(context); - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - } - - var existing = root.OptionalChildren.OrderBy(e => e.Id).First(); - - existing.Parent = null; - existing.ParentId = null; - ((ICollection)root.OptionalChildren).Remove(existing); - - context.Entry(existing).State = EntityState.Deleted; - - var queried = context.Set().ToList(); - - Assert.Null(existing.Parent); - Assert.Null(existing.ParentId); - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(existing, root.OptionalChildren); - - Assert.Equal(2, queried.Count); - Assert.Contains(existing, queried); - } - - [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) - { - Optional1 new1 = null; - Optional1Derived new1d = null; - Optional1MoreDerived new1dd = null; - Optional2 new2a = null; - Optional2 new2b = null; - Optional2Derived new2d = null; - Optional2MoreDerived new2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - new1 = context.CreateProxy(); - new1d = context.CreateProxy(); - new1dd = context.CreateProxy(); - new2a = context.CreateProxy(); - new2b = context.CreateProxy(); - new2d = context.CreateProxy(); - new2dd = context.CreateProxy(); - - if (useExistingEntities) - { - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - } - - var existing = root.OptionalChildren.OrderBy(e => e.Id).First(); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (Optional1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (Optional1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2a = context.Set().Single(e => e.Id == new2a.Id); - new2b = context.Set().Single(e => e.Id == new2b.Id); - new2d = (Optional2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (Optional2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Add(existing.Children, new2a); - Add(existing.Children, new2b); - Add(new1d.Children, new2d); - Add(new1dd.Children, new2dd); - Add(root.OptionalChildren, new1); - Add(root.OptionalChildren, new1d); - Add(root.OptionalChildren, new1dd); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new2a.Parent = existing; - new2b.Parent = existing; - new2d.Parent = new1d; - new2dd.Parent = new1dd; - new1.Parent = root; - new1d.Parent = root; - new1dd.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new2a.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; - new2b.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; - new2d.ParentId = context.Entry(new1d).Property(e => e.Id).CurrentValue; - new2dd.ParentId = context.Entry(new1dd).Property(e => e.Id).CurrentValue; - new1.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - new1d.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - new1dd.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Contains(new2a, existing.Children); - Assert.Contains(new2b, existing.Children); - Assert.Contains(new2d, new1d.Children); - Assert.Contains(new2dd, new1dd.Children); - Assert.Contains(new1, root.OptionalChildren); - Assert.Contains(new1d, root.OptionalChildren); - Assert.Contains(new1dd, root.OptionalChildren); - - Assert.Same(existing, new2a.Parent); - Assert.Same(existing, new2b.Parent); - Assert.Same(new1d, new2d.Parent); - Assert.Same(new1dd, new2dd.Parent); - Assert.Same(root, existing.Parent); - Assert.Same(root, new1d.Parent); - Assert.Same(root, new1dd.Parent); - - Assert.Equal(existing.Id, new2a.ParentId); - Assert.Equal(existing.Id, new2b.ParentId); - Assert.Equal(new1d.Id, new2d.ParentId); - Assert.Equal(new1dd.Id, new2dd.ParentId); - Assert.Equal(root.Id, existing.ParentId); - Assert.Equal(root.Id, new1d.ParentId); - Assert.Equal(root.Id, new1dd.ParentId); - }); - } - - [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) - { - Root newRoot; - Required1 new1 = null; - Required1Derived new1d = null; - Required1MoreDerived new1dd = null; - Required2 new2a = null; - Required2 new2b = null; - Required2Derived new2d = null; - Required2MoreDerived new2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - newRoot = context.CreateProxy(); - new1 = context.CreateProxy(e => e.Parent = newRoot); - new1d = context.CreateProxy(e => e.Parent = newRoot); - new1dd = context.CreateProxy(e => e.Parent = newRoot); - new2a = context.CreateProxy(e => e.Parent = new1); - new2b = context.CreateProxy(e => e.Parent = new1); - new2d = context.CreateProxy(e => e.Parent = new1); - new2dd = context.CreateProxy(e => e.Parent = new1); - - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new1d, new1dd, new2a, new2d, new2dd, new2b); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - var existing = root.RequiredChildren.OrderBy(e => e.Id).First(); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (Required1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (Required1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2a = context.Set().Single(e => e.Id == new2a.Id); - new2b = context.Set().Single(e => e.Id == new2b.Id); - new2d = (Required2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (Required2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - new1.Parent = null; - new1d.Parent = null; - new1dd.Parent = null; - - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Add(existing.Children, new2a); - Add(existing.Children, new2b); - Add(new1d.Children, new2d); - Add(new1dd.Children, new2dd); - Add(root.RequiredChildren, new1); - Add(root.RequiredChildren, new1d); - Add(root.RequiredChildren, new1dd); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new2a.Parent = existing; - new2b.Parent = existing; - new2d.Parent = new1d; - new2dd.Parent = new1dd; - new1.Parent = root; - new1d.Parent = root; - new1dd.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new2a.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; - new2b.ParentId = context.Entry(existing).Property(e => e.Id).CurrentValue; - new2d.ParentId = context.Entry(new1d).Property(e => e.Id).CurrentValue; - new2dd.ParentId = context.Entry(new1dd).Property(e => e.Id).CurrentValue; - new1.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - new1d.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - new1dd.ParentId = context.Entry(root).Property(e => e.Id).CurrentValue; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Contains(new2a, existing.Children); - Assert.Contains(new2b, existing.Children); - Assert.Contains(new2d, new1d.Children); - Assert.Contains(new2dd, new1dd.Children); - Assert.Contains(new1, root.RequiredChildren); - Assert.Contains(new1d, root.RequiredChildren); - Assert.Contains(new1dd, root.RequiredChildren); - - Assert.Same(existing, new2a.Parent); - Assert.Same(existing, new2b.Parent); - Assert.Same(new1d, new2d.Parent); - Assert.Same(new1dd, new2dd.Parent); - Assert.Same(root, existing.Parent); - Assert.Same(root, new1d.Parent); - Assert.Same(root, new1dd.Parent); - - Assert.Equal(existing.Id, new2a.ParentId); - Assert.Equal(existing.Id, new2b.ParentId); - Assert.Equal(new1d.Id, new2d.ParentId); - Assert.Equal(new1dd.Id, new2dd.ParentId); - Assert.Equal(root.Id, existing.ParentId); - Assert.Equal(root.Id, new1d.ParentId); - Assert.Equal(root.Id, new1dd.ParentId); - }); - } - - [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) - { - Root root; - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - context.Entry(root.OptionalChildren.First()).Collection(e => e.Children).Load(); - } - - var childCollection = root.OptionalChildren.First().Children; - var removed2 = childCollection.First(); - var removed1 = root.OptionalChildren.Skip(1).First(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Remove(childCollection, removed2); - Remove(root.OptionalChildren, removed1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - removed2.Parent = null; - removed1.Parent = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - removed2.ParentId = null; - removed1.ParentId = null; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.DoesNotContain(removed1, root.OptionalChildren); - Assert.DoesNotContain(removed2, childCollection); - - Assert.Null(removed1.Parent); - Assert.Null(removed2.Parent); - Assert.Null(removed1.ParentId); - Assert.Null(removed2.ParentId); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(loadedRoot).Collection(e => e.OptionalChildren).Load(); - context.Entry(loadedRoot.OptionalChildren.First()).Collection(e => e.Children).Load(); - } - - Assert.Single(loadedRoot.OptionalChildren); - Assert.Single(loadedRoot.OptionalChildren.First().Children); - } - }); - } - - [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) - { - var removed1Id = 0; - var removed2Id = 0; - List removed1ChildrenIds = null; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - context.Entry(root.RequiredChildren.First()).Collection(e => e.Children).Load(); - } - - var childCollection = root.RequiredChildren.First().Children; - var removed2 = childCollection.First(); - var removed1 = root.RequiredChildren.Skip(1).First(); - - removed1Id = removed1.Id; - removed2Id = removed2.Id; - removed1ChildrenIds = removed1.Children.Select(e => e.Id).ToList(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Remove(childCollection, removed2); - Remove(root.RequiredChildren, removed1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - removed2.Parent = null; - removed1.Parent = null; - } - - 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()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - Assert.Single(root.RequiredChildren); - Assert.DoesNotContain(removed1Id, root.RequiredChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removed1Id)); - Assert.Empty(context.Set().Where(e => e.Id == removed2Id)); - Assert.Empty(context.Set().Where(e => removed1ChildrenIds.Contains(e.Id))); - }); - } - - [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) - { - OptionalSingle2 new2 = null; - OptionalSingle2Derived new2d = null; - OptionalSingle2MoreDerived new2dd = null; - OptionalSingle1 new1 = null; - OptionalSingle1Derived new1d = null; - OptionalSingle1MoreDerived new1dd = null; - OptionalSingle1 old1 = null; - OptionalSingle1Derived old1d = null; - OptionalSingle1MoreDerived old1dd = null; - OptionalSingle2 old2 = null; - OptionalSingle2Derived old2d = null; - OptionalSingle2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - new2 = context.CreateProxy(); - new2d = context.CreateProxy(); - new2dd = context.CreateProxy(); - new1 = context.CreateProxy(e => e.Single = new2); - new1d = context.CreateProxy(e => e.Single = new2d); - new1dd = context.CreateProxy(e => e.Single = new2dd); - - if (useExistingEntities) - { - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingle).Load(); - context.Entry(root).Reference(e => e.OptionalSingleDerived).Load(); - context.Entry(root).Reference(e => e.OptionalSingleMoreDerived).Load(); - } - - old1 = root.OptionalSingle; - old1d = root.OptionalSingleDerived; - old1dd = root.OptionalSingleMoreDerived; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1d).Reference(e => e.Single).Load(); - context.Entry(old1dd).Reference(e => e.Single).Load(); - } - - old2 = root.OptionalSingle.Single; - old2d = (OptionalSingle2Derived)root.OptionalSingleDerived.Single; - old2dd = (OptionalSingle2MoreDerived)root.OptionalSingleMoreDerived.Single; - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (OptionalSingle1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (OptionalSingle1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2d = (OptionalSingle2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (OptionalSingle2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.OptionalSingle = new1; - root.OptionalSingleDerived = new1d; - root.OptionalSingleMoreDerived = new1dd; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - new1d.DerivedRoot = root; - new1dd.MoreDerivedRoot = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new1.RootId = root.Id; - new1d.DerivedRootId = root.Id; - new1dd.MoreDerivedRootId = root.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.Id, new1.RootId); - Assert.Equal(root.Id, new1d.DerivedRootId); - Assert.Equal(root.Id, new1dd.MoreDerivedRootId); - Assert.Equal(new1.Id, new2.BackId); - Assert.Equal(new1d.Id, new2d.BackId); - Assert.Equal(new1dd.Id, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Equal(old1, old2.Back); - Assert.Equal(old1d, old2d.Back); - Assert.Equal(old1dd, old2dd.Back); - Assert.Null(old1.RootId); - Assert.Null(old1d.DerivedRootId); - Assert.Null(old1dd.MoreDerivedRootId); - Assert.Equal(old1.Id, old2.BackId); - Assert.Equal(old1d.Id, old2d.BackId); - Assert.Equal(old1dd.Id, old2dd.BackId); - }, - context => - { - LoadRoot(context); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded1d = context.Set().Single(e => e.Id == old1d.Id); - var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2d = context.Set().Single(e => e.Id == old2d.Id); - var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); - - Assert.Null(loaded1.Root); - Assert.Null(loaded1d.Root); - Assert.Null(loaded1dd.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1d, loaded2d.Back); - Assert.Same(loaded1dd, loaded2dd.Back); - Assert.Null(loaded1.RootId); - Assert.Null(loaded1d.RootId); - Assert.Null(loaded1dd.RootId); - Assert.Equal(loaded1.Id, loaded2.BackId); - Assert.Equal(loaded1d.Id, loaded2d.BackId); - Assert.Equal(loaded1dd.Id, loaded2dd.BackId); - }); - } - - [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 Save_required_one_to_one_changed_by_reference(ChangeMechanism changeMechanism) - { - RequiredSingle1 old1 = null; - RequiredSingle2 old2 = null; - Root oldRoot; - RequiredSingle2 new2 = null; - RequiredSingle1 new1 = null; - ExecuteWithStrategyInTransaction( - context => - { - oldRoot = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(oldRoot).Reference(e => e.RequiredSingle).Load(); - } - - old1 = oldRoot.RequiredSingle; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - } - - old2 = oldRoot.RequiredSingle.Single; - - context.Entry(oldRoot).State = EntityState.Detached; - context.Entry(old1).State = EntityState.Detached; - context.Entry(old2).State = EntityState.Detached; - - new2 = context.CreateProxy(); - new1 = context.CreateProxy(e => e.Single = new2); - }); - - - ExecuteWithStrategyInTransaction( - context => - { - var root = context.Set().Include(e => e.RequiredSingle.Single).Single(IsTheRoot); - - context.Entry(root.RequiredSingle.Single).State = EntityState.Deleted; - context.Entry(root.RequiredSingle).State = EntityState.Deleted; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredSingle = new1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - context.Add(new1); - new1.Root = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - context.Add(new1); - new1.Id = root.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.Id, new1.Id); - Assert.Equal(new1.Id, new2.Id); - Assert.Same(root, new1.Root); - Assert.Same(new1, new2.Back); - - Assert.NotNull(old1.Root); - Assert.Same(old1, old2.Back); - Assert.Equal(old1.Id, old2.Id); - }); - } - - [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) - { - RequiredNonPkSingle2 new2 = null; - RequiredNonPkSingle2Derived new2d = null; - RequiredNonPkSingle2MoreDerived new2dd = null; - RequiredNonPkSingle1 new1 = null; - RequiredNonPkSingle1Derived new1d = null; - RequiredNonPkSingle1MoreDerived new1dd = null; - Root newRoot; - RequiredNonPkSingle1 old1 = null; - RequiredNonPkSingle1Derived old1d = null; - RequiredNonPkSingle1MoreDerived old1dd = null; - RequiredNonPkSingle2 old2 = null; - RequiredNonPkSingle2Derived old2d = null; - RequiredNonPkSingle2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - new2 = context.CreateProxy(); - new2d = context.CreateProxy(); - new2dd = context.CreateProxy(); - new1 = context.CreateProxy(e => e.Single = new2); - new1d = context.CreateProxy( - e => - { - e.Single = new2d; - e.Root = context.CreateProxy(); - }); - new1dd = context.CreateProxy( - e => - { - e.Single = new2dd; - e.Root = context.CreateProxy(); - e.DerivedRoot = context.CreateProxy(); - }); - newRoot = context.CreateProxy( - e => - { - e.RequiredNonPkSingle = new1; - e.RequiredNonPkSingleDerived = new1d; - e.RequiredNonPkSingleMoreDerived = new1dd; - }); - - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new1d, new1dd, new2, new2d, new2dd); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - context.Entry(root).Reference(e => e.RequiredNonPkSingleDerived).Load(); - context.Entry(root).Reference(e => e.RequiredNonPkSingleMoreDerived).Load(); - } - - old1 = root.RequiredNonPkSingle; - old1d = root.RequiredNonPkSingleDerived; - old1dd = root.RequiredNonPkSingleMoreDerived; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1d).Reference(e => e.Single).Load(); - context.Entry(old1dd).Reference(e => e.Single).Load(); - context.Entry(old1d).Reference(e => e.Root).Load(); - context.Entry(old1dd).Reference(e => e.Root).Load(); - context.Entry(old1dd).Reference(e => e.DerivedRoot).Load(); - } - - old2 = root.RequiredNonPkSingle.Single; - old2d = (RequiredNonPkSingle2Derived)root.RequiredNonPkSingleDerived.Single; - old2dd = (RequiredNonPkSingle2MoreDerived)root.RequiredNonPkSingleMoreDerived.Single; - - context.Set().Remove(old1d); - context.Set().Remove(old1dd); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (RequiredNonPkSingle1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (RequiredNonPkSingle1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2d = (RequiredNonPkSingle2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (RequiredNonPkSingle2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - - new1d.RootId = old1d.RootId; - new1dd.RootId = old1dd.RootId; - new1dd.DerivedRootId = old1dd.DerivedRootId; - } - else - { - new1d.Root = old1d.Root; - new1dd.Root = old1dd.Root; - new1dd.DerivedRoot = old1dd.DerivedRoot; - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredNonPkSingle = new1; - root.RequiredNonPkSingleDerived = new1d; - root.RequiredNonPkSingleMoreDerived = new1dd; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - new1d.DerivedRoot = root; - new1dd.MoreDerivedRoot = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new1.RootId = root.Id; - new1d.DerivedRootId = root.Id; - new1dd.MoreDerivedRootId = root.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.Id, new1.RootId); - Assert.Equal(root.Id, new1d.DerivedRootId); - Assert.Equal(root.Id, new1dd.MoreDerivedRootId); - Assert.Equal(new1.Id, new2.BackId); - Assert.Equal(new1d.Id, new2d.BackId); - Assert.Equal(new1dd.Id, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Null(old2.Back); - Assert.Null(old2d.Back); - Assert.Null(old2dd.Back); - Assert.Equal(old1.Id, old2.BackId); - Assert.Equal(old1d.Id, old2d.BackId); - Assert.Equal(old1dd.Id, old2dd.BackId); - }, - context => - { - var loadedRoot = LoadRoot(context); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old1d.Id)); - Assert.False(context.Set().Any(e => e.Id == old1dd.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2d.Id)); - Assert.False(context.Set().Any(e => e.Id == old2dd.Id)); - }); - } - - [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) - { - Root root; - OptionalSingle1 old1 = null; - OptionalSingle2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingle).Load(); - } - - old1 = root.OptionalSingle; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - } - - old2 = root.OptionalSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.OptionalSingle = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = null; - } - - Assert.False(context.Entry(root).Reference(e => e.OptionalSingle).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Same(old1, old2.Back); - Assert.Null(old1.RootId); - Assert.Equal(old1.Id, old2.BackId); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - LoadRoot(context); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - - Assert.Null(loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Null(loaded1.RootId); - Assert.Equal(loaded1.Id, loaded2.BackId); - } - }); - } - - [ConditionalTheory] - [InlineData((int)ChangeMechanism.Dependent)] - [InlineData((int)ChangeMechanism.Principal)] - [InlineData((int)(ChangeMechanism.Principal | ChangeMechanism.Dependent))] - public virtual void Sever_required_one_to_one(ChangeMechanism changeMechanism) - { - Root root = null; - RequiredSingle1 old1 = null; - RequiredSingle2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - old1 = root.RequiredSingle; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - } - - old2 = root.RequiredSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredSingle = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - Assert.False(context.Entry(root).Reference(e => e.RequiredSingle).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Equal(old1.Id, old2.Id); - }, - context => - { - LoadRoot(context); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - }); - } - - [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) - { - Root root; - RequiredNonPkSingle1 old1 = null; - RequiredNonPkSingle2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - old1 = root.RequiredNonPkSingle; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - } - - old2 = root.RequiredNonPkSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredNonPkSingle = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - 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()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Equal(old1.Id, old2.BackId); - }, - context => - { - LoadRoot(context); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - }); - } - - [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) - { - Root newRoot = null; - Root root; - OptionalSingle1 old1 = null; - OptionalSingle2 old2 = null; - - ExecuteWithStrategyInTransaction( - context => - { - newRoot = context.CreateProxy(); - - if (useExistingRoot) - { - context.AddRange(newRoot); - context.SaveChanges(); - } - }, - context => - { - root = LoadRoot(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingle).Load(); - } - - old1 = root.OptionalSingle; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - } - - old2 = root.OptionalSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.OptionalSingle = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = context.Entry(newRoot).Property(e => e.Id).CurrentValue; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.OptionalSingle); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Equal(newRoot.Id, old1.RootId); - Assert.Equal(old1.Id, old2.BackId); - }, - context => - { - var loadedRoot = LoadRoot(context); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Equal(newRoot.Id, loaded1.RootId); - Assert.Equal(loaded1.Id, loaded2.BackId); - }); - } - - [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) - { - Root newRoot = null; - - ExecuteWithStrategyInTransaction( - context => - { - newRoot = context.CreateProxy(); - - if (useExistingRoot) - { - context.AddRange(newRoot); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - Assert.Equal( - CoreStrings.KeyReadOnly("Id", typeof(RequiredSingle1).Name), - Assert.Throws( - () => - { - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.RequiredSingle = root.RequiredSingle; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - root.RequiredSingle.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - root.RequiredSingle.Id = newRoot.Id; - } - - newRoot.RequiredSingle = root.RequiredSingle; - - context.SaveChanges(); - }).Message); - }); - } - - [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) - { - Root newRoot = null; - Root root; - RequiredNonPkSingle1 old1 = null; - RequiredNonPkSingle2 old2 = null; - - ExecuteWithStrategyInTransaction( - context => - { - newRoot = context.CreateProxy(); - - if (useExistingRoot) - { - context.AddRange(newRoot); - context.SaveChanges(); - } - }, - context => - { - root = LoadRoot(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - old1 = root.RequiredNonPkSingle; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - } - - old2 = root.RequiredNonPkSingle.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.RequiredNonPkSingle = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = context.Entry(newRoot).Property(e => e.Id).CurrentValue; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Equal(newRoot.Id, old1.RootId); - Assert.Equal(old1.Id, old2.BackId); - }, - context => - { - var loadedRoot = LoadRoot(context); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Equal(newRoot.Id, loaded1.RootId); - Assert.Equal(loaded1.Id, loaded2.BackId); - }); - } - - [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) - { - var compositeCount = 0; - OptionalAk1 oldParent = null; - OptionalComposite2 oldComposite1 = null; - OptionalComposite2 oldComposite2 = null; - Optional1 newParent = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (!useExistingParent) - { - newParent = context.CreateProxy( - e => e.CompositeChildren = new ObservableHashSet(ReferenceEqualityComparer.Instance)); - - context.Set().Add(newParent); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - compositeCount = context.Set().Count(); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - } - - oldParent = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); - - if (!DoesLazyLoading) - { - context.Entry(oldParent).Collection(e => e.CompositeChildren).Load(); - } - - oldComposite1 = oldParent.CompositeChildren.OrderBy(e => e.Id).First(); - oldComposite2 = oldParent.CompositeChildren.OrderBy(e => e.Id).Last(); - - if (useExistingParent) - { - newParent = root.OptionalChildren.OrderBy(e => e.Id).Last(); - } - else - { - newParent = context.Set().Single(e => e.Id == newParent.Id); - newParent.Parent = root; - } - - if (!DoesLazyLoading) - { - context.Entry(newParent).Collection(e => e.CompositeChildren).Load(); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - oldParent.CompositeChildren.Remove(oldComposite1); - newParent.CompositeChildren.Add(oldComposite1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - oldComposite1.Parent = null; - oldComposite1.Parent2 = newParent; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - oldComposite1.ParentId = null; - oldComposite1.Parent2Id = newParent.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Same(oldComposite2, oldParent.CompositeChildren.Single()); - Assert.Same(oldParent, oldComposite2.Parent); - Assert.Equal(oldParent.Id, oldComposite2.ParentId); - Assert.Null(oldComposite2.Parent2); - Assert.Null(oldComposite2.Parent2Id); - - Assert.Same(oldComposite1, newParent.CompositeChildren.Single()); - Assert.Same(newParent, oldComposite1.Parent2); - Assert.Equal(newParent.Id, oldComposite1.Parent2Id); - Assert.Null(oldComposite1.Parent); - Assert.Null(oldComposite1.ParentId); - - Assert.Equal(compositeCount, context.Set().Count()); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadRoot(context); - - oldParent = context.Set().Single(e => e.Id == oldParent.Id); - newParent = context.Set().Single(e => e.Id == newParent.Id); - - oldComposite1 = context.Set().Single(e => e.Id == oldComposite1.Id); - oldComposite2 = context.Set().Single(e => e.Id == oldComposite2.Id); - - Assert.Same(oldComposite2, oldParent.CompositeChildren.Single()); - Assert.Same(oldParent, oldComposite2.Parent); - Assert.Equal(oldParent.Id, oldComposite2.ParentId); - Assert.Null(oldComposite2.Parent2); - Assert.Null(oldComposite2.Parent2Id); - - Assert.Same(oldComposite1, newParent.CompositeChildren.Single()); - Assert.Same(newParent, oldComposite1.Parent2); - Assert.Equal(newParent.Id, oldComposite1.Parent2Id); - Assert.Null(oldComposite1.Parent); - Assert.Null(oldComposite1.ParentId); - - Assert.Equal(compositeCount, context.Set().Count()); - } - }); - } - - [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) - { - Root root = null; - var childCount = 0; - RequiredComposite1 oldParent = null; - OptionalOverlapping2 oldChild1 = null; - OptionalOverlapping2 oldChild2 = null; - RequiredComposite1 newParent = null; - - ExecuteWithStrategyInTransaction( - context => - { - if (!useExistingParent) - { - newParent = context.CreateProxy( - e => - { - e.Id = 3; - e.Parent = context.Set().Single(IsTheRoot); - e.CompositeChildren = new ObservableHashSet(ReferenceEqualityComparer.Instance) - { - context.CreateProxy(e => e.Id = 5), - context.CreateProxy(e => e.Id = 6) - }; - }); - - context.Set().Add(newParent); - context.SaveChanges(); - } - }, - context => - { - root = LoadRoot(context); - - childCount = context.Set().Count(); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredCompositeChildren).Load(); - } - - oldParent = root.RequiredCompositeChildren.OrderBy(e => e.Id).First(); - - if (!DoesLazyLoading) - { - context.Entry(oldParent).Collection(e => e.CompositeChildren).Load(); - } - - oldChild1 = oldParent.CompositeChildren.OrderBy(e => e.Id).First(); - oldChild2 = oldParent.CompositeChildren.OrderBy(e => e.Id).Last(); - - Assert.Equal(useExistingParent ? 2 : 3, root.RequiredCompositeChildren.Count()); - - if (useExistingParent) - { - newParent = root.RequiredCompositeChildren.OrderBy(e => e.Id).Last(); - } - else - { - newParent = context.Set().Single(e => e.Id == newParent.Id); - newParent.Parent = root; - } - - if (!DoesLazyLoading) - { - context.Entry(newParent).Collection(e => e.CompositeChildren).Load(); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - oldParent.CompositeChildren.Remove(oldChild1); - newParent.CompositeChildren.Add(oldChild1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - oldChild1.Parent = newParent; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - oldChild1.ParentId = newParent.Id; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Same(oldChild2, oldParent.CompositeChildren.Single()); - Assert.Same(oldParent, oldChild2.Parent); - Assert.Equal(oldParent.Id, oldChild2.ParentId); - Assert.Equal(oldParent.ParentAlternateId, oldChild2.ParentAlternateId); - Assert.Equal(root.AlternateId, oldChild2.ParentAlternateId); - Assert.Same(root, oldChild2.Root); - - Assert.Equal(3, newParent.CompositeChildren.Count); - Assert.Same(oldChild1, newParent.CompositeChildren.Single(e => e.Id == oldChild1.Id)); - Assert.Same(newParent, oldChild1.Parent); - Assert.Equal(newParent.Id, oldChild1.ParentId); - Assert.Equal(oldParent.ParentAlternateId, oldChild1.ParentAlternateId); - Assert.Equal(root.AlternateId, oldChild1.ParentAlternateId); - Assert.Same(root, oldChild1.Root); - - Assert.Equal(childCount, context.Set().Count()); - }, - context => - { - var loadedRoot = LoadRoot(context); - - oldParent = context.Set().Single(e => e.Id == oldParent.Id); - newParent = context.Set().Single(e => e.Id == newParent.Id); - - oldChild1 = context.Set().Single(e => e.Id == oldChild1.Id); - oldChild2 = context.Set().Single(e => e.Id == oldChild2.Id); - - if (!DoesLazyLoading) - { - context.Entry(oldParent).Collection(e => e.CompositeChildren).Load(); - context.Entry(newParent).Collection(e => e.CompositeChildren).Load(); - } - - Assert.Same(oldChild2, oldParent.CompositeChildren.Single()); - Assert.Same(oldParent, oldChild2.Parent); - Assert.Equal(oldParent.Id, oldChild2.ParentId); - Assert.Equal(oldParent.ParentAlternateId, oldChild2.ParentAlternateId); - Assert.Equal(root.AlternateId, oldChild2.ParentAlternateId); - - Assert.Same(oldChild1, newParent.CompositeChildren.Single(e => e.Id == oldChild1.Id)); - Assert.Same(newParent, oldChild1.Parent); - Assert.Equal(newParent.Id, oldChild1.ParentId); - Assert.Equal(oldParent.ParentAlternateId, oldChild1.ParentAlternateId); - Assert.Equal(root.AlternateId, oldChild1.ParentAlternateId); - - Assert.Equal(childCount, context.Set().Count()); - }); - } - - [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_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) - { - OptionalAk1 new1 = null; - OptionalAk1Derived new1d = null; - OptionalAk1MoreDerived new1dd = null; - OptionalAk2 new2a = null; - OptionalAk2 new2b = null; - OptionalComposite2 new2ca = null; - OptionalComposite2 new2cb = null; - OptionalAk2Derived new2d = null; - OptionalAk2MoreDerived new2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - new1 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new1d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new1dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2a = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2b = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2ca = context.CreateProxy(); - new2cb = context.CreateProxy(); - new2d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - - if (useExistingEntities) - { - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - } - - var existing = root.OptionalChildrenAk.OrderBy(e => e.Id).First(); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (OptionalAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (OptionalAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2a = context.Set().Single(e => e.Id == new2a.Id); - new2b = context.Set().Single(e => e.Id == new2b.Id); - new2ca = context.Set().Single(e => e.Id == new2ca.Id); - new2cb = context.Set().Single(e => e.Id == new2cb.Id); - new2d = (OptionalAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (OptionalAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Add(existing.Children, new2a); - Add(existing.Children, new2b); - Add(existing.CompositeChildren, new2ca); - Add(existing.CompositeChildren, new2cb); - Add(new1d.Children, new2d); - Add(new1dd.Children, new2dd); - Add(root.OptionalChildrenAk, new1); - Add(root.OptionalChildrenAk, new1d); - Add(root.OptionalChildrenAk, new1dd); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new2a.Parent = existing; - new2b.Parent = existing; - new2ca.Parent = existing; - new2cb.Parent = existing; - new2d.Parent = new1d; - new2dd.Parent = new1dd; - new1.Parent = root; - new1d.Parent = root; - new1dd.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new2a.ParentId = existing.AlternateId; - new2b.ParentId = existing.AlternateId; - new2ca.ParentId = existing.Id; - new2ca.ParentAlternateId = existing.AlternateId; - new2cb.ParentId = existing.Id; - new2cb.ParentAlternateId = existing.AlternateId; - new2d.ParentId = new1d.AlternateId; - new2dd.ParentId = new1dd.AlternateId; - new1.ParentId = root.AlternateId; - new1d.ParentId = root.AlternateId; - new1dd.ParentId = root.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Contains(new2a, existing.Children); - Assert.Contains(new2b, existing.Children); - Assert.Contains(new2ca, existing.CompositeChildren); - Assert.Contains(new2cb, existing.CompositeChildren); - Assert.Contains(new2d, new1d.Children); - Assert.Contains(new2dd, new1dd.Children); - Assert.Contains(new1, root.OptionalChildrenAk); - Assert.Contains(new1d, root.OptionalChildrenAk); - Assert.Contains(new1dd, root.OptionalChildrenAk); - - Assert.Same(existing, new2a.Parent); - Assert.Same(existing, new2b.Parent); - Assert.Same(existing, new2ca.Parent); - Assert.Same(existing, new2cb.Parent); - Assert.Same(new1d, new2d.Parent); - Assert.Same(new1dd, new2dd.Parent); - Assert.Same(root, existing.Parent); - Assert.Same(root, new1d.Parent); - Assert.Same(root, new1dd.Parent); - - Assert.Equal(existing.AlternateId, new2a.ParentId); - Assert.Equal(existing.AlternateId, new2b.ParentId); - Assert.Equal(existing.Id, new2ca.ParentId); - Assert.Equal(existing.Id, new2cb.ParentId); - Assert.Equal(existing.AlternateId, new2ca.ParentAlternateId); - Assert.Equal(existing.AlternateId, new2cb.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.ParentId); - Assert.Equal(new1dd.AlternateId, new2dd.ParentId); - Assert.Equal(root.AlternateId, existing.ParentId); - Assert.Equal(root.AlternateId, new1d.ParentId); - Assert.Equal(root.AlternateId, new1dd.ParentId); - }); - } - - [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_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) - { - Root newRoot; - RequiredAk1 new1 = null; - RequiredAk1Derived new1d = null; - RequiredAk1MoreDerived new1dd = null; - RequiredAk2 new2a = null; - RequiredAk2 new2b = null; - RequiredComposite2 new2ca = null; - RequiredComposite2 new2cb = null; - RequiredAk2Derived new2d = null; - RequiredAk2MoreDerived new2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - newRoot = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new1 = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.Parent = newRoot; - }); - new1d = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.Parent = newRoot; - }); - new1dd = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.Parent = newRoot; - }); - new2a = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.Parent = new1; - }); - new2b = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.Parent = new1; - }); - new2ca = context.CreateProxy(e => e.Parent = new1); - new2cb = context.CreateProxy(e => e.Parent = new1); - new2d = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.Parent = new1; - }); - new2dd = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.Parent = new1; - }); - - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - var existing = root.RequiredChildrenAk.OrderBy(e => e.Id).First(); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (RequiredAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (RequiredAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2a = context.Set().Single(e => e.Id == new2a.Id); - new2b = context.Set().Single(e => e.Id == new2b.Id); - new2ca = context.Set().Single(e => e.Id == new2ca.Id); - new2cb = context.Set().Single(e => e.Id == new2cb.Id); - new2d = (RequiredAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (RequiredAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - new1.Parent = null; - new1d.Parent = null; - new1dd.Parent = null; - - context.AddRange(new1, new1d, new1dd, new2a, new2d, new2dd, new2b, new2ca, new2cb); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Add(existing.Children, new2a); - Add(existing.Children, new2b); - Add(existing.CompositeChildren, new2ca); - Add(existing.CompositeChildren, new2cb); - Add(new1d.Children, new2d); - Add(new1dd.Children, new2dd); - Add(root.RequiredChildrenAk, new1); - Add(root.RequiredChildrenAk, new1d); - Add(root.RequiredChildrenAk, new1dd); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new2a.Parent = existing; - new2b.Parent = existing; - new2ca.Parent = existing; - new2cb.Parent = existing; - new2d.Parent = new1d; - new2dd.Parent = new1dd; - new1.Parent = root; - new1d.Parent = root; - new1dd.Parent = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new2a.ParentId = existing.AlternateId; - new2b.ParentId = existing.AlternateId; - new2ca.ParentId = existing.Id; - new2cb.ParentId = existing.Id; - new2ca.ParentAlternateId = existing.AlternateId; - new2cb.ParentAlternateId = existing.AlternateId; - new2d.ParentId = new1d.AlternateId; - new2dd.ParentId = new1dd.AlternateId; - new1.ParentId = root.AlternateId; - new1d.ParentId = root.AlternateId; - new1dd.ParentId = root.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Contains(new2a, existing.Children); - Assert.Contains(new2b, existing.Children); - Assert.Contains(new2ca, existing.CompositeChildren); - Assert.Contains(new2cb, existing.CompositeChildren); - Assert.Contains(new2d, new1d.Children); - Assert.Contains(new2dd, new1dd.Children); - Assert.Contains(new1, root.RequiredChildrenAk); - Assert.Contains(new1d, root.RequiredChildrenAk); - Assert.Contains(new1dd, root.RequiredChildrenAk); - - Assert.Same(existing, new2a.Parent); - Assert.Same(existing, new2b.Parent); - Assert.Same(existing, new2ca.Parent); - Assert.Same(existing, new2cb.Parent); - Assert.Same(new1d, new2d.Parent); - Assert.Same(new1dd, new2dd.Parent); - Assert.Same(root, existing.Parent); - Assert.Same(root, new1d.Parent); - Assert.Same(root, new1dd.Parent); - - Assert.Equal(existing.AlternateId, new2a.ParentId); - Assert.Equal(existing.AlternateId, new2b.ParentId); - Assert.Equal(existing.Id, new2ca.ParentId); - Assert.Equal(existing.Id, new2cb.ParentId); - Assert.Equal(existing.AlternateId, new2ca.ParentAlternateId); - Assert.Equal(existing.AlternateId, new2cb.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.ParentId); - Assert.Equal(new1dd.AlternateId, new2dd.ParentId); - Assert.Equal(root.AlternateId, existing.ParentId); - Assert.Equal(root.AlternateId, new1d.ParentId); - Assert.Equal(root.AlternateId, new1dd.ParentId); - }); - } - - [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) - { - Root root; - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - context.Entry(root.OptionalChildrenAk.First()).Collection(e => e.Children).Load(); - context.Entry(root.OptionalChildrenAk.First()).Collection(e => e.CompositeChildren).Load(); - } - - var childCollection = root.OptionalChildrenAk.First().Children; - var childCompositeCollection = root.OptionalChildrenAk.First().CompositeChildren; - var removed2 = childCollection.First(); - var removed1 = root.OptionalChildrenAk.Skip(1).First(); - var removed2c = childCompositeCollection.First(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Remove(childCollection, removed2); - Remove(childCompositeCollection, removed2c); - Remove(root.OptionalChildrenAk, removed1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - removed2.Parent = null; - removed2c.Parent = null; - removed1.Parent = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - removed2.ParentId = null; - removed2c.ParentId = null; - removed1.ParentId = null; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.DoesNotContain(removed1, root.OptionalChildrenAk); - Assert.DoesNotContain(removed2, childCollection); - Assert.DoesNotContain(removed2c, childCompositeCollection); - - Assert.Null(removed1.Parent); - Assert.Null(removed2.Parent); - Assert.Null(removed2c.Parent); - - Assert.Null(removed1.ParentId); - Assert.Null(removed2.ParentId); - Assert.Null(removed2c.ParentId); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(loadedRoot).Collection(e => e.OptionalChildrenAk).Load(); - context.Entry(loadedRoot.OptionalChildrenAk.First()).Collection(e => e.Children).Load(); - } - - Assert.Single(loadedRoot.OptionalChildrenAk); - Assert.Single(loadedRoot.OptionalChildrenAk.First().Children); - } - }); - } - - [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) - { - Root root = null; - RequiredAk2 removed2 = null; - RequiredComposite2 removed2c = null; - RequiredAk1 removed1 = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - context.Entry(root.RequiredChildrenAk.First()).Collection(e => e.Children).Load(); - context.Entry(root.RequiredChildrenAk.First()).Collection(e => e.CompositeChildren).Load(); - } - - var childCollection = root.RequiredChildrenAk.First().Children; - var childCompositeCollection = root.RequiredChildrenAk.First().CompositeChildren; - removed2 = childCollection.First(); - removed2c = childCompositeCollection.First(); - removed1 = root.RequiredChildrenAk.Skip(1).First(); - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - Remove(childCollection, removed2); - Remove(childCompositeCollection, removed2c); - Remove(root.RequiredChildrenAk, removed1); - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - removed2.Parent = null; - removed2c.Parent = null; - removed1.Parent = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.DoesNotContain(removed1, root.RequiredChildrenAk); - Assert.DoesNotContain(removed2, childCollection); - Assert.DoesNotContain(removed2c, childCompositeCollection); - - Assert.Null(removed1.Parent); - Assert.Null(removed2.Parent); - Assert.Null(removed2c.Parent); - }, - context => - { - var loadedRoot = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(loadedRoot).Collection(e => e.RequiredChildrenAk).Load(); - context.Entry(loadedRoot.RequiredChildrenAk.First()).Collection(e => e.Children).Load(); - context.Entry(loadedRoot.RequiredChildrenAk.First()).Collection(e => e.CompositeChildren).Load(); - } - - Assert.False(context.Set().Any(e => e.Id == removed1.Id)); - Assert.False(context.Set().Any(e => e.Id == removed2.Id)); - Assert.False(context.Set().Any(e => e.Id == removed2c.Id)); - - Assert.Single(loadedRoot.RequiredChildrenAk); - Assert.Single(loadedRoot.RequiredChildrenAk.First().Children); - Assert.Single(loadedRoot.RequiredChildrenAk.First().CompositeChildren); - }); - } - - [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) - { - OptionalSingleAk2 new2 = null; - OptionalSingleAk2Derived new2d = null; - OptionalSingleAk2MoreDerived new2dd = null; - OptionalSingleComposite2 new2c = null; - OptionalSingleAk1 new1 = null; - OptionalSingleAk1Derived new1d = null; - OptionalSingleAk1MoreDerived new1dd = null; - OptionalSingleAk1 old1 = null; - OptionalSingleAk1Derived old1d = null; - OptionalSingleAk1MoreDerived old1dd = null; - OptionalSingleAk2 old2 = null; - OptionalSingleComposite2 old2c = null; - OptionalSingleAk2Derived old2d = null; - OptionalSingleAk2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - new2 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2c = context.CreateProxy(); - new1 = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2; - e.SingleComposite = new2c; - }); - new1d = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2d; - }); - new1dd = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2dd; - }); - - if (useExistingEntities) - { - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - context.Entry(root).Reference(e => e.OptionalSingleAkDerived).Load(); - context.Entry(root).Reference(e => e.OptionalSingleAkMoreDerived).Load(); - } - - old1 = root.OptionalSingleAk; - old1d = root.OptionalSingleAkDerived; - old1dd = root.OptionalSingleAkMoreDerived; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1).Reference(e => e.SingleComposite).Load(); - context.Entry(old1d).Reference(e => e.Single).Load(); - context.Entry(old1dd).Reference(e => e.Single).Load(); - } - - old2 = root.OptionalSingleAk.Single; - old2c = root.OptionalSingleAk.SingleComposite; - old2d = (OptionalSingleAk2Derived)root.OptionalSingleAkDerived.Single; - old2dd = (OptionalSingleAk2MoreDerived)root.OptionalSingleAkMoreDerived.Single; - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (OptionalSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (OptionalSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2c = context.Set().Single(e => e.Id == new2c.Id); - new2d = (OptionalSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (OptionalSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - } - else - { - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.OptionalSingleAk = new1; - root.OptionalSingleAkDerived = new1d; - root.OptionalSingleAkMoreDerived = new1dd; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - new1d.DerivedRoot = root; - new1dd.MoreDerivedRoot = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new1.RootId = root.AlternateId; - new1d.DerivedRootId = root.AlternateId; - new1dd.MoreDerivedRootId = root.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(root.AlternateId, new1d.DerivedRootId); - Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1.Id, new2c.BackId); - Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.BackId); - Assert.Equal(new1dd.AlternateId, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1, new2c.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(old1d, old2d.Back); - Assert.Equal(old1dd, old2dd.Back); - Assert.Null(old1.RootId); - Assert.Null(old1d.DerivedRootId); - Assert.Null(old1dd.MoreDerivedRootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - Assert.Equal(old1d.AlternateId, old2d.BackId); - Assert.Equal(old1dd.AlternateId, old2dd.BackId); - }, - context => - { - LoadRoot(context); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded1d = context.Set().Single(e => e.Id == old1d.Id); - var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2d = context.Set().Single(e => e.Id == old2d.Id); - var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - Assert.Null(loaded1.Root); - Assert.Null(loaded1d.Root); - Assert.Null(loaded1dd.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Same(loaded1d, loaded2d.Back); - Assert.Same(loaded1dd, loaded2dd.Back); - Assert.Null(loaded1.RootId); - Assert.Null(loaded1d.RootId); - Assert.Null(loaded1dd.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); - Assert.Equal(loaded1d.AlternateId, loaded2d.BackId); - Assert.Equal(loaded1dd.AlternateId, loaded2dd.BackId); - }); - } - - [ConditionalFact] - public virtual void Save_changed_optional_one_to_one_with_alternate_key_in_store() - { - OptionalSingleAk2 new2; - OptionalSingleAk2Derived new2d; - OptionalSingleAk2MoreDerived new2dd; - OptionalSingleComposite2 new2c; - OptionalSingleAk1 new1; - OptionalSingleAk1Derived new1d; - OptionalSingleAk1MoreDerived new1dd; - OptionalSingleAk1 old1 = null; - OptionalSingleAk1Derived old1d = null; - OptionalSingleAk1MoreDerived old1dd = null; - OptionalSingleAk2 old2 = null; - OptionalSingleComposite2 old2c = null; - OptionalSingleAk2Derived old2d = null; - OptionalSingleAk2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - new2 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2c = context.CreateProxy(); - new1 = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2; - e.SingleComposite = new2c; - }); - new1d = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2d; - }); - new1dd = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2dd; - }); - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - context.Entry(root).Reference(e => e.OptionalSingleAkDerived).Load(); - context.Entry(root).Reference(e => e.OptionalSingleAkMoreDerived).Load(); - } - - old1 = root.OptionalSingleAk; - old1d = root.OptionalSingleAkDerived; - old1dd = root.OptionalSingleAkMoreDerived; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1).Reference(e => e.SingleComposite).Load(); - context.Entry(old1d).Reference(e => e.Single).Load(); - context.Entry(old1dd).Reference(e => e.Single).Load(); - } - - old2 = root.OptionalSingleAk.Single; - old2c = root.OptionalSingleAk.SingleComposite; - old2d = (OptionalSingleAk2Derived)root.OptionalSingleAkDerived.Single; - old2dd = (OptionalSingleAk2MoreDerived)root.OptionalSingleAkMoreDerived.Single; - - using (var context2 = CreateContext()) - { - UseTransaction(context2.Database, context.Database.CurrentTransaction); - var root2 = context2.Set() - .Include(e => e.OptionalChildrenAk).ThenInclude(e => e.Children) - .Include(e => e.OptionalChildrenAk).ThenInclude(e => e.CompositeChildren) - .Include(e => e.OptionalSingleAk).ThenInclude(e => e.Single) - .Include(e => e.OptionalSingleAk).ThenInclude(e => e.SingleComposite) - .Include(e => e.OptionalSingleAkDerived).ThenInclude(e => e.Single) - .Include(e => e.OptionalSingleAkMoreDerived).ThenInclude(e => e.Single) - .Single(IsTheRoot); - - context2.AddRange(new1, new1d, new1dd, new2, new2d, new2dd, new2c); - root2.OptionalSingleAk = new1; - root2.OptionalSingleAkDerived = new1d; - root2.OptionalSingleAkMoreDerived = new1dd; - - context2.SaveChanges(); - } - - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (OptionalSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (OptionalSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2c = context.Set().Single(e => e.Id == new2c.Id); - new2d = (OptionalSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (OptionalSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(root.AlternateId, new1d.DerivedRootId); - Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1.Id, new2c.BackId); - Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.BackId); - Assert.Equal(new1dd.AlternateId, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1, new2c.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(old1d, old2d.Back); - Assert.Equal(old1dd, old2dd.Back); - Assert.Null(old1.RootId); - Assert.Null(old1d.DerivedRootId); - Assert.Null(old1dd.MoreDerivedRootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - Assert.Equal(old1d.AlternateId, old2d.BackId); - Assert.Equal(old1dd.AlternateId, old2dd.BackId); - - context.SaveChanges(); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(root.AlternateId, new1d.DerivedRootId); - Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1.Id, new2c.BackId); - Assert.Equal(new1.AlternateId, new2c.ParentAlternateId); - Assert.Equal(new1d.AlternateId, new2d.BackId); - Assert.Equal(new1dd.AlternateId, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1, new2c.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(old1d, old2d.Back); - Assert.Equal(old1dd, old2dd.Back); - Assert.Null(old1.RootId); - Assert.Null(old1d.DerivedRootId); - Assert.Null(old1dd.MoreDerivedRootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - Assert.Equal(old1d.AlternateId, old2d.BackId); - Assert.Equal(old1dd.AlternateId, old2dd.BackId); - }, - context => - { - LoadRoot(context); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded1d = context.Set().Single(e => e.Id == old1d.Id); - var loaded1dd = context.Set().Single(e => e.Id == old1dd.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2d = context.Set().Single(e => e.Id == old2d.Id); - var loaded2dd = context.Set().Single(e => e.Id == old2dd.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - Assert.Null(loaded1.Root); - Assert.Null(loaded1d.Root); - Assert.Null(loaded1dd.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Same(loaded1d, loaded2d.Back); - Assert.Same(loaded1dd, loaded2dd.Back); - Assert.Null(loaded1.RootId); - Assert.Null(loaded1d.RootId); - Assert.Null(loaded1dd.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); - Assert.Equal(loaded1d.AlternateId, loaded2d.BackId); - Assert.Equal(loaded1dd.AlternateId, loaded2dd.BackId); - }); - } - - [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)] - public virtual void Save_required_one_to_one_changed_by_reference_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) - { - RequiredSingleAk2 new2 = null; - RequiredSingleComposite2 new2c = null; - RequiredSingleAk1 new1 = null;; - Root newRoot; - RequiredSingleAk1 old1 = null; - RequiredSingleAk2 old2 = null; - RequiredSingleComposite2 old2c = null; - - ExecuteWithStrategyInTransaction( - context => - { - new2 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2c = context.CreateProxy(); - new1 = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2; - e.SingleComposite = new2c; - }); - newRoot = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.RequiredSingleAk = new1; - }); - - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new2, new2c); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - old1 = root.RequiredSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1).Reference(e => e.SingleComposite).Load(); - } - - old2 = root.RequiredSingleAk.Single; - old2c = root.RequiredSingleAk.SingleComposite; - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2c = context.Set().Single(e => e.Id == new2c.Id); - } - else - { - context.AddRange(new1, new2, new2c); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredSingleAk = new1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1.Id, new2c.BackId); - Assert.Equal(new1.AlternateId, new2c.BackAlternateId); - Assert.Same(root, new1.Root); - Assert.Same(new1, new2.Back); - Assert.Same(new1, new2c.Back); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Null(old2c.Back); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.BackAlternateId); - }, - context => - { - LoadRoot(context); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2c.Id)); - }); - } - - [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_with_alternate_key( - ChangeMechanism changeMechanism, bool useExistingEntities) - { - RequiredNonPkSingleAk2 new2 = null; - RequiredNonPkSingleAk2Derived new2d = null; - RequiredNonPkSingleAk2MoreDerived new2dd = null; - RequiredNonPkSingleAk1 new1 = null; - RequiredNonPkSingleAk1Derived new1d = null; - RequiredNonPkSingleAk1MoreDerived new1dd = null; - Root newRoot; - RequiredNonPkSingleAk1 old1 = null; - RequiredNonPkSingleAk1Derived old1d = null; - RequiredNonPkSingleAk1MoreDerived old1dd = null; - RequiredNonPkSingleAk2 old2 = null; - RequiredNonPkSingleAk2Derived old2d = null; - RequiredNonPkSingleAk2MoreDerived old2dd = null; - - ExecuteWithStrategyInTransaction( - context => - { - new2 = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2d = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new2dd = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - new1 = context.CreateProxy(e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2; - }); - new1d = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2d; - e.Root = context.CreateProxy(); - }); - new1dd = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.Single = new2dd; - e.Root = context.CreateProxy(); - e.DerivedRoot = context.CreateProxy(); - }); - newRoot = context.CreateProxy( - e => - { - e.AlternateId = Guid.NewGuid(); - e.RequiredNonPkSingleAk = new1; - e.RequiredNonPkSingleAkDerived = new1d; - e.RequiredNonPkSingleAkMoreDerived = new1dd; - }); - - if (useExistingEntities) - { - context.AddRange(newRoot, new1, new1d, new1dd, new2, new2d, new2dd); - context.SaveChanges(); - } - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - context.Entry(root).Reference(e => e.RequiredNonPkSingleAkDerived).Load(); - context.Entry(root).Reference(e => e.RequiredNonPkSingleAkMoreDerived).Load(); - } - - old1 = root.RequiredNonPkSingleAk; - old1d = root.RequiredNonPkSingleAkDerived; - old1dd = root.RequiredNonPkSingleAkMoreDerived; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1d).Reference(e => e.Single).Load(); - context.Entry(old1dd).Reference(e => e.Single).Load(); - context.Entry(old1d).Reference(e => e.Root).Load(); - context.Entry(old1dd).Reference(e => e.Root).Load(); - context.Entry(old1dd).Reference(e => e.DerivedRoot).Load(); - } - - old2 = root.RequiredNonPkSingleAk.Single; - old2d = (RequiredNonPkSingleAk2Derived)root.RequiredNonPkSingleAkDerived.Single; - old2dd = (RequiredNonPkSingleAk2MoreDerived)root.RequiredNonPkSingleAkMoreDerived.Single; - - context.Set().Remove(old1d); - context.Set().Remove(old1dd); - - if (useExistingEntities) - { - new1 = context.Set().Single(e => e.Id == new1.Id); - new1d = (RequiredNonPkSingleAk1Derived)context.Set().Single(e => e.Id == new1d.Id); - new1dd = (RequiredNonPkSingleAk1MoreDerived)context.Set().Single(e => e.Id == new1dd.Id); - new2 = context.Set().Single(e => e.Id == new2.Id); - new2d = (RequiredNonPkSingleAk2Derived)context.Set().Single(e => e.Id == new2d.Id); - new2dd = (RequiredNonPkSingleAk2MoreDerived)context.Set().Single(e => e.Id == new2dd.Id); - - new1d.RootId = old1d.RootId; - new1dd.RootId = old1dd.RootId; - new1dd.DerivedRootId = old1dd.DerivedRootId; - } - else - { - new1d.Root = old1d.Root; - new1dd.Root = old1dd.Root; - new1dd.DerivedRoot = old1dd.DerivedRoot; - context.AddRange(new1, new1d, new1dd, new2, new2d, new2dd); - } - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredNonPkSingleAk = new1; - root.RequiredNonPkSingleAkDerived = new1d; - root.RequiredNonPkSingleAkMoreDerived = new1dd; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - new1.Root = root; - new1d.DerivedRoot = root; - new1dd.MoreDerivedRoot = root; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - new1.RootId = root.AlternateId; - new1d.DerivedRootId = root.AlternateId; - new1dd.MoreDerivedRootId = root.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(root.AlternateId, new1.RootId); - Assert.Equal(root.AlternateId, new1d.DerivedRootId); - Assert.Equal(root.AlternateId, new1dd.MoreDerivedRootId); - Assert.Equal(new1.AlternateId, new2.BackId); - Assert.Equal(new1d.AlternateId, new2d.BackId); - Assert.Equal(new1dd.AlternateId, new2dd.BackId); - Assert.Same(root, new1.Root); - Assert.Same(root, new1d.DerivedRoot); - Assert.Same(root, new1dd.MoreDerivedRoot); - Assert.Same(new1, new2.Back); - Assert.Same(new1d, new2d.Back); - Assert.Same(new1dd, new2dd.Back); - - Assert.Null(old1.Root); - Assert.Null(old1d.DerivedRoot); - Assert.Null(old1dd.MoreDerivedRoot); - Assert.Null(old2.Back); - Assert.Null(old2d.Back); - Assert.Null(old2dd.Back); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1d.AlternateId, old2d.BackId); - Assert.Equal(old1dd.AlternateId, old2dd.BackId); - }, - context => - { - var loadedRoot = LoadRoot(context); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old1d.Id)); - Assert.False(context.Set().Any(e => e.Id == old1dd.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2d.Id)); - Assert.False(context.Set().Any(e => e.Id == old2dd.Id)); - }); - } - - [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) - { - Root root = null; - OptionalSingleAk1 old1 = null; - OptionalSingleAk2 old2 = null; - OptionalSingleComposite2 old2c = null; - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - } - - old1 = root.OptionalSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1).Reference(e => e.SingleComposite).Load(); - } - - old2 = root.OptionalSingleAk.Single; - old2c = root.OptionalSingleAk.SingleComposite; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.OptionalSingleAk = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = null; - } - - Assert.False(context.Entry(root).Reference(e => e.OptionalSingleAk).IsLoaded); - Assert.False(context.Entry(old1).Reference(e => e.Root).IsLoaded); - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Null(old1.RootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - }, - context => - { - if ((changeMechanism & ChangeMechanism.Fk) == 0) - { - var loadedRoot = LoadRoot(context); - - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - Assert.Null(loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Null(loaded1.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); - } - }); - } - - [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) - { - Root root = null; - RequiredSingleAk1 old1 = null; - RequiredSingleAk2 old2 = null; - RequiredSingleComposite2 old2c = null; - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - old1 = root.RequiredSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1).Reference(e => e.SingleComposite).Load(); - } - - old2 = root.RequiredSingleAk.Single; - old2c = root.RequiredSingleAk.SingleComposite; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredSingleAk = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - 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()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Null(old2c.Back); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.BackAlternateId); - }, - context => - { - var loadedRoot = LoadRoot(context); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - Assert.False(context.Set().Any(e => e.Id == old2c.Id)); - }); - } - - [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) - { - Root root = null; - RequiredNonPkSingleAk1 old1 = null; - RequiredNonPkSingleAk2 old2 = null; - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - old1 = root.RequiredNonPkSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - } - - old2 = root.RequiredNonPkSingleAk.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - root.RequiredNonPkSingleAk = null; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = null; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - throw new ArgumentOutOfRangeException(nameof(changeMechanism)); - } - - if (!DoesChangeTracking) - { - 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()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(old1.Root); - Assert.Null(old2.Back); - Assert.Equal(old1.AlternateId, old2.BackId); - }, - context => - { - var loadedRoot = LoadRoot(context); - - Assert.False(context.Set().Any(e => e.Id == old1.Id)); - Assert.False(context.Set().Any(e => e.Id == old2.Id)); - }); - } - - [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) - { - Root newRoot = null; - Root root; - OptionalSingleAk1 old1 = null; - OptionalSingleAk2 old2 = null; - OptionalSingleComposite2 old2c = null; - - ExecuteWithStrategyInTransaction( - context => - { - newRoot = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - - if (useExistingRoot) - { - context.Add(newRoot); - context.SaveChanges(); - } - }, - context => - { - root = LoadRoot(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - } - - old1 = root.OptionalSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1).Reference(e => e.SingleComposite).Load(); - } - - old2 = root.OptionalSingleAk.Single; - old2c = root.OptionalSingleAk.SingleComposite; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.OptionalSingleAk = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = newRoot.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.OptionalSingleAk); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(newRoot.AlternateId, old1.RootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.ParentAlternateId); - }, - context => - { - LoadRoot(context); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Equal(newRoot.AlternateId, loaded1.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.ParentAlternateId); - }); - } - - [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) - { - Root newRoot = null; - Root root; - RequiredSingleAk1 old1 = null; - RequiredSingleAk2 old2 = null; - RequiredSingleComposite2 old2c = null; - - ExecuteWithStrategyInTransaction( - context => - { - newRoot = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - - if (useExistingRoot) - { - context.Add(newRoot); - context.SaveChanges(); - } - }, - context => - { - root = LoadRoot(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - old1 = root.RequiredSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - context.Entry(old1).Reference(e => e.SingleComposite).Load(); - } - - old2 = root.RequiredSingleAk.Single; - old2c = root.RequiredSingleAk.SingleComposite; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.RequiredSingleAk = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = newRoot.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.RequiredSingleAk); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Same(old1, old2c.Back); - Assert.Equal(newRoot.AlternateId, old1.RootId); - Assert.Equal(old1.AlternateId, old2.BackId); - Assert.Equal(old1.Id, old2c.BackId); - Assert.Equal(old1.AlternateId, old2c.BackAlternateId); - }, - context => - { - var loadedRoot = LoadRoot(context); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - var loaded2c = context.Set().Single(e => e.Id == old2c.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Same(loaded1, loaded2c.Back); - Assert.Equal(newRoot.AlternateId, loaded1.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - Assert.Equal(loaded1.Id, loaded2c.BackId); - Assert.Equal(loaded1.AlternateId, loaded2c.BackAlternateId); - }); - } - - [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) - { - Root newRoot = null; - Root root; - RequiredNonPkSingleAk1 old1 = null; - RequiredNonPkSingleAk2 old2 = null; - - ExecuteWithStrategyInTransaction( - context => - { - newRoot = context.CreateProxy(e => e.AlternateId = Guid.NewGuid()); - - if (useExistingRoot) - { - context.Add(newRoot); - context.SaveChanges(); - } - }, - context => - { - root = LoadRoot(context); - - context.Entry(newRoot).State = useExistingRoot ? EntityState.Unchanged : EntityState.Added; - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - old1 = root.RequiredNonPkSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(old1).Reference(e => e.Single).Load(); - } - - old2 = root.RequiredNonPkSingleAk.Single; - - if ((changeMechanism & ChangeMechanism.Principal) != 0) - { - newRoot.RequiredNonPkSingleAk = old1; - } - - if ((changeMechanism & ChangeMechanism.Dependent) != 0) - { - old1.Root = newRoot; - } - - if ((changeMechanism & ChangeMechanism.Fk) != 0) - { - old1.RootId = newRoot.AlternateId; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Same(newRoot, old1.Root); - Assert.Same(old1, old2.Back); - Assert.Equal(newRoot.AlternateId, old1.RootId); - Assert.Equal(old1.AlternateId, old2.BackId); - }, - context => - { - var loadedRoot = LoadRoot(context); - - newRoot = context.Set().Single(e => e.Id == newRoot.Id); - var loaded1 = context.Set().Single(e => e.Id == old1.Id); - var loaded2 = context.Set().Single(e => e.Id == old2.Id); - - Assert.Same(newRoot, loaded1.Root); - Assert.Same(loaded1, loaded2.Back); - Assert.Equal(newRoot.AlternateId, loaded1.RootId); - Assert.Equal(loaded1.AlternateId, loaded2.BackId); - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - Assert.Equal(2, root.RequiredChildren.Count()); - - var removed = root.RequiredChildren.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - } - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Single(root.RequiredChildren); - 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - Assert.Single(root.RequiredChildren); - 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))); - } - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - } - - Assert.Equal(2, root.OptionalChildren.Count()); - - var removed = root.OptionalChildren.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Children.ToList(); - orphanedIds = orphaned.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - } - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingle).Load(); - } - - var removed = root.OptionalSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, - context => - { - var root = LoadRoot(context); - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - var removed = root.RequiredSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - var removed = root.RequiredNonPkSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Null(root.RequiredNonPkSingle); - - 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); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - } - - Assert.Equal(2, root.OptionalChildrenAk.Count()); - - var removed = root.OptionalChildrenAk.First(); - context.Entry(removed).Collection(e => e.CompositeChildren).Load(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Children.ToList(); - orphanedIds = orphaned.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - } - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - }); - } - - [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; - List orphanedIdCs = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - Assert.Equal(2, root.RequiredChildrenAk.Count()); - - var removed = root.RequiredChildrenAk.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - context.Entry(removed).Collection(e => e.CompositeChildren).Load(); - } - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - var cascadeRemovedC = removed.CompositeChildren.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - Assert.Equal(2, orphanedIdCs.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Single(root.RequiredChildrenAk); - 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - Assert.Single(root.RequiredChildrenAk); - 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))); - } - }); - } - - [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; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - } - - var removed = root.OptionalSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - context.Entry(removed).Reference(e => e.SingleComposite).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - var orphanedC = removed.SingleComposite; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - } - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); - }); - } - - [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; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - var removed = root.RequiredSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - context.Entry(removed).Reference(e => e.SingleComposite).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - var orphanedC = removed.SingleComposite; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - Assert.Equal(EntityState.Detached, context.Entry(orphanedC).State); - - Assert.Null(root.RequiredSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - 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)); - } - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - var removed = root.RequiredNonPkSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Null(root.RequiredNonPkSingleAk); - - 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); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - var removed = root.RequiredChildren.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - } - - removedId = removed.Id; - orphanedIds = removed.Children.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - }, - context => - { - 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); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(root.RequiredChildren); - 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.Same(root, removed.Parent); - Assert.Empty(removed.Children); - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - Assert.Single(root.RequiredChildren); - 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))); - }); - } - - [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; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - var removed = root.RequiredSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - orphanedId = removed.Single.Id; - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = context.Set().Include(e => e.RequiredSingle).Single(IsTheRoot); - - var removed = root.RequiredSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == CascadeTiming.Never) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - var removed = root.RequiredNonPkSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - orphanedId = removed.Single.Id; - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = context.Set().Include(e => e.RequiredNonPkSingle).Single(IsTheRoot); - - var removed = root.RequiredNonPkSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - if (cascadeDeleteTiming == CascadeTiming.Never) - { - Assert.Throws(() => context.SaveChanges()); - } - else - { - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - Assert.Null(root.RequiredNonPkSingle); - - 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); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - List orphanedIdCs = null; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - var removed = root.RequiredChildrenAk.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - context.Entry(removed).Collection(e => e.CompositeChildren).Load(); - } - - removedId = removed.Id; - orphanedIds = removed.Children.Select(e => e.Id).ToList(); - orphanedIdCs = removed.CompositeChildren.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - Assert.Equal(2, orphanedIdCs.Count); - }, - context => - { - 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); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(root.RequiredChildrenAk); - 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.Same(root, removed.Parent); - Assert.Empty(removed.Children); // Never loaded - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - Assert.Single(root.RequiredChildrenAk); - 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))); - }); - } - - [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; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - var removed = root.RequiredSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - context.Entry(removed).Reference(e => e.SingleComposite).Load(); - } - - removedId = removed.Id; - orphanedId = removed.Single.Id; - orphanedIdC = removed.SingleComposite.Id; - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = context.Set().Include(e => e.RequiredSingleAk).Single(IsTheRoot); - - var removed = root.RequiredSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Null(root.RequiredSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedIdC)); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - 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)); - } - }); - } - - [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; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - var removed = root.RequiredNonPkSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - orphanedId = removed.Single.Id; - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = context.Set().Include(e => e.RequiredNonPkSingleAk).Single(IsTheRoot); - - var removed = root.RequiredNonPkSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Null(root.RequiredNonPkSingleAk); - - 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); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - } - - var removed = root.OptionalChildren.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - } - - removedId = removed.Id; - orphanedIds = removed.Children.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - }, - context => - { - 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); - - Assert.Equal(2, orphanedIds.Count); - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIds.Count, orphaned.Count); - Assert.True(orphaned.All(e => e.ParentId == null)); - - Assert.Same(root, removed.Parent); - Assert.Empty(removed.Children); // Never loaded - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - } - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIds.Count, orphaned.Count); - Assert.True(orphaned.All(e => e.ParentId == null)); - }); - } - - [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; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingle).Load(); - } - - var removed = root.OptionalSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - orphanedId = removed.Single.Id; - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = context.Set().Include(e => e.OptionalSingle).Single(IsTheRoot); - - var removed = root.OptionalSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - var orphaned = removed.Single; - - context.Remove(removed); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingle).Load(); - } - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); - }); - } - - [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; - List orphanedIdCs = null; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - } - - var removed = root.OptionalChildrenAk.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - context.Entry(removed).Collection(e => e.CompositeChildren).Load(); - } - - removedId = removed.Id; - orphanedIds = removed.Children.Select(e => e.Id).ToList(); - orphanedIdCs = removed.CompositeChildren.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - Assert.Equal(2, orphanedIdCs.Count); - }, - context => - { - 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); - - context.Remove(removed); - - foreach (var toOrphan in context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList()) - { - toOrphan.ParentId = null; - } - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIds.Count, orphaned.Count); - Assert.True(orphaned.All(e => e.ParentId == null)); - - var orphanedC = context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIdCs.Count, orphanedC.Count); - Assert.True(orphanedC.All(e => e.ParentId == null)); - - Assert.Same(root, removed.Parent); - Assert.Empty(removed.Children); // Never loaded - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - } - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - - var orphaned = context.Set().Where(e => orphanedIds.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIds.Count, orphaned.Count); - Assert.True(orphaned.All(e => e.ParentId == null)); - - var orphanedC = context.Set().Where(e => orphanedIdCs.Contains(e.Id)).ToList(); - Assert.Equal(orphanedIdCs.Count, orphanedC.Count); - Assert.True(orphanedC.All(e => e.ParentId == null)); - }); - } - - [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; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - } - - var removed = root.OptionalSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - context.Entry(removed).Reference(e => e.SingleComposite).Load(); - } - - removedId = removed.Id; - orphanedId = removed.Single.Id; - orphanedIdC = removed.SingleComposite.Id; - }, - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = context.Set().Include(e => e.OptionalSingleAk).Single(IsTheRoot); - - var removed = root.OptionalSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - var orphaned = removed.Single; - - context.Remove(removed); - - // Cannot have SET NULL action in the store because one of the FK columns - // is not nullable, so need to do this on the EF side. - context.Set().Single(e => e.Id == orphanedIdC).BackId = null; - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); - Assert.Null(context.Set().Single(e => e.Id == orphanedIdC).BackId); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, - context => - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - } - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Null(context.Set().Single(e => e.Id == orphanedId).BackId); - Assert.Null(context.Set().Single(e => e.Id == orphanedIdC).BackId); - }); - } - - [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; - Root root = null; - Required1 removed = null; - List cascadeRemoved = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - removed = root.RequiredChildren.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - } - - cascadeRemoved = removed.Children.ToList(); - - Assert.Equal(2, root.RequiredChildren.Count()); - }, - context => - { - 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); - - var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.True(cascadeRemoved.All(e => context.Entry(e).State == expectedState)); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - Assert.Single(root.RequiredChildren); - 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))); - } - }); - } - - [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; - Root root = null; - Optional1 removed = null; - List orphaned = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - } - - removed = root.OptionalChildren.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - } - - orphaned = removed.Children.ToList(); - - Assert.Equal(2, root.OptionalChildren.Count()); - }, - context => - { - 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); - - var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate - ? EntityState.Modified - : EntityState.Unchanged; - - Assert.True(orphaned.All(e => context.Entry(e).State == expectedState)); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.True(orphaned.All(e => context.Entry(e).State == EntityState.Unchanged)); - - Assert.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - }, - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildren).Load(); - } - - Assert.Single(root.OptionalChildren); - Assert.DoesNotContain(removedId, root.OptionalChildren.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - }); - } - - [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; - Root root = null; - OptionalSingle1 removed = null; - OptionalSingle2 orphaned = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingle).Load(); - } - - removed = root.OptionalSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - orphaned = removed.Single; - }, - context => - { - 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); - - var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate - ? EntityState.Modified - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingle).Load(); - } - - Assert.Null(root.OptionalSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - }); - } - - [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; - Root root = null; - RequiredSingle1 removed = null; - RequiredSingle2 orphaned = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - removed = root.RequiredSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - orphaned = removed.Single; - }, - context => - { - 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); - - var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - Root root = null; - RequiredNonPkSingle1 removed = null; - RequiredNonPkSingle2 orphaned = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - removed = root.RequiredNonPkSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - orphaned = removed.Single; - }, - context => - { - 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); - - var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - List orphanedIdCs = null; - Root root = null; - OptionalAk1 removed = null; - List orphaned = null; - List orphanedC = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - } - - removed = root.OptionalChildrenAk.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - context.Entry(removed).Collection(e => e.CompositeChildren).Load(); - } - - orphaned = removed.Children.ToList(); - orphanedC = removed.CompositeChildren.ToList(); - - Assert.Equal(2, root.OptionalChildrenAk.Count()); - }, - context => - { - 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); - - 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()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - }, - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.OptionalChildrenAk).Load(); - } - - Assert.Single(root.OptionalChildrenAk); - Assert.DoesNotContain(removedId, root.OptionalChildrenAk.Select(e => e.Id)); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(orphanedIds.Count, context.Set().Count(e => orphanedIds.Contains(e.Id))); - Assert.Equal(orphanedIdCs.Count, context.Set().Count(e => orphanedIdCs.Contains(e.Id))); - }); - } - - [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; - List orphanedIdCs = null; - Root root = null; - RequiredAk1 removed = null; - List cascadeRemoved = null; - List cascadeRemovedC = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - removed = root.RequiredChildrenAk.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - context.Entry(removed).Collection(e => e.CompositeChildren).Load(); - } - - cascadeRemoved = removed.Children.ToList(); - cascadeRemovedC = removed.CompositeChildren.ToList(); - - Assert.Equal(2, root.RequiredChildrenAk.Count()); - }, - context => - { - 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); - - 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()); - - 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.Same(root, removed.Parent); - Assert.Equal(2, removed.Children.Count()); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - Assert.Single(root.RequiredChildrenAk); - 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))); - } - }); - } - - [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; - var orphanedIdC = 0; - Root root = null; - OptionalSingleAk1 removed = null; - OptionalSingleAk2 orphaned = null; - OptionalSingleComposite2 orphanedC = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - } - - removed = root.OptionalSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - context.Entry(removed).Reference(e => e.SingleComposite).Load(); - } - - orphaned = removed.Single; - orphanedC = removed.SingleComposite; - }, - context => - { - 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); - - 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()); - - context.SaveChanges(); - - Assert.False(context.ChangeTracker.HasChanges()); - - Assert.Equal(EntityState.Detached, context.Entry(removed).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphaned).State); - Assert.Equal(EntityState.Unchanged, context.Entry(orphanedC).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - }, - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.OptionalSingleAk).Load(); - } - - Assert.Null(root.OptionalSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedId)); - Assert.Equal(1, context.Set().Count(e => e.Id == orphanedIdC)); - }); - } - - [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; - var orphanedIdC = 0; - Root root = null; - RequiredSingleAk1 removed = null; - RequiredSingleAk2 orphaned = null; - RequiredSingleComposite2 orphanedC = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - removed = root.RequiredSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - context.Entry(removed).Reference(e => e.SingleComposite).Load(); - } - - orphaned = removed.Single; - orphanedC = removed.SingleComposite; - }, - context => - { - 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); - - 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()); - - 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.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); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - 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)); - } - }); - } - - [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; - Root root = null; - RequiredNonPkSingleAk1 removed = null; - RequiredNonPkSingleAk2 orphaned = null; - - ExecuteWithStrategyInTransaction( - context => - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - removed = root.RequiredNonPkSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - orphaned = removed.Single; - }, - context => - { - 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); - - var expectedState = cascadeDeleteTiming == CascadeTiming.Immediate - ? EntityState.Deleted - : EntityState.Unchanged; - - Assert.Equal(expectedState, context.Entry(orphaned).State); - - Assert.True(context.ChangeTracker.HasChanges()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - Assert.Equal(2, root.RequiredChildren.Count()); - - var removed = root.RequiredChildren.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - } - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - - var added = context.CreateProxy(); - Add(removed.Children, added); - - if (context.ChangeTracker.AutoDetectChangesEnabled - && !DoesChangeTracking) - { - 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)); - - context.Remove(removed); - - 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()); - - 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.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()); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildren).Load(); - } - - Assert.Single(root.RequiredChildren); - 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))); - } - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - var removed = root.RequiredSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Entry(orphaned).State = EntityState.Added; - - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.Remove(removed); - - 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()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingle).Load(); - } - - Assert.Null(root.RequiredSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - var removed = root.RequiredNonPkSingle; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Entry(orphaned).State = EntityState.Added; - - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.Remove(removed); - - 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()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingle).Load(); - } - - Assert.Null(root.RequiredNonPkSingle); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [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; - List orphanedIdCs = null; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - Assert.Equal(2, root.RequiredChildrenAk.Count()); - - var removed = root.RequiredChildrenAk.First(); - - if (!DoesLazyLoading) - { - context.Entry(removed).Collection(e => e.Children).Load(); - context.Entry(removed).Collection(e => e.CompositeChildren).Load(); - } - - removedId = removed.Id; - var cascadeRemoved = removed.Children.ToList(); - var cascadeRemovedC = removed.CompositeChildren.ToList(); - orphanedIds = cascadeRemoved.Select(e => e.Id).ToList(); - orphanedIdCs = cascadeRemovedC.Select(e => e.Id).ToList(); - - Assert.Equal(2, orphanedIds.Count); - Assert.Equal(2, orphanedIdCs.Count); - - var added = context.CreateProxy(); - var addedC = context.CreateProxy(); - Add(removed.Children, added); - Add(removed.CompositeChildren, addedC); - - if (context.ChangeTracker.AutoDetectChangesEnabled - && !DoesChangeTracking) - { - context.ChangeTracker.DetectChanges(); - } - - Assert.Equal(EntityState.Unchanged, 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)); - - context.Remove(removed); - - 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()); - - 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.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()); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Collection(e => e.RequiredChildrenAk).Load(); - } - - Assert.Single(root.RequiredChildrenAk); - 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))); - } - }); - } - - [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; - var orphanedIdC = 0; - - ExecuteWithStrategyInTransaction( - context => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - var removed = root.RequiredSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - context.Entry(removed).Reference(e => e.SingleComposite).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - var orphanedC = removed.SingleComposite; - orphanedId = orphaned.Id; - orphanedIdC = orphanedC.Id; - - context.Entry(orphaned).State = EntityState.Added; - context.Entry(orphanedC).State = EntityState.Added; - - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - Assert.Equal(EntityState.Added, context.Entry(orphanedC).State); - - context.Remove(removed); - - 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()); - - 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.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); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredSingleAk).Load(); - } - - 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)); - } - }); - } - - [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 => - { - context.ChangeTracker.CascadeDeleteTiming = cascadeDeleteTiming; - context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; - - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - var removed = root.RequiredNonPkSingleAk; - - if (!DoesLazyLoading) - { - context.Entry(removed).Reference(e => e.Single).Load(); - } - - removedId = removed.Id; - var orphaned = removed.Single; - orphanedId = orphaned.Id; - - context.Entry(orphaned).State = EntityState.Added; - - Assert.Equal(EntityState.Unchanged, context.Entry(removed).State); - Assert.Equal(EntityState.Added, context.Entry(orphaned).State); - - context.Remove(removed); - - 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()); - - 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.Equal(EntityState.Detached, context.Entry(orphaned).State); - - Assert.Same(root, removed.Root); - Assert.Same(orphaned, removed.Single); - } - }, - context => - { - if (cascadeDeleteTiming != CascadeTiming.Never) - { - var root = LoadRoot(context); - - if (!DoesLazyLoading) - { - context.Entry(root).Reference(e => e.RequiredNonPkSingleAk).Load(); - } - - Assert.Null(root.RequiredNonPkSingleAk); - - Assert.Empty(context.Set().Where(e => e.Id == removedId)); - Assert.Empty(context.Set().Where(e => e.Id == orphanedId)); - } - }); - } - - [ConditionalFact] - public virtual void Sometimes_not_calling_DetectChanges_when_required_does_not_throw_for_null_ref() - { - ExecuteWithStrategyInTransaction( - context => - { - var dependent = context.Set().Single(); - - dependent.BadCustomerId = null; - - var principal = context.Set().Single(); - - principal.Status++; - - Assert.Null(dependent.BadCustomerId); - Assert.Null(dependent.BadCustomer); - Assert.Empty(principal.BadOrders); - - context.SaveChanges(); - - Assert.Null(dependent.BadCustomerId); - Assert.Null(dependent.BadCustomer); - Assert.Empty(principal.BadOrders); - }, - context => - { - var dependent = context.Set().Single(); - var principal = context.Set().Single(); - - Assert.Null(dependent.BadCustomerId); - Assert.Null(dependent.BadCustomer); - Assert.Empty(principal.BadOrders); - }); - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs new file mode 100644 index 00000000000..70f249dea1a --- /dev/null +++ b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/GraphUpdatesSqlServerTest.cs @@ -0,0 +1,138 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; +using Microsoft.EntityFrameworkCore.TestUtilities; + +namespace Microsoft.EntityFrameworkCore +{ + public class GraphUpdatesSqlServerTest + { + public class ClientCascade : GraphUpdatesSqlServerTestBase + { + public ClientCascade(SqlServerFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqlServerFixture : GraphUpdatesSqlServerFixtureBase + { + public override bool NoStoreCascades => true; + + protected override string StoreName { get; } = "GraphClientCascadeUpdatesTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + foreach (var foreignKey in modelBuilder.Model + .GetEntityTypes() + .SelectMany(e => MutableEntityTypeExtensions.GetDeclaredForeignKeys(e)) + .Where(e => e.DeleteBehavior == DeleteBehavior.Cascade)) + { + foreignKey.DeleteBehavior = DeleteBehavior.ClientCascade; + } + } + } + } + + public class ClientNoAction : GraphUpdatesSqlServerTestBase + { + public ClientNoAction(SqlServerFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqlServerFixture : GraphUpdatesSqlServerFixtureBase + { + public override bool ForceClientNoAction => true; + + protected override string StoreName { get; } = "GraphClientNoActionUpdatesTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + base.OnModelCreating(modelBuilder, context); + + foreach (var foreignKey in modelBuilder.Model + .GetEntityTypes() + .SelectMany(e => e.GetDeclaredForeignKeys())) + { + foreignKey.DeleteBehavior = DeleteBehavior.ClientNoAction; + } + } + } + } + + public class Identity : GraphUpdatesSqlServerTestBase + { + public Identity(SqlServerFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqlServerFixture : GraphUpdatesSqlServerFixtureBase + { + protected override string StoreName { get; } = "GraphIdentityUpdatesTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + modelBuilder.UseIdentityColumns(); + + base.OnModelCreating(modelBuilder, context); + } + } + } + + public class HiLo : GraphUpdatesSqlServerTestBase + { + public HiLo(SqlServerFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqlServerFixture : GraphUpdatesSqlServerFixtureBase + { + protected override string StoreName { get; } = "GraphHiLoUpdatesTest"; + + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + { + modelBuilder.UseHiLo(); + + base.OnModelCreating(modelBuilder, context); + } + } + } + + public abstract class GraphUpdatesSqlServerTestBase : GraphUpdatesTestBase + where TFixture : GraphUpdatesSqlServerTestBase.GraphUpdatesSqlServerFixtureBase, new() + { + protected GraphUpdatesSqlServerTestBase(TFixture fixture) + : base(fixture) + { + } + + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public abstract class GraphUpdatesSqlServerFixtureBase : GraphUpdatesFixtureBase + { + public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; + protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; + } + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/ProxyGraphUpdatesSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdates/ProxyGraphUpdatesSqlServerTest.cs similarity index 100% rename from test/EFCore.SqlServer.FunctionalTests/ProxyGraphUpdatesSqlServerTest.cs rename to test/EFCore.SqlServer.FunctionalTests/GraphUpdates/ProxyGraphUpdatesSqlServerTest.cs diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestBase.cs deleted file mode 100644 index 70249b9f897..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestBase.cs +++ /dev/null @@ -1,27 +0,0 @@ -// 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.Infrastructure; -using Microsoft.EntityFrameworkCore.Storage; -using Microsoft.EntityFrameworkCore.TestUtilities; - -namespace Microsoft.EntityFrameworkCore -{ - public abstract class GraphUpdatesSqlServerTestBase : GraphUpdatesTestBase - where TFixture : GraphUpdatesSqlServerTestBase.GraphUpdatesSqlServerFixtureBase, new() - { - protected GraphUpdatesSqlServerTestBase(TFixture fixture) - : base(fixture) - { - } - - protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) - => facade.UseTransaction(transaction.GetDbTransaction()); - - public abstract class GraphUpdatesSqlServerFixtureBase : GraphUpdatesFixtureBase - { - public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override ITestStoreFactory TestStoreFactory => SqlServerTestStoreFactory.Instance; - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestClientCascade.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestClientCascade.cs deleted file mode 100644 index f50bc66b135..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestClientCascade.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Linq; - -namespace Microsoft.EntityFrameworkCore -{ - public class GraphUpdatesSqlServerTestClientCascade : GraphUpdatesSqlServerTestBase< - GraphUpdatesSqlServerTestClientCascade.GraphUpdatesWithClientCascadeSqlServerFixture> - { - public GraphUpdatesSqlServerTestClientCascade(GraphUpdatesWithClientCascadeSqlServerFixture fixture) - : base(fixture) - { - } - - public class GraphUpdatesWithClientCascadeSqlServerFixture : GraphUpdatesSqlServerFixtureBase - { - protected override string StoreName { get; } = "GraphClientCascadeUpdatesTest"; - public override bool NoStoreCascades => true; - - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) - { - base.OnModelCreating(modelBuilder, context); - - foreach (var foreignKey in modelBuilder.Model - .GetEntityTypes() - .SelectMany(e => e.GetDeclaredForeignKeys()) - .Where(e => e.DeleteBehavior == DeleteBehavior.Cascade)) - { - foreignKey.DeleteBehavior = DeleteBehavior.ClientCascade; - } - } - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestClientNoAction.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestClientNoAction.cs deleted file mode 100644 index 87b85852a89..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestClientNoAction.cs +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Linq; - -namespace Microsoft.EntityFrameworkCore -{ - public class GraphUpdatesSqlServerTestClientNoAction : GraphUpdatesSqlServerTestBase< - GraphUpdatesSqlServerTestClientNoAction.GraphUpdatesWithClientNoActionSqlServerFixture> - { - public GraphUpdatesSqlServerTestClientNoAction(GraphUpdatesWithClientNoActionSqlServerFixture fixture) - : base(fixture) - { - } - - public class GraphUpdatesWithClientNoActionSqlServerFixture : GraphUpdatesSqlServerFixtureBase - { - protected override string StoreName { get; } = "GraphClientNoActionUpdatesTest"; - public override bool ForceClientNoAction => true; - - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) - { - base.OnModelCreating(modelBuilder, context); - - foreach (var foreignKey in modelBuilder.Model - .GetEntityTypes() - .SelectMany(e => e.GetDeclaredForeignKeys())) - { - foreignKey.DeleteBehavior = DeleteBehavior.ClientNoAction; - } - } - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestIdentity.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestIdentity.cs deleted file mode 100644 index ca3233ccb6d..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestIdentity.cs +++ /dev/null @@ -1,26 +0,0 @@ -// 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 -{ - public class GraphUpdatesSqlServerTestIdentity : GraphUpdatesSqlServerTestBase< - GraphUpdatesSqlServerTestIdentity.GraphUpdatesWithIdentitySqlServerFixture> - { - public GraphUpdatesSqlServerTestIdentity(GraphUpdatesWithIdentitySqlServerFixture fixture) - : base(fixture) - { - } - - public class GraphUpdatesWithIdentitySqlServerFixture : GraphUpdatesSqlServerFixtureBase - { - protected override string StoreName { get; } = "GraphIdentityUpdatesTest"; - - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) - { - modelBuilder.UseIdentityColumns(); - - base.OnModelCreating(modelBuilder, context); - } - } - } -} diff --git a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestSequence.cs b/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestSequence.cs deleted file mode 100644 index 7c399d2f92f..00000000000 --- a/test/EFCore.SqlServer.FunctionalTests/GraphUpdatesSqlServerTestSequence.cs +++ /dev/null @@ -1,28 +0,0 @@ -// 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.TestUtilities; - -namespace Microsoft.EntityFrameworkCore -{ - [SqlServerCondition(SqlServerCondition.SupportsSequences)] - public class GraphUpdatesSqlServerTestSequence : GraphUpdatesSqlServerTestBase< - GraphUpdatesSqlServerTestSequence.GraphUpdatesWithSequenceSqlServerFixture> - { - public GraphUpdatesSqlServerTestSequence(GraphUpdatesWithSequenceSqlServerFixture fixture) - : base(fixture) - { - } - - public class GraphUpdatesWithSequenceSqlServerFixture : GraphUpdatesSqlServerFixtureBase - { - protected override string StoreName { get; } = "GraphSequenceUpdatesTest"; - - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) - { - modelBuilder.UseHiLo(); // ensure model uses sequences - base.OnModelCreating(modelBuilder, context); - } - } - } -} diff --git a/test/EFCore.Sqlite.FunctionalTests/GraphUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/GraphUpdates/GraphUpdatesSqliteTest.cs similarity index 67% rename from test/EFCore.Sqlite.FunctionalTests/GraphUpdatesSqliteTest.cs rename to test/EFCore.Sqlite.FunctionalTests/GraphUpdates/GraphUpdatesSqliteTest.cs index c3df4d5000f..234ba3b9970 100644 --- a/test/EFCore.Sqlite.FunctionalTests/GraphUpdatesSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/GraphUpdates/GraphUpdatesSqliteTest.cs @@ -4,16 +4,15 @@ using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Storage; using Microsoft.EntityFrameworkCore.TestUtilities; -using Xunit.Abstractions; namespace Microsoft.EntityFrameworkCore { - public abstract class GraphUpdatesSqliteTest + public class GraphUpdatesSqliteTest { - public abstract class GraphUpdatesSqliteTestBase : GraphUpdatesTestBase - where TFixture : GraphUpdatesSqliteTestBase.GraphUpdatesSqliteFixtureBase, new() + public class ChangedChangingNotifications + : GraphUpdatesTestBase { - protected GraphUpdatesSqliteTestBase(TFixture fixture) + public ChangedChangingNotifications(SqliteFixture fixture) : base(fixture) { } @@ -21,104 +20,116 @@ protected GraphUpdatesSqliteTestBase(TFixture fixture) protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) => facade.UseTransaction(transaction.GetDbTransaction()); - public abstract class GraphUpdatesSqliteFixtureBase : GraphUpdatesFixtureBase + public class SqliteFixture : GraphUpdatesSqliteTestBase.GraphUpdatesSqliteFixtureBase { - public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; - protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; - protected virtual bool AutoDetectChanges => false; + protected override string StoreName { get; } = "GraphUpdatesChangedChangingTest"; - public override PoolableDbContext CreateContext() + protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { - var context = base.CreateContext(); - context.ChangeTracker.AutoDetectChangesEnabled = AutoDetectChanges; + modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); - return context; + base.OnModelCreating(modelBuilder, context); } } } - public class SnapshotNotifications - : GraphUpdatesSqliteTestBase + public class ChangedNotifications + : GraphUpdatesTestBase { - public SnapshotNotifications(SnapshotNotificationsFixture fixture, ITestOutputHelper testOutputHelper) + public ChangedNotifications(SqliteFixture fixture) : base(fixture) { - //Fixture.TestSqlLoggerFactory.SetTestOutputHelper(testOutputHelper); } - public class SnapshotNotificationsFixture : GraphUpdatesSqliteFixtureBase + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqliteFixture : GraphUpdatesSqliteTestBase.GraphUpdatesSqliteFixtureBase { - protected override string StoreName { get; } = "GraphUpdatesSnapshotTest"; - protected override bool AutoDetectChanges => true; + protected override string StoreName { get; } = "GraphUpdatesChangedTest"; protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { - modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); + modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); base.OnModelCreating(modelBuilder, context); } } } - public class ChangedNotifications - : GraphUpdatesSqliteTestBase + public class FullWithOriginalsNotifications + : GraphUpdatesTestBase { - public ChangedNotifications(ChangedNotificationsFixture fixture) + public FullWithOriginalsNotifications(SqliteFixture fixture) : base(fixture) { } - public class ChangedNotificationsFixture : GraphUpdatesSqliteFixtureBase + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqliteFixture : GraphUpdatesSqliteTestBase.GraphUpdatesSqliteFixtureBase { - protected override string StoreName { get; } = "GraphUpdatesChangedTest"; + protected override string StoreName { get; } = "GraphUpdatesFullWithOriginalsTest"; protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { - modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangedNotifications); + modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); base.OnModelCreating(modelBuilder, context); } } } - public class ChangedChangingNotifications - : GraphUpdatesSqliteTestBase + public class SnapshotNotifications + : GraphUpdatesTestBase { - public ChangedChangingNotifications(ChangedChangingNotificationsFixture fixture) + public SnapshotNotifications(SqliteFixture fixture) : base(fixture) { } - public class ChangedChangingNotificationsFixture : GraphUpdatesSqliteFixtureBase + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public class SqliteFixture : GraphUpdatesSqliteTestBase.GraphUpdatesSqliteFixtureBase { - protected override string StoreName { get; } = "GraphUpdatesFullTest"; + protected override string StoreName { get; } = "GraphUpdatesSnapshotTest"; + + protected override bool AutoDetectChanges => true; protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) { - modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotifications); + modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.Snapshot); base.OnModelCreating(modelBuilder, context); } } } - public class FullWithOriginalsNotifications - : GraphUpdatesSqliteTestBase + public abstract class GraphUpdatesSqliteTestBase : GraphUpdatesTestBase + where TFixture : GraphUpdatesSqliteTestBase.GraphUpdatesSqliteFixtureBase, new() { - public FullWithOriginalsNotifications(FullWithOriginalsNotificationsFixture fixture) + protected GraphUpdatesSqliteTestBase(TFixture fixture) : base(fixture) { } - public class FullWithOriginalsNotificationsFixture : GraphUpdatesSqliteFixtureBase + protected override void UseTransaction(DatabaseFacade facade, IDbContextTransaction transaction) + => facade.UseTransaction(transaction.GetDbTransaction()); + + public abstract class GraphUpdatesSqliteFixtureBase : GraphUpdatesFixtureBase { - protected override string StoreName { get; } = "GraphUpdatesOriginalsTest"; + public TestSqlLoggerFactory TestSqlLoggerFactory => (TestSqlLoggerFactory)ListLoggerFactory; + protected override ITestStoreFactory TestStoreFactory => SqliteTestStoreFactory.Instance; + protected virtual bool AutoDetectChanges => false; - protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext context) + public override PoolableDbContext CreateContext() { - modelBuilder.HasChangeTrackingStrategy(ChangeTrackingStrategy.ChangingAndChangedNotificationsWithOriginalValues); + var context = base.CreateContext(); + context.ChangeTracker.AutoDetectChangesEnabled = AutoDetectChanges; - base.OnModelCreating(modelBuilder, context); + return context; } } } diff --git a/test/EFCore.Sqlite.FunctionalTests/ProxyGraphUpdatesSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/GraphUpdates/ProxyGraphUpdatesSqliteTest.cs similarity index 100% rename from test/EFCore.Sqlite.FunctionalTests/ProxyGraphUpdatesSqliteTest.cs rename to test/EFCore.Sqlite.FunctionalTests/GraphUpdates/ProxyGraphUpdatesSqliteTest.cs