From f69832bab9efc7a668a38a3c1265adcd233ace48 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Tue, 5 Jul 2022 21:03:13 +0100 Subject: [PATCH] Unidirectional many-to-many relationships Fixes #3864 --- ...erExpressionProcessingExpressionVisitor.cs | 20 +- .../Proxies/Internal/ProxyBindingRewriter.cs | 37 +- ...sitor.ShaperProcessingExpressionVisitor.cs | 24 +- src/EFCore/ChangeTracking/CollectionEntry.cs | 9 +- src/EFCore/ChangeTracking/CollectionEntry`.cs | 2 +- .../Internal/InternalEntityEntry.cs | 38 +- .../Internal/InternalEntityEntrySubscriber.cs | 7 +- .../Internal/NavigationFixer.cs | 6 +- .../ChangeTracking/Internal/StateManager.cs | 2 +- src/EFCore/Infrastructure/ModelValidator.cs | 10 - src/EFCore/Internal/ManyToManyLoader.cs | 58 +- .../Internal/ManyToManyLoaderFactory.cs | 8 +- .../Builders/CollectionNavigationBuilder.cs | 53 +- .../Builders/CollectionNavigationBuilder`.cs | 29 +- src/EFCore/Metadata/IClrCollectionAccessor.cs | 24 + .../Internal/ClrCollectionAccessor.cs | 44 +- .../Internal/ClrCollectionAccessorFactory.cs | 57 +- .../Internal/InternalEntityTypeBuilder.cs | 205 +- src/EFCore/Properties/CoreStrings.Designer.cs | 8 - src/EFCore/Properties/CoreStrings.resx | 3 - .../TPCManyToManyQueryRelationalFixture.cs | 6 +- .../TPTManyToManyQueryRelationalFixture.cs | 4 + .../ManyToManyLoadTestBase.cs | 5 +- .../ManyToManyTrackingTestBase.cs | 2 +- .../ManyToManyNoTrackingQueryTestBase.cs | 3 + .../Query/ManyToManyQueryFixtureBase.cs | 254 ++ .../Query/ManyToManyQueryTestBase.cs | 738 +++++ .../ManyToManyModel/ManyToManyContext.cs | 7 + .../ManyToManyModel/ManyToManyData.cs | 1216 ++++++- .../UnidirectionalEntityBranch.cs | 9 + .../UnidirectionalEntityCompositeKey.cs | 19 + .../UnidirectionalEntityLeaf.cs | 12 + .../UnidirectionalEntityOne.cs | 26 + .../UnidirectionalEntityRoot.cs | 11 + .../UnidirectionalEntityThree.cs | 24 + .../UnidirectionalEntityTwo.cs | 26 + .../UnidirectionalGeneratedKeysLeft.cs | 14 + .../UnidirectionalGeneratedKeysRight.cs | 14 + .../UnidirectionalJoinCompositeKeyToLeaf.cs | 15 + .../UnidirectionalJoinOneSelfPayload.cs | 13 + .../UnidirectionalJoinOneToBranch.cs | 10 + ...UnidirectionalJoinOneToThreePayloadFull.cs | 14 + .../UnidirectionalJoinOneToTwo.cs | 13 + .../UnidirectionalJoinOneToTwoExtra.cs | 12 + ...idirectionalJoinThreeToCompositeKeyFull.cs | 16 + .../UnidirectionalJoinTwoToThree.cs | 12 + .../UnidirectionalManyToManyLoadTestBase.cs | 1037 ++++++ ...nidirectionalManyToManyTrackingTestBase.cs | 2934 +++++++++++++++++ .../ManyToManyLoadSqlServerTest.cs | 15 + ...oManyTrackingGeneratedKeysSqlServerTest.cs | 6 + ...TrackingProxyGeneratedKeysSqlServerTest.cs | 19 + .../ManyToManyTrackingProxySqlServerTest.cs | 13 + .../ManyToManyTrackingSqlServerTestBase.cs | 15 + ...CManyToManyNoTrackingQuerySqlServerTest.cs | 2 +- .../Query/TPCManyToManyQuerySqlServerTest.cs | 2 +- ...TManyToManyNoTrackingQuerySqlServerTest.cs | 2 +- .../Query/TPTManyToManyQuerySqlServerTest.cs | 2 +- ...TemporalManyToManyQuerySqlServerFixture.cs | 173 + .../TemporalManyToManyQuerySqlServerTest.cs | 7 + .../ManyToManyLoadSqliteTestBase.cs | 15 + .../ManyToManyTrackingSqliteTest.cs | 16 + .../Internal/ShadowFixupTest.cs | 2 +- .../ModelBuilding/ManyToManyTestBase.cs | 38 - 63 files changed, 7070 insertions(+), 367 deletions(-) create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityBranch.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityCompositeKey.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityLeaf.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityOne.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityRoot.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityThree.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityTwo.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalGeneratedKeysLeft.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalGeneratedKeysRight.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinCompositeKeyToLeaf.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneSelfPayload.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToBranch.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToThreePayloadFull.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToTwo.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToTwoExtra.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinThreeToCompositeKeyFull.cs create mode 100644 test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinTwoToThree.cs create mode 100644 test/EFCore.Specification.Tests/UnidirectionalManyToManyLoadTestBase.cs create mode 100644 test/EFCore.Specification.Tests/UnidirectionalManyToManyTrackingTestBase.cs diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs index 5f8f37002ff..5331dc08316 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.ShaperExpressionProcessingExpressionVisitor.cs @@ -308,8 +308,10 @@ private static void IncludeCollection + var expressions = new List(); + + if (!navigation.IsShadowProperty()) { - navigation.IsCollection - ? AddToCollectionNavigation(entityParameter, relatedEntityParameter, navigation) - : AssignReferenceNavigation(entityParameter, relatedEntityParameter, navigation) + expressions.Add( + navigation.IsCollection + ? AddToCollectionNavigation(entityParameter, relatedEntityParameter, navigation) + : AssignReferenceNavigation(entityParameter, relatedEntityParameter, navigation)); }; - if (inverseNavigation != null) + if (inverseNavigation != null + && !inverseNavigation.IsShadowProperty()) { expressions.Add( inverseNavigation.IsCollection diff --git a/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs b/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs index 1287165a5f9..5e37f9f58d1 100644 --- a/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs +++ b/src/EFCore.Proxies/Proxies/Internal/ProxyBindingRewriter.cs @@ -78,30 +78,33 @@ public virtual void ProcessModelFinalizing( foreach (var navigationBase in entityType.GetDeclaredNavigations() .Concat(entityType.GetDeclaredSkipNavigations())) { - if (navigationBase.PropertyInfo == null) + if (!navigationBase.IsShadowProperty()) { - throw new InvalidOperationException( - ProxiesStrings.FieldProperty(navigationBase.Name, entityType.DisplayName())); - } - - if (_options.UseChangeTrackingProxies - && navigationBase.PropertyInfo.SetMethod?.IsReallyVirtual() == false) - { - throw new InvalidOperationException( - ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName())); - } + if (navigationBase.PropertyInfo == null) + { + throw new InvalidOperationException( + ProxiesStrings.FieldProperty(navigationBase.Name, entityType.DisplayName())); + } - if (_options.UseLazyLoadingProxies) - { - if (!navigationBase.PropertyInfo.GetMethod!.IsReallyVirtual() - && (!(navigationBase is INavigation navigation - && navigation.ForeignKey.IsOwnership))) + if (_options.UseChangeTrackingProxies + && navigationBase.PropertyInfo.SetMethod?.IsReallyVirtual() == false) { throw new InvalidOperationException( ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName())); } - navigationBase.SetPropertyAccessMode(PropertyAccessMode.Field); + if (_options.UseLazyLoadingProxies) + { + if (!navigationBase.PropertyInfo.GetMethod!.IsReallyVirtual() + && (!(navigationBase is INavigation navigation + && navigation.ForeignKey.IsOwnership))) + { + throw new InvalidOperationException( + ProxiesStrings.NonVirtualProperty(navigationBase.Name, entityType.DisplayName())); + } + + navigationBase.SetPropertyAccessMode(PropertyAccessMode.Field); + } } } diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index b9fca300552..e4809d7726d 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -548,7 +548,9 @@ protected override Expression VisitExtension(Expression extensionExpression) Expression.Constant(parentIdentifierLambda.Compile()), Expression.Constant(outerIdentifierLambda.Compile()), Expression.Constant(navigation), - Expression.Constant(navigation.GetCollectionAccessor()), + Expression.Constant(navigation.IsShadowProperty() + ? null + : navigation.GetCollectionAccessor(), typeof(IClrCollectionAccessor)), Expression.Constant(_isTracking), #pragma warning disable EF1001 // Internal EF Core API usage. Expression.Constant(includeExpression.SetLoaded))); @@ -937,14 +939,18 @@ private static LambdaExpression GenerateFixup( { var entityParameter = Expression.Parameter(entityType); var relatedEntityParameter = Expression.Parameter(relatedEntityType); - var expressions = new List + var expressions = new List(); + + if (!navigation.IsShadowProperty()) { - navigation.IsCollection - ? AddToCollectionNavigation(entityParameter, relatedEntityParameter, navigation) - : AssignReferenceNavigation(entityParameter, relatedEntityParameter, navigation) - }; + expressions.Add( + navigation.IsCollection + ? AddToCollectionNavigation(entityParameter, relatedEntityParameter, navigation) + : AssignReferenceNavigation(entityParameter, relatedEntityParameter, navigation)); + } - if (inverseNavigation != null) + if (inverseNavigation != null + && !inverseNavigation.IsShadowProperty()) { expressions.Add( inverseNavigation.IsCollection @@ -1201,7 +1207,7 @@ private static void InitializeIncludeCollection( Func parentIdentifier, Func outerIdentifier, INavigationBase navigation, - IClrCollectionAccessor clrCollectionAccessor, + IClrCollectionAccessor? clrCollectionAccessor, bool trackingQuery, bool setLoaded) where TParent : class @@ -1222,7 +1228,7 @@ private static void InitializeIncludeCollection( } } - collection = clrCollectionAccessor.GetOrCreate(entity, forMaterialization: true); + collection = clrCollectionAccessor?.GetOrCreate(entity, forMaterialization: true); } var parentKey = parentIdentifier(queryContext, dbDataReader); diff --git a/src/EFCore/ChangeTracking/CollectionEntry.cs b/src/EFCore/ChangeTracking/CollectionEntry.cs index cfe71fec338..82c423c7ac5 100644 --- a/src/EFCore/ChangeTracking/CollectionEntry.cs +++ b/src/EFCore/ChangeTracking/CollectionEntry.cs @@ -55,6 +55,11 @@ public CollectionEntry(InternalEntityEntry internalEntry, INavigationBase naviga private void LocalDetectChanges() { + if (Metadata.IsShadowProperty()) + { + EnsureInitialized(); + } + var collection = CurrentValue; if (collection != null) { @@ -274,7 +279,7 @@ public override IQueryable Query() } private void EnsureInitialized() - => Metadata.GetCollectionAccessor()!.GetOrCreate(InternalEntry.Entity, forMaterialization: true); + => InternalEntry.GetOrCreateCollection(Metadata, forMaterialization: true); /// /// The of an entity this navigation targets. @@ -302,7 +307,7 @@ private void EnsureInitialized() [EntityFrameworkInternal] protected virtual InternalEntityEntry? GetInternalTargetEntry(object entity) => CurrentValue == null - || !Metadata.GetCollectionAccessor()!.Contains(InternalEntry.Entity, entity) + || !InternalEntry.CollectionContains(Metadata, entity) ? null : InternalEntry.StateManager.GetOrCreateEntry(entity, Metadata.TargetEntityType); diff --git a/src/EFCore/ChangeTracking/CollectionEntry`.cs b/src/EFCore/ChangeTracking/CollectionEntry`.cs index b848c0f3764..ac6e28b6325 100644 --- a/src/EFCore/ChangeTracking/CollectionEntry`.cs +++ b/src/EFCore/ChangeTracking/CollectionEntry`.cs @@ -73,7 +73,7 @@ public CollectionEntry(InternalEntityEntry internalEntry, INavigationBase naviga /// public new virtual IEnumerable? CurrentValue { - get => this.GetInfrastructure().GetCurrentValue>(Metadata); + get => (IEnumerable?)this.GetInfrastructure().GetCurrentValue(Metadata); set => base.CurrentValue = value; } diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs index 90576bc61ec..a73e3dd1fc8 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntry.cs @@ -921,11 +921,12 @@ public object GetOrCreateCollection(INavigationBase navigationBase, bool forMate ? GetOrCreateCollectionTyped(navigationBase) : navigationBase.GetCollectionAccessor()!.GetOrCreate(Entity, forMaterialization); - private ICollection GetOrCreateCollectionTyped(INavigationBase navigation) + private object GetOrCreateCollectionTyped(INavigationBase navigation) { - if (!(_shadowValues[navigation.GetShadowIndex()] is ICollection collection)) + var collection = _shadowValues[navigation.GetShadowIndex()]; + if (collection == null) { - collection = new HashSet(); + collection = navigation.GetCollectionAccessor()!.Create(); _shadowValues[navigation.GetShadowIndex()] = collection; } @@ -938,10 +939,13 @@ private ICollection GetOrCreateCollectionTyped(INavigationBase navigatio /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public bool CollectionContains(INavigationBase navigationBase, InternalEntityEntry value) - => navigationBase.IsShadowProperty() - ? GetOrCreateCollectionTyped(navigationBase).Contains(value.Entity) - : navigationBase.GetCollectionAccessor()!.Contains(Entity, value.Entity); + public bool CollectionContains(INavigationBase navigationBase, object value) + { + var collectionAccessor = navigationBase.GetCollectionAccessor()!; + return navigationBase.IsShadowProperty() + ? collectionAccessor.ContainsStandalone(GetOrCreateCollectionTyped(navigationBase), value) + : collectionAccessor.Contains(Entity, value); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -951,18 +955,19 @@ public bool CollectionContains(INavigationBase navigationBase, InternalEntityEnt /// public bool AddToCollection( INavigationBase navigationBase, - InternalEntityEntry value, + object value, bool forMaterialization) { + var collectionAccessor = navigationBase.GetCollectionAccessor()!; if (!navigationBase.IsShadowProperty()) { - return navigationBase.GetCollectionAccessor()!.Add(Entity, value.Entity, forMaterialization); + return collectionAccessor.Add(Entity, value, forMaterialization); } var collection = GetOrCreateCollectionTyped(navigationBase); - if (!collection.Contains(value.Entity)) + if (!collectionAccessor.ContainsStandalone(collection, value)) { - collection.Add(value.Entity); + collectionAccessor.AddStandalone(collection, value); return true; } @@ -975,10 +980,13 @@ public bool AddToCollection( /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// - public bool RemoveFromCollection(INavigationBase navigationBase, InternalEntityEntry value) - => navigationBase.IsShadowProperty() - ? GetOrCreateCollectionTyped(navigationBase).Remove(value.Entity) - : navigationBase.GetCollectionAccessor()!.Remove(Entity, value.Entity); + public bool RemoveFromCollection(INavigationBase navigationBase, object value) + { + var collectionAccessor = navigationBase.GetCollectionAccessor()!; + return navigationBase.IsShadowProperty() + ? collectionAccessor.RemoveStandalone(GetOrCreateCollectionTyped(navigationBase), value) + : collectionAccessor.Remove(Entity, value); + } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to diff --git a/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs b/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs index 5d2563197a8..dd0e965429f 100644 --- a/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs +++ b/src/EFCore/ChangeTracking/Internal/InternalEntityEntrySubscriber.cs @@ -98,11 +98,10 @@ private static INotifyCollectionChanged AsINotifyCollectionChanged( IEntityType entityType, ChangeTrackingStrategy changeTrackingStrategy) { - if (navigation.GetCollectionAccessor() - ?.GetOrCreate(entry.Entity, forMaterialization: false) is not INotifyCollectionChanged notifyingCollection) + var collection = entry.GetOrCreateCollection(navigation, forMaterialization: false); + if (collection is not INotifyCollectionChanged notifyingCollection) { - var collectionType = navigation.GetCollectionAccessor() - ?.GetOrCreate(entry.Entity, forMaterialization: false).GetType().DisplayName(fullName: false); + var collectionType = collection.GetType().DisplayName(fullName: false); throw new InvalidOperationException( CoreStrings.NonNotifyingCollection(navigation.Name, entityType.DisplayName(), collectionType, changeTrackingStrategy)); } diff --git a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs index 446696f4cd1..70903f59b14 100644 --- a/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs +++ b/src/EFCore/ChangeTracking/Internal/NavigationFixer.cs @@ -1022,7 +1022,7 @@ private void DelayedFixup( { if (navigation.IsCollection) { - if (entry.CollectionContains(navigation, referencedEntry)) + if (entry.CollectionContains(navigation, referencedEntry.Entity)) { FixupToDependent(entry, referencedEntry, navigation.ForeignKey, setModified, fromQuery); } @@ -1491,7 +1491,7 @@ private void AddToCollection(InternalEntityEntry entry, INavigationBase? navigat _inFixup = true; try { - if (entry.AddToCollection(navigation, value, fromQuery)) + if (entry.AddToCollection(navigation, value.Entity, fromQuery)) { entry.AddToCollectionSnapshot(navigation, value.Entity); } @@ -1508,7 +1508,7 @@ private void RemoveFromCollection(InternalEntityEntry entry, INavigationBase nav _inFixup = true; try { - if (entry.RemoveFromCollection(navigation, value)) + if (entry.RemoveFromCollection(navigation, value.Entity)) { entry.RemoveFromCollectionSnapshot(navigation, value.Entity); } diff --git a/src/EFCore/ChangeTracking/Internal/StateManager.cs b/src/EFCore/ChangeTracking/Internal/StateManager.cs index ac26fa73880..28aa13a5a6c 100644 --- a/src/EFCore/ChangeTracking/Internal/StateManager.cs +++ b/src/EFCore/ChangeTracking/Internal/StateManager.cs @@ -803,7 +803,7 @@ public virtual bool ResolveToExistingEntry( var navigationValue = referencedFromEntry![navigation]; if (navigationValue != null && navigation.IsCollection) { - navigation.GetCollectionAccessor()!.Remove(referencedFromEntry.Entity, newEntry.Entity); + referencedFromEntry.RemoveFromCollection(navigation, newEntry.Entity); } } diff --git a/src/EFCore/Infrastructure/ModelValidator.cs b/src/EFCore/Infrastructure/ModelValidator.cs index 7f6554003cb..08e42a2cc90 100644 --- a/src/EFCore/Infrastructure/ModelValidator.cs +++ b/src/EFCore/Infrastructure/ModelValidator.cs @@ -115,16 +115,6 @@ protected virtual void ValidateRelationships( CoreStrings.SkipNavigationNoInverse( skipNavigation.Name, skipNavigation.DeclaringEntityType.DisplayName())); } - - if (skipNavigation.IsShadowProperty()) - { - throw new InvalidOperationException( - CoreStrings.ShadowManyToManyNavigation( - skipNavigation.DeclaringEntityType.DisplayName(), - skipNavigation.Name, - skipNavigation.Inverse.DeclaringEntityType.DisplayName(), - skipNavigation.Inverse.Name)); - } } } } diff --git a/src/EFCore/Internal/ManyToManyLoader.cs b/src/EFCore/Internal/ManyToManyLoader.cs index 72b0e6032c5..f8a73dd0830 100644 --- a/src/EFCore/Internal/ManyToManyLoader.cs +++ b/src/EFCore/Internal/ManyToManyLoader.cs @@ -3,6 +3,7 @@ using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Metadata.Internal; +using ValueBuffer = Microsoft.EntityFrameworkCore.Storage.ValueBuffer; namespace Microsoft.EntityFrameworkCore.Internal; @@ -12,9 +13,10 @@ namespace Microsoft.EntityFrameworkCore.Internal; /// any release. You should only use it directly in your code with extreme caution and knowing that /// doing so can result in application failures when updating to a new Entity Framework Core release. /// -public class ManyToManyLoader : ICollectionLoader +public class ManyToManyLoader : ICollectionLoader where TEntity : class where TSourceEntity : class + where TJoinEntity : class { private readonly ISkipNavigation _skipNavigation; @@ -42,7 +44,7 @@ public virtual void Load(InternalEntityEntry entry) // Short-circuit for any null key values for perf and because of #6129 if (keyValues != null) { - Query(entry.Context, keyValues).Load(); + Query(entry.Context, keyValues).Result.Load(); } entry.SetIsLoaded(_skipNavigation); @@ -61,7 +63,8 @@ public virtual async Task LoadAsync(InternalEntityEntry entry, CancellationToken // Short-circuit for any null key values for perf and because of #6129 if (keyValues != null) { - await Query(entry.Context, keyValues).LoadAsync(cancellationToken).ConfigureAwait(false); + await (await Query(entry.Context, keyValues, true, cancellationToken).ConfigureAwait(false)) + .LoadAsync(cancellationToken).ConfigureAwait(false); } entry.SetIsLoaded(_skipNavigation); @@ -89,9 +92,9 @@ public virtual IQueryable Query(InternalEntityEntry entry) return queryRoot.Where(e => false); } - return Query(context, keyValues); + return Query(context, keyValues).Result; } - + private object[]? PrepareForLoad(InternalEntityEntry entry) { if (entry.EntityState == EntityState.Detached) @@ -125,12 +128,9 @@ public virtual IQueryable Query(InternalEntityEntry entry) IQueryable ICollectionLoader.Query(InternalEntityEntry entry) => Query(entry); - private IQueryable Query( - DbContext context, - object[] keyValues) + private async Task> Query( + DbContext context, object[] keyValues, bool async = false, CancellationToken cancellationToken = default) { - var loadProperties = _skipNavigation.ForeignKey.PrincipalKey.Properties; - // Example of query being built: // // IQueryable loaded @@ -140,16 +140,38 @@ private IQueryable Query( // .SelectMany(e => e.TwoSkip) // .NotQuiteInclude(e => e.OneSkip.Where(e => e.Id == left.Id)); + var loadProperties = _skipNavigation.ForeignKey.PrincipalKey.Properties; var queryRoot = _skipNavigation.DeclaringEntityType.HasSharedClrType ? context.Set(_skipNavigation.DeclaringEntityType.Name) : context.Set(); - return queryRoot + var queryable = queryRoot .AsTracking() - .Where(BuildWhereLambda(loadProperties, new ValueBuffer(keyValues))) - .SelectMany(BuildSelectManyLambda(_skipNavigation)) - .NotQuiteInclude(BuildIncludeLambda(_skipNavigation.Inverse, loadProperties, new ValueBuffer(keyValues))) - .AsQueryable(); + .Where(BuildWhereLambda(loadProperties, new ValueBuffer(keyValues))) + .SelectMany(BuildSelectManyLambda(_skipNavigation)); + + if (_skipNavigation.Inverse.IsShadowProperty()) + { + // TODOU: This is a hack, until #27493 is implemented. + var joinEntitiesQuery = (_skipNavigation.JoinEntityType.HasSharedClrType + ? context.Set(_skipNavigation.JoinEntityType.Name) + : context.Set()) + .AsTracking() + .Where(BuildWhereLambda(_skipNavigation.ForeignKey.Properties, new ValueBuffer(keyValues))); + + if (async) + { + await joinEntitiesQuery.LoadAsync(cancellationToken).ConfigureAwait(false); + } + else + { + joinEntitiesQuery.Load(); + } + + return queryable; + } + + return queryable.NotQuiteInclude(BuildIncludeLambda(_skipNavigation.Inverse, loadProperties, new ValueBuffer(keyValues))); } private static Expression>> BuildIncludeLambda( @@ -171,13 +193,13 @@ private static Expression>> BuildInclud whereParameter)), entityParameter); } - private static Expression> BuildWhereLambda( + private static Expression> BuildWhereLambda( IReadOnlyList keyProperties, ValueBuffer keyValues) { - var entityParameter = Expression.Parameter(typeof(TSourceEntity), "e"); + var entityParameter = Expression.Parameter(typeof(T), "e"); - return Expression.Lambda>( + return Expression.Lambda>( ExpressionExtensions.BuildPredicate(keyProperties, keyValues, entityParameter), entityParameter); } diff --git a/src/EFCore/Internal/ManyToManyLoaderFactory.cs b/src/EFCore/Internal/ManyToManyLoaderFactory.cs index 05953b2b651..f4b1b75cbf2 100644 --- a/src/EFCore/Internal/ManyToManyLoaderFactory.cs +++ b/src/EFCore/Internal/ManyToManyLoaderFactory.cs @@ -25,12 +25,14 @@ private static readonly MethodInfo GenericCreate public virtual ICollectionLoader Create(ISkipNavigation skipNavigation) => (ICollectionLoader)GenericCreate.MakeGenericMethod( skipNavigation.TargetEntityType.ClrType, - skipNavigation.DeclaringEntityType.ClrType) + skipNavigation.DeclaringEntityType.ClrType, + skipNavigation.JoinEntityType.ClrType) .Invoke(null, new object[] { skipNavigation })!; [UsedImplicitly] - private static ICollectionLoader CreateManyToMany(ISkipNavigation skipNavigation) + private static ICollectionLoader CreateManyToMany(ISkipNavigation skipNavigation) where TEntity : class where TTargetEntity : class - => new ManyToManyLoader(skipNavigation); + where TJoinEntity : class + => new ManyToManyLoader(skipNavigation); } diff --git a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs index 2fbc91ed551..e14ec81b439 100644 --- a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs +++ b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder.cs @@ -216,16 +216,7 @@ private InternalForeignKeyBuilder WithOneBuilder(MemberIdentity reference) /// An object to further configure the relationship. public virtual CollectionCollectionBuilder WithMany(string navigationName) { - if (Builder != null - && Builder.Metadata.PrincipalToDependent == null) - { - throw new InvalidOperationException( - CoreStrings.MissingInverseManyToManyNavigation( - Builder.Metadata.PrincipalEntityType.DisplayName(), - Builder.Metadata.DeclaringEntityType.DisplayName())); - } - - var leftName = Builder?.Metadata.PrincipalToDependent!.Name; + var leftName = Builder?.Metadata.PrincipalToDependent?.Name; var collectionCollectionBuilder = new CollectionCollectionBuilder( RelatedEntityType, @@ -253,9 +244,9 @@ protected virtual IMutableSkipNavigation WithLeftManyNavigation(MemberInfo inver /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual IMutableSkipNavigation WithLeftManyNavigation(string inverseName) + protected virtual IMutableSkipNavigation WithLeftManyNavigation(string? inverseName) { - Check.NotEmpty(inverseName, nameof(inverseName)); + Check.NullButNotEmpty(inverseName, nameof(inverseName)); if (SkipNavigation != null) { @@ -288,7 +279,7 @@ protected virtual IMutableSkipNavigation WithLeftManyNavigation(string inverseNa /// doing so can result in application failures when updating to a new Entity Framework Core release. /// [EntityFrameworkInternal] - protected virtual IMutableSkipNavigation WithRightManyNavigation(string navigationName, string inverseName) + protected virtual IMutableSkipNavigation WithRightManyNavigation(string? navigationName, string? inverseName) => WithRightManyNavigation(MemberIdentity.Create(navigationName), inverseName); /// @@ -306,29 +297,31 @@ protected virtual IMutableSkipNavigation WithRightManyNavigation( private IMutableSkipNavigation WithRightManyNavigation(MemberIdentity navigationMember, string? inverseName) { Check.DebugAssert(Builder == null, "Expected no associated foreign key at this point"); - Check.DebugAssert(navigationMember.Name is not null, $"{nameof(navigationMember.Name)} is null"); var navigationName = navigationMember.Name; - var conflictingNavigation = RelatedEntityType.FindNavigation(navigationName) as IConventionNavigation; - var foreignKey = (ForeignKey?)conflictingNavigation?.ForeignKey; - if (conflictingNavigation?.GetConfigurationSource() == ConfigurationSource.Explicit) - { - InternalForeignKeyBuilder.ThrowForConflictingNavigation( - foreignKey!, DeclaringEntityType, RelatedEntityType, inverseName, navigationName); - } - using (((EntityType)RelatedEntityType).Model.DelayConventions()) { - if (conflictingNavigation != null) + if (navigationName != null) { - foreignKey!.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, ConfigurationSource.Explicit); - } - else - { - var skipNavigation = RelatedEntityType.FindSkipNavigation(navigationMember.Name); - if (skipNavigation != null) + var conflictingNavigation = RelatedEntityType.FindNavigation(navigationName) as IConventionNavigation; + var foreignKey = (ForeignKey?)conflictingNavigation?.ForeignKey; + if (conflictingNavigation?.GetConfigurationSource() == ConfigurationSource.Explicit) + { + InternalForeignKeyBuilder.ThrowForConflictingNavigation( + foreignKey!, DeclaringEntityType, RelatedEntityType, inverseName, navigationName); + } + + if (conflictingNavigation != null) + { + foreignKey!.DeclaringEntityType.Builder.HasNoRelationship(foreignKey, ConfigurationSource.Explicit); + } + else { - return skipNavigation; + var skipNavigation = RelatedEntityType.FindSkipNavigation(navigationName); + if (skipNavigation != null) + { + return skipNavigation; + } } } diff --git a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs index e504fcf1fa2..7fcc7837ef7 100644 --- a/src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs +++ b/src/EFCore/Metadata/Builders/CollectionNavigationBuilder`.cs @@ -80,27 +80,19 @@ public virtual ReferenceCollectionBuilder WithOne( /// Configures this as a many-to-many relationship. /// /// - /// The name of the collection navigation property on the other end of this relationship. + /// The name of the collection navigation property on the other end of this relationship. Can be to + /// create a unidirectional relationship. /// /// An object to further configure the relationship. - public new virtual CollectionCollectionBuilder WithMany(string navigationName) + public new virtual CollectionCollectionBuilder WithMany(string? navigationName = null) { - if (Builder != null - && Builder.Metadata.PrincipalToDependent == null) - { - throw new InvalidOperationException( - CoreStrings.MissingInverseManyToManyNavigation( - Builder.Metadata.PrincipalEntityType.DisplayName(), - Builder.Metadata.DeclaringEntityType.DisplayName())); - } - - var leftName = Builder?.Metadata.PrincipalToDependent!.Name; + var leftName = Builder?.Metadata.PrincipalToDependent?.Name; var collectionCollectionBuilder = new CollectionCollectionBuilder( RelatedEntityType, DeclaringEntityType, WithLeftManyNavigation(navigationName), - WithRightManyNavigation(navigationName, leftName!)); + WithRightManyNavigation(navigationName, leftName)); return collectionCollectionBuilder; } @@ -119,16 +111,7 @@ public virtual ReferenceCollectionBuilder WithOne( public virtual CollectionCollectionBuilder WithMany( Expression?>> navigationExpression) { - if (Builder != null - && Builder.Metadata.PrincipalToDependent == null) - { - throw new InvalidOperationException( - CoreStrings.MissingInverseManyToManyNavigation( - Builder.Metadata.PrincipalEntityType.DisplayName(), - Builder.Metadata.DeclaringEntityType.DisplayName())); - } - - var leftName = Builder?.Metadata.PrincipalToDependent!.Name; + var leftName = Builder?.Metadata.PrincipalToDependent?.Name; var collectionCollectionBuilder = new CollectionCollectionBuilder( RelatedEntityType, diff --git a/src/EFCore/Metadata/IClrCollectionAccessor.cs b/src/EFCore/Metadata/IClrCollectionAccessor.cs index be2b29e9c9a..c0d634283e0 100644 --- a/src/EFCore/Metadata/IClrCollectionAccessor.cs +++ b/src/EFCore/Metadata/IClrCollectionAccessor.cs @@ -44,6 +44,30 @@ public interface IClrCollectionAccessor /// if the value was contained in the collection; otherwise. bool Remove(object entity, object value); + /// + /// Adds a value to the collection, unless it is already contained in the collection. + /// + /// The collection. + /// The value to add. + /// if a value was added; if it was already in the collection. + bool AddStandalone(object collection, object value); + + /// + /// Checks whether the value is contained in the collection. + /// + /// The collection. + /// The value to check. + /// if the value is contained in the collection; otherwise. + bool ContainsStandalone(object collection, object value); + + /// + /// Removes a value from the collection. + /// + /// The collection. + /// The value to check. + /// if the value was contained in the collection; otherwise. + bool RemoveStandalone(object? collection, object value); + /// /// Creates a new collection instance of the appropriate type for the navigation property. /// diff --git a/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs b/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs index f07a30d2e62..52b807c6c92 100644 --- a/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs +++ b/src/EFCore/Metadata/Internal/ClrCollectionAccessor.cs @@ -17,7 +17,7 @@ public class ClrICollectionAccessor : IClrCollec where TElement : class { private readonly string _propertyName; - private readonly Func _getCollection; + private readonly Func? _getCollection; private readonly Action? _setCollection; private readonly Action? _setCollectionForMaterialization; private readonly Func, TCollection>? _createAndSetCollection; @@ -40,7 +40,7 @@ public virtual Type CollectionType /// public ClrICollectionAccessor( string propertyName, - Func getCollection, + Func? getCollection, Action? setCollection, Action? setCollectionForMaterialization, Func, TCollection>? createAndSetCollection, @@ -61,13 +61,19 @@ public ClrICollectionAccessor( /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool Add(object entity, object value, bool forMaterialization) - { - var collection = GetOrCreateCollection(entity, forMaterialization); - var element = (TElement)value; + => AddStandalone(GetOrCreateCollection(entity, forMaterialization), value); - if (!Contains(collection, value)) + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool AddStandalone(object collection, object value) + { + if (!ContainsStandalone(collection, value)) { - collection.Add(element); + ((ICollection)collection).Add((TElement)value); return true; } @@ -131,7 +137,7 @@ private ICollection GetOrCreateCollection(object instance, bool forMat private ICollection? GetCollection(object instance) { - var enumerable = _getCollection((TEntity)instance); + var enumerable = _getCollection!((TEntity)instance); var collection = enumerable as ICollection; if (enumerable != null @@ -157,6 +163,15 @@ private ICollection GetOrCreateCollection(object instance, bool forMat public virtual bool Contains(object entity, object value) => Contains(GetCollection((TEntity)entity), value); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool ContainsStandalone(object collection, object value) + => Contains((ICollection)collection, value); + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -164,9 +179,16 @@ public virtual bool Contains(object entity, object value) /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual bool Remove(object entity, object value) - { - var collection = GetCollection((TEntity)entity); + => RemoveStandalone(GetCollection((TEntity)entity), value); + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual bool RemoveStandalone(object? collection, object value) + { switch (collection) { case List list: @@ -196,7 +218,7 @@ public virtual bool Remove(object entity, object value) && ReferenceEquals(found, value) && sortedSet.Remove(found); default: - return collection?.Remove((TElement)value) ?? false; + return ((ICollection?)collection)?.Remove((TElement)value) ?? false; } } diff --git a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs index 889cf109bc3..7c2eb1913ea 100644 --- a/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs +++ b/src/EFCore/Metadata/Internal/ClrCollectionAccessorFactory.cs @@ -42,7 +42,7 @@ private static readonly MethodInfo CreateObservableHashSetMethod /// doing so can result in application failures when updating to a new Entity Framework Core release. /// public virtual IClrCollectionAccessor? Create(INavigationBase navigation) - => !navigation.IsCollection || navigation.IsShadowProperty() ? null : Create(navigation, navigation.TargetEntityType); + => !navigation.IsCollection ? null : Create(navigation, navigation.TargetEntityType); private static IClrCollectionAccessor? Create(IPropertyBase navigation, IEntityType? targetType) { @@ -58,7 +58,10 @@ private static readonly MethodInfo CreateObservableHashSetMethod } var memberInfo = GetMostDerivedMemberInfo(); - var propertyType = navigation.IsIndexerProperty() ? navigation.ClrType : memberInfo.GetMemberType(); + var propertyType = navigation.IsIndexerProperty() || navigation.IsShadowProperty() + ? navigation.ClrType + : memberInfo!.GetMemberType(); + var elementType = propertyType.TryGetElementType(typeof(IEnumerable<>)); if (elementType == null) @@ -81,7 +84,7 @@ private static readonly MethodInfo CreateObservableHashSetMethod } var boundMethod = GenericCreate.MakeGenericMethod( - memberInfo.DeclaringType!, propertyType, elementType); + memberInfo?.DeclaringType ?? navigation.DeclaringType.ClrType, propertyType, elementType); try { @@ -93,7 +96,7 @@ private static readonly MethodInfo CreateObservableHashSetMethod throw invocationException.InnerException!; } - MemberInfo GetMostDerivedMemberInfo() + MemberInfo? GetMostDerivedMemberInfo() { var propertyInfo = navigation.PropertyInfo; var fieldInfo = navigation.FieldInfo; @@ -104,7 +107,7 @@ MemberInfo GetMostDerivedMemberInfo() ? fieldInfo : fieldInfo.FieldType.IsAssignableFrom(propertyInfo.PropertyType) ? propertyInfo - : fieldInfo)!; + : fieldInfo); } } @@ -117,33 +120,37 @@ private static IClrCollectionAccessor CreateGeneric>( - memberAccessForRead, - entityParameter).Compile(); - + Func? getterDelegate = null; Action? setterDelegate = null; Action? setterDelegateForMaterialization = null; Func, TCollection>? createAndSetDelegate = null; Func? createDelegate = null; - if (memberInfoForWrite != null) + if (!navigation.IsShadowProperty()) { - setterDelegate = CreateSetterDelegate(entityParameter, memberInfoForWrite, valueParameter); - } + var memberInfoForRead = navigation.GetMemberInfo(forMaterialization: false, forSet: false); + navigation.TryGetMemberInfo(forMaterialization: false, forSet: true, out var memberInfoForWrite, out _); + navigation.TryGetMemberInfo(forMaterialization: true, forSet: true, out var memberInfoForMaterialization, out _); - if (memberInfoForMaterialization != null) - { - setterDelegateForMaterialization = CreateSetterDelegate(entityParameter, memberInfoForMaterialization, valueParameter); + var memberAccessForRead = (Expression)Expression.MakeMemberAccess(entityParameter, memberInfoForRead); + if (memberAccessForRead.Type != typeof(TCollection)) + { + memberAccessForRead = Expression.Convert(memberAccessForRead, typeof(TCollection)); + } + + getterDelegate = Expression.Lambda>( + memberAccessForRead, + entityParameter).Compile(); + + if (memberInfoForWrite != null) + { + setterDelegate = CreateSetterDelegate(entityParameter, memberInfoForWrite, valueParameter); + } + + if (memberInfoForMaterialization != null) + { + setterDelegateForMaterialization = CreateSetterDelegate(entityParameter, memberInfoForMaterialization, valueParameter); + } } var concreteType = new CollectionTypeFactory().TryFindTypeToInstantiate( diff --git a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs index ccc8df0413b..8859fa2e8eb 100644 --- a/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs +++ b/src/EFCore/Metadata/Internal/InternalEntityTypeBuilder.cs @@ -4118,139 +4118,160 @@ private static bool Contains(IReadOnlyForeignKey? inheritedFk, IReadOnlyForeignK { List? navigationsToDetach = null; List<(InternalSkipNavigationBuilder Navigation, InternalSkipNavigationBuilder Inverse)>? detachedNavigations = null; - var navigationName = navigationProperty.Name!; - var memberInfo = navigationProperty.MemberInfo; - var existingNavigation = Metadata.FindSkipNavigation(navigationName); - if (existingNavigation != null) + InternalSkipNavigationBuilder builder; + + var navigationName = navigationProperty.Name; + if (navigationName != null) { - Check.DebugAssert( - memberInfo == null - || existingNavigation.IsIndexerProperty() - || memberInfo.IsSameAs(existingNavigation.GetIdentifyingMemberInfo()), - "Expected memberInfo to be the same on the existing navigation"); + var memberInfo = navigationProperty.MemberInfo; + var existingNavigation = Metadata.FindSkipNavigation(navigationName); + if (existingNavigation != null) + { + Check.DebugAssert( + memberInfo == null + || existingNavigation.IsIndexerProperty() + || memberInfo.IsSameAs(existingNavigation.GetIdentifyingMemberInfo()), + "Expected memberInfo to be the same on the existing navigation"); - Check.DebugAssert( - collection == null || collection == existingNavigation.IsCollection, - "Expected existing navigation to have the same cardinality"); + Check.DebugAssert( + collection == null || collection == existingNavigation.IsCollection, + "Expected existing navigation to have the same cardinality"); - Check.DebugAssert( - onDependent == null || onDependent == existingNavigation.IsOnDependent, - "Expected existing navigation to be on the same side"); + Check.DebugAssert( + onDependent == null || onDependent == existingNavigation.IsOnDependent, + "Expected existing navigation to be on the same side"); - if (existingNavigation.DeclaringEntityType != Metadata) - { - if (!IsIgnored(navigationName, configurationSource)) + if (existingNavigation.DeclaringEntityType != Metadata) { - Metadata.RemoveIgnored(navigationName); + if (!IsIgnored(navigationName, configurationSource)) + { + Metadata.RemoveIgnored(navigationName); + } } - } - if (configurationSource.HasValue) - { - existingNavigation.UpdateConfigurationSource(configurationSource.Value); - } - - return existingNavigation.Builder; - } + if (configurationSource.HasValue) + { + existingNavigation.UpdateConfigurationSource(configurationSource.Value); + } - if (configurationSource != ConfigurationSource.Explicit - && (!configurationSource.HasValue - || !CanAddSkipNavigation(navigationName, memberInfo?.GetMemberType(), configurationSource.Value))) - { - return null; - } + return existingNavigation.Builder; + } - foreach (var derivedType in Metadata.GetDerivedTypes()) - { - var conflictingNavigation = derivedType.FindDeclaredSkipNavigation(navigationName); - if (conflictingNavigation != null) + if (configurationSource != ConfigurationSource.Explicit + && (!configurationSource.HasValue + || !CanAddSkipNavigation(navigationName, memberInfo?.GetMemberType(), configurationSource.Value))) { - navigationsToDetach ??= new List(); - - navigationsToDetach.Add(conflictingNavigation); + return null; } - } - - if (collection == null - && memberInfo != null) - { - var navigationType = memberInfo.GetMemberType(); - var navigationTargetClrType = navigationType.TryGetSequenceType(); - collection = navigationTargetClrType != null - && navigationType != targetEntityType.ClrType - && navigationTargetClrType.IsAssignableFrom(targetEntityType.ClrType); - } - InternalSkipNavigationBuilder builder; - using (ModelBuilder.Metadata.DelayConventions()) - { - Metadata.RemoveIgnored(navigationName); - - foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(navigationName)) + foreach (var derivedType in Metadata.GetDerivedTypes()) { - if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + var conflictingNavigation = derivedType.FindDeclaredSkipNavigation(navigationName); + if (conflictingNavigation != null) { - conflictingProperty.DeclaringEntityType.RemoveProperty(conflictingProperty); + navigationsToDetach ??= new List(); + + navigationsToDetach.Add(conflictingNavigation); } } - foreach (var conflictingServiceProperty in Metadata.FindServicePropertiesInHierarchy(navigationName)) + if (collection == null + && memberInfo != null) { - if (conflictingServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) - { - conflictingServiceProperty.DeclaringEntityType.RemoveServiceProperty(conflictingServiceProperty); - } + var navigationType = memberInfo.GetMemberType(); + var navigationTargetClrType = navigationType.TryGetSequenceType(); + collection = navigationTargetClrType != null + && navigationType != targetEntityType.ClrType + && navigationTargetClrType.IsAssignableFrom(targetEntityType.ClrType); } - foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(navigationName)) + using (ModelBuilder.Metadata.DelayConventions()) { - if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) + Metadata.RemoveIgnored(navigationName); + + foreach (var conflictingProperty in Metadata.FindPropertiesInHierarchy(navigationName)) { - continue; + if (conflictingProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + conflictingProperty.DeclaringEntityType.RemoveProperty(conflictingProperty); + } } - var conflictingForeignKey = conflictingNavigation.ForeignKey; - if (conflictingForeignKey.GetConfigurationSource() == ConfigurationSource.Convention) + foreach (var conflictingServiceProperty in Metadata.FindServicePropertiesInHierarchy(navigationName)) { - conflictingForeignKey.DeclaringEntityType.Builder.HasNoRelationship( - conflictingForeignKey, ConfigurationSource.Convention); + if (conflictingServiceProperty.GetConfigurationSource() != ConfigurationSource.Explicit) + { + conflictingServiceProperty.DeclaringEntityType.RemoveServiceProperty(conflictingServiceProperty); + } } - else if (conflictingForeignKey.Builder.HasNavigation( - (string?)null, - conflictingNavigation.IsOnDependent, - configurationSource.Value) - == null) + + foreach (var conflictingNavigation in Metadata.FindNavigationsInHierarchy(navigationName)) { - return null; + if (conflictingNavigation.GetConfigurationSource() == ConfigurationSource.Explicit) + { + continue; + } + + var conflictingForeignKey = conflictingNavigation.ForeignKey; + if (conflictingForeignKey.GetConfigurationSource() == ConfigurationSource.Convention) + { + conflictingForeignKey.DeclaringEntityType.Builder.HasNoRelationship( + conflictingForeignKey, ConfigurationSource.Convention); + } + else if (conflictingForeignKey.Builder.HasNavigation( + (string?)null, + conflictingNavigation.IsOnDependent, + configurationSource.Value) + == null) + { + return null; + } } - } - if (navigationsToDetach != null) - { - detachedNavigations = new List<(InternalSkipNavigationBuilder, InternalSkipNavigationBuilder)>(); - foreach (var navigationToDetach in navigationsToDetach) + if (navigationsToDetach != null) { - var inverse = navigationToDetach.Inverse; - detachedNavigations.Add((DetachSkipNavigation(navigationToDetach)!, DetachSkipNavigation(inverse)!)); + detachedNavigations = new List<(InternalSkipNavigationBuilder, InternalSkipNavigationBuilder)>(); + foreach (var navigationToDetach in navigationsToDetach) + { + var inverse = navigationToDetach.Inverse; + detachedNavigations.Add((DetachSkipNavigation(navigationToDetach)!, DetachSkipNavigation(inverse)!)); + } } - } - builder = Metadata.AddSkipNavigation( - navigationName, memberInfo, - targetEntityType, collection ?? true, onDependent ?? false, configurationSource.Value)!.Builder; + builder = Metadata.AddSkipNavigation( + navigationName, memberInfo, + targetEntityType, collection ?? true, onDependent ?? false, configurationSource.Value)!.Builder; - if (detachedNavigations != null) + if (detachedNavigations != null) + { + foreach (var (navigation, inverse) in detachedNavigations) + { + navigation.Attach(this, inverseBuilder: inverse); + } + } + } + } + else + { + if (navigationName == null) { - foreach (var (navigation, inverse) in detachedNavigations) + var generatedNavigationName = targetEntityType.ShortName(); + navigationName = generatedNavigationName; + var uniquifier = 0; + while(Metadata.FindMembersInHierarchy(navigationName).Any()) { - navigation.Attach(this, inverseBuilder: inverse); + navigationName = generatedNavigationName + (++uniquifier); } } + + builder = Metadata.AddSkipNavigation( + navigationName, null, + targetEntityType, collection ?? true, onDependent ?? false, ConfigurationSource.Explicit)!.Builder; } return builder.Metadata.IsInModel ? builder - : Metadata.FindSkipNavigation(navigationName)?.Builder; + : Metadata.FindSkipNavigation(navigationName!)?.Builder; } /// diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs index 12d4a8e293e..d414192d028 100644 --- a/src/EFCore/Properties/CoreStrings.Designer.cs +++ b/src/EFCore/Properties/CoreStrings.Designer.cs @@ -1515,14 +1515,6 @@ public static string MissingBackingField(object? field, object? property, object GetString("MissingBackingField", nameof(field), "1_property", "2_entityType"), field, property, entityType); - /// - /// Unable to set up a many-to-many relationship between the entity types '{principalEntityType}' and '{declaringEntityType}' because one of the navigations was not specified. Provide a navigation in the 'HasMany' call in 'OnModelCreating'. Consider adding a private property for this. - /// - public static string MissingInverseManyToManyNavigation(object? principalEntityType, object? declaringEntityType) - => string.Format( - GetString("MissingInverseManyToManyNavigation", nameof(principalEntityType), nameof(declaringEntityType)), - principalEntityType, declaringEntityType); - /// /// Runtime metadata changes are not allowed when the model hasn't been marked as read-only. /// diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx index 1f6866bc7c4..8e1da961ebd 100644 --- a/src/EFCore/Properties/CoreStrings.resx +++ b/src/EFCore/Properties/CoreStrings.resx @@ -980,9 +980,6 @@ The specified field '{field}' could not be found for property '{2_entityType}.{1_property}'. - - Unable to set up a many-to-many relationship between the entity types '{principalEntityType}' and '{declaringEntityType}' because one of the navigations was not specified. Provide a navigation in the 'HasMany' call in 'OnModelCreating'. Consider adding a private property for this. - Runtime metadata changes are not allowed when the model hasn't been marked as read-only. diff --git a/test/EFCore.Relational.Specification.Tests/Query/TPCManyToManyQueryRelationalFixture.cs b/test/EFCore.Relational.Specification.Tests/Query/TPCManyToManyQueryRelationalFixture.cs index bcf5e8851a9..9404d6c2436 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/TPCManyToManyQueryRelationalFixture.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/TPCManyToManyQueryRelationalFixture.cs @@ -19,9 +19,13 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con base.OnModelCreating(modelBuilder, context); modelBuilder.Entity().UseTpcMappingStrategy(); - modelBuilder.Entity().ToTable("Roots"); modelBuilder.Entity().ToTable("Branches"); modelBuilder.Entity().ToTable("Leaves"); + + modelBuilder.Entity().UseTpcMappingStrategy(); + modelBuilder.Entity().ToTable("UnidirectionalRoots"); + modelBuilder.Entity().ToTable("UnidirectionalBranches"); + modelBuilder.Entity().ToTable("UnidirectionalLeaves"); } } diff --git a/test/EFCore.Relational.Specification.Tests/Query/TPTManyToManyQueryRelationalFixture.cs b/test/EFCore.Relational.Specification.Tests/Query/TPTManyToManyQueryRelationalFixture.cs index 036815b0064..115e6224d0b 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/TPTManyToManyQueryRelationalFixture.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/TPTManyToManyQueryRelationalFixture.cs @@ -16,5 +16,9 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().ToTable("Roots"); modelBuilder.Entity().ToTable("Branches"); modelBuilder.Entity().ToTable("Leaves"); + + modelBuilder.Entity().ToTable("UnidirectionalRoots"); + modelBuilder.Entity().ToTable("UnidirectionalBranches"); + modelBuilder.Entity().ToTable("UnidirectionalLeaves"); } } diff --git a/test/EFCore.Specification.Tests/ManyToManyLoadTestBase.cs b/test/EFCore.Specification.Tests/ManyToManyLoadTestBase.cs index 879d0851121..eedbc06e86a 100644 --- a/test/EFCore.Specification.Tests/ManyToManyLoadTestBase.cs +++ b/test/EFCore.Specification.Tests/ManyToManyLoadTestBase.cs @@ -6,7 +6,7 @@ namespace Microsoft.EntityFrameworkCore; -public abstract class ManyToManyLoadTestBase : IClassFixture +public abstract partial class ManyToManyLoadTestBase : IClassFixture where TFixture : ManyToManyLoadTestBase.ManyToManyLoadFixtureBase { protected ManyToManyLoadTestBase(TFixture fixture) @@ -1096,6 +1096,7 @@ protected virtual bool ExpectLazyLoading public abstract class ManyToManyLoadFixtureBase : ManyToManyQueryFixtureBase { - protected override string StoreName { get; } = "ManyToManyLoadTest"; + protected override string StoreName + => "ManyToManyLoadTest"; } } diff --git a/test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs b/test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs index 8b8bd5d12b0..1f06cc6b623 100644 --- a/test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs +++ b/test/EFCore.Specification.Tests/ManyToManyTrackingTestBase.cs @@ -7,7 +7,7 @@ namespace Microsoft.EntityFrameworkCore; -public abstract class ManyToManyTrackingTestBase : IClassFixture +public abstract partial class ManyToManyTrackingTestBase : IClassFixture where TFixture : ManyToManyTrackingTestBase.ManyToManyTrackingFixtureBase { [ConditionalTheory] diff --git a/test/EFCore.Specification.Tests/Query/ManyToManyNoTrackingQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ManyToManyNoTrackingQueryTestBase.cs index 5fbecac5ba7..3053c9b9a12 100644 --- a/test/EFCore.Specification.Tests/Query/ManyToManyNoTrackingQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ManyToManyNoTrackingQueryTestBase.cs @@ -60,4 +60,7 @@ public virtual async Task Include_skip_navigation_then_include_inverse_throws_in public override Task Include_skip_navigation_then_include_inverse_works_for_tracking_query(bool async) => Task.CompletedTask; + + public override Task Include_skip_navigation_then_include_inverse_works_for_tracking_query_unidirectional(bool async) + => Task.CompletedTask; } diff --git a/test/EFCore.Specification.Tests/Query/ManyToManyQueryFixtureBase.cs b/test/EFCore.Specification.Tests/Query/ManyToManyQueryFixtureBase.cs index a7de08f4a15..2fc3f31b989 100644 --- a/test/EFCore.Specification.Tests/Query/ManyToManyQueryFixtureBase.cs +++ b/test/EFCore.Specification.Tests/Query/ManyToManyQueryFixtureBase.cs @@ -40,6 +40,18 @@ public IReadOnlyDictionary GetEntitySorters() { typeof(EntityRoot), e => ((EntityRoot)e)?.Id }, { typeof(EntityBranch), e => ((EntityBranch)e)?.Id }, { typeof(EntityLeaf), e => ((EntityLeaf)e)?.Id }, + { typeof(UnidirectionalEntityOne), e => ((UnidirectionalEntityOne)e)?.Id }, + { typeof(UnidirectionalEntityTwo), e => ((UnidirectionalEntityTwo)e)?.Id }, + { typeof(UnidirectionalEntityThree), e => ((UnidirectionalEntityThree)e)?.Id }, + { + typeof(UnidirectionalEntityCompositeKey), + e => (((UnidirectionalEntityCompositeKey)e)?.Key1, + ((UnidirectionalEntityCompositeKey)e)?.Key2, + ((UnidirectionalEntityCompositeKey)e)?.Key3) + }, + { typeof(UnidirectionalEntityRoot), e => ((UnidirectionalEntityRoot)e)?.Id }, + { typeof(UnidirectionalEntityBranch), e => ((UnidirectionalEntityBranch)e)?.Id }, + { typeof(UnidirectionalEntityLeaf), e => ((UnidirectionalEntityLeaf)e)?.Id }, }.ToDictionary(e => e.Key, e => (object)e.Value); public IReadOnlyDictionary GetEntityAsserters() @@ -148,6 +160,116 @@ public IReadOnlyDictionary GetEntityAsserters() var ee = (EntityLeaf)e; var aa = (EntityLeaf)a; + Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.Name, aa.Name); + Assert.Equal(ee.Number, aa.Number); + Assert.Equal(ee.IsGreen, aa.IsGreen); + } + } + }, + { + typeof(UnidirectionalEntityOne), (e, a) => + { + Assert.Equal(e == null, a == null); + + if (a != null) + { + var ee = (UnidirectionalEntityOne)e; + var aa = (UnidirectionalEntityOne)a; + + Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.Name, aa.Name); + } + } + }, + { + typeof(UnidirectionalEntityTwo), (e, a) => + { + Assert.Equal(e == null, a == null); + + if (a != null) + { + var ee = (UnidirectionalEntityTwo)e; + var aa = (UnidirectionalEntityTwo)a; + + Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.Name, aa.Name); + } + } + }, + { + typeof(UnidirectionalEntityThree), (e, a) => + { + Assert.Equal(e == null, a == null); + + if (a != null) + { + var ee = (UnidirectionalEntityThree)e; + var aa = (UnidirectionalEntityThree)a; + + Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.Name, aa.Name); + } + } + }, + { + typeof(UnidirectionalEntityCompositeKey), (e, a) => + { + Assert.Equal(e == null, a == null); + + if (a != null) + { + var ee = (UnidirectionalEntityCompositeKey)e; + var aa = (UnidirectionalEntityCompositeKey)a; + + Assert.Equal(ee.Key1, aa.Key1); + Assert.Equal(ee.Key2, aa.Key2); + Assert.Equal(ee.Key3, aa.Key3); + Assert.Equal(ee.Name, aa.Name); + } + } + }, + { + typeof(UnidirectionalEntityRoot), (e, a) => + { + Assert.Equal(e == null, a == null); + + if (a != null) + { + var ee = (UnidirectionalEntityRoot)e; + var aa = (UnidirectionalEntityRoot)a; + + Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.Name, aa.Name); + } + } + }, + { + typeof(UnidirectionalEntityBranch), (e, a) => + { + Assert.Equal(e == null, a == null); + + if (a != null) + { + var ee = (UnidirectionalEntityBranch)e; + var aa = (UnidirectionalEntityBranch)a; + + Assert.Equal(ee.Id, aa.Id); + Assert.Equal(ee.Name, aa.Name); + Assert.Equal(ee.Number, aa.Number); + } + } + }, + { + typeof(UnidirectionalEntityLeaf), (e, a) => + { + Assert.Equal(e == null, a == null); + + if (a != null) + { + var ee = (UnidirectionalEntityLeaf)e; + var aa = (UnidirectionalEntityLeaf)a; + Assert.Equal(ee.Id, aa.Id); Assert.Equal(ee.Name, aa.Name); Assert.Equal(ee.Number, aa.Number); @@ -173,6 +295,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().HasBaseType(); modelBuilder.Entity().HasBaseType(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().HasKey( + e => new + { + e.Key1, + e.Key2, + e.Key3 + }); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().HasBaseType(); + modelBuilder.Entity().HasBaseType(); + modelBuilder.Entity() .HasMany(e => e.Collection) .WithOne(e => e.CollectionInverse) @@ -291,6 +427,124 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con e.CompositeId3 })); + modelBuilder.Entity() + .HasMany(e => e.Collection) + .WithOne(e => e.CollectionInverse) + .HasForeignKey(e => e.CollectionInverseId); + + modelBuilder.Entity() + .HasOne(e => e.Reference) + .WithOne(e => e.ReferenceInverse) + .HasForeignKey(e => e.ReferenceInverseId); + + modelBuilder.Entity() + .HasMany(e => e.TwoSkipShared) + .WithMany(); + + // Nav:2 Payload:No Join:Concrete Extra:None + modelBuilder.Entity() + .HasMany(e => e.TwoSkip) + .WithMany() + .UsingEntity(); + + // Nav:6 Payload:Yes Join:Concrete Extra:None + modelBuilder.Entity() + .HasMany() + .WithMany() + .UsingEntity( + r => r.HasOne(x => x.Three).WithMany(e => e.JoinOnePayloadFull), + l => l.HasOne(x => x.One).WithMany(e => e.JoinThreePayloadFull)); + + // Nav:4 Payload:Yes Join:Shared Extra:None + modelBuilder.Entity() + .HasMany(e => e.ThreeSkipPayloadFullShared) + .WithMany() + .UsingEntity>( + "UnidirectionalJoinOneToThreePayloadFullShared", + r => r.HasOne().WithMany(e => e.JoinOnePayloadFullShared).HasForeignKey("ThreeId"), + l => l.HasOne().WithMany(e => e.JoinThreePayloadFullShared).HasForeignKey("OneId")) + .IndexerProperty("Payload"); + + // Nav:6 Payload:Yes Join:Concrete Extra:Self-Ref + modelBuilder.Entity() + .HasMany(e => e.SelfSkipPayloadLeft) + .WithMany() + .UsingEntity( + l => l.HasOne(x => x.Left).WithMany(x => x.JoinSelfPayloadLeft), + r => r.HasOne(x => x.Right).WithMany(x => x.JoinSelfPayloadRight)); + + // Nav:2 Payload:No Join:Concrete Extra:Inheritance + modelBuilder.Entity() + .HasMany(e => e.BranchSkip) + .WithMany() + .UsingEntity(); + + modelBuilder.Entity() + .HasOne(e => e.Reference) + .WithOne(e => e.ReferenceInverse) + .HasForeignKey(e => e.ReferenceInverseId); + + modelBuilder.Entity() + .HasMany(e => e.Collection) + .WithOne(e => e.CollectionInverse) + .HasForeignKey(e => e.CollectionInverseId); + + // Nav:6 Payload:No Join:Concrete Extra:None + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.TwoSkipFull) + .UsingEntity( + r => r.HasOne(x => x.Three).WithMany(e => e.JoinTwoFull), + l => l.HasOne(x => x.Two).WithMany(e => e.JoinThreeFull)); + + // Nav:2 Payload:No Join:Shared Extra:Self-ref + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.SelfSkipSharedRight); + + // Nav:2 Payload:No Join:Shared Extra:CompositeKey + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.TwoSkipShared); + + // Nav:6 Payload:No Join:Concrete Extra:CompositeKey + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.ThreeSkipFull) + .UsingEntity( + l => l.HasOne(x => x.Composite).WithMany(x => x.JoinThreeFull).HasForeignKey( + e => new + { + e.CompositeId1, + e.CompositeId2, + e.CompositeId3 + }).IsRequired(), + r => r.HasOne(x => x.Three).WithMany(x => x.JoinCompositeKeyFull).IsRequired()); + + // Nav:2 Payload:No Join:Shared Extra:Inheritance + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.ThreeSkipShared); + + // Nav:2 Payload:No Join:Shared Extra:Inheritance,CompositeKey + modelBuilder.Entity() + .HasMany(e => e.RootSkipShared) + .WithMany(); + + // Nav:6 Payload:No Join:Concrete Extra:Inheritance,CompositeKey + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.CompositeKeySkipFull) + .UsingEntity( + r => r.HasOne(x => x.Leaf).WithMany(x => x.JoinCompositeKeyFull), + l => l.HasOne(x => x.Composite).WithMany(x => x.JoinLeafFull).HasForeignKey( + e => new + { + e.CompositeId1, + e.CompositeId2, + e.CompositeId3 + })); + modelBuilder.SharedTypeEntity( "PST", b => { diff --git a/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs index 266a94ace57..2b22c624e14 100644 --- a/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ManyToManyQueryTestBase.cs @@ -786,6 +786,744 @@ public virtual Task GetType_in_hierarchy_in_querying_base_type(bool async) ss => ss.Set().Where(e => e.GetType() == typeof(EntityRoot)), entryCount: 0); + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_all_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.TwoSkip.All(e => e.Name.Contains("B"))), + entryCount: 1); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_any_without_predicate_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Where(e => e.ThreeSkipPayloadFull.Where(e => e.Name.Contains("B")).Any()), + // entryCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_any_with_predicate_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.TwoSkipShared.Any(e => e.Name.Contains("B"))), + entryCount: 0); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_contains_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.ThreeSkipPayloadFullShared.Contains(new UnidirectionalEntityThree { Id = 1 })), + ss => ss.Set().Where(e => e.ThreeSkipPayloadFullShared.Select(i => i.Id).Contains(1)), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_count_without_predicate_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.SelfSkipPayloadLeft.Count > 0), + entryCount: 16); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_count_with_predicate_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(e => e.BranchSkip.Count(e => e.Name.StartsWith("L"))) + .ThenBy(e => e.Id), + assertOrder: true, + entryCount: 20); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_long_count_without_predicate_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Where(e => e.ThreeSkipFull.LongCount() > 0), + // entryCount: 19); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_long_count_with_predicate_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().OrderByDescending(e => e.SelfSkipSharedLeft.LongCount(e => e.Name.StartsWith("L"))) + // .ThenBy(e => e.Id), + // assertOrder: true, + // entryCount: 20); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_select_many_average_unidirectional(bool async) + => Task.CompletedTask; + // => AssertAverage( + // async, + // ss => ss.Set().SelectMany(e => e.CompositeKeySkipShared.Select(e => e.Key1))); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_select_many_max_unidirectional(bool async) + => Task.CompletedTask; + // => AssertMax( + // async, + // ss => ss.Set().SelectMany(e => e.CompositeKeySkipFull.Select(e => e.Key1)), + // entryCount: 0); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_select_many_min_unidirectional(bool async) + => Task.CompletedTask; + // => AssertMin( + // async, + // ss => ss.Set().SelectMany(e => e.RootSkipShared.Select(e => e.Id)), + // entryCount: 0); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_select_many_sum_unidirectional(bool async) + => Task.CompletedTask; + // => AssertSum( + // async, + // ss => ss.Set().SelectMany(e => e.CompositeKeySkipShared.Select(e => e.Key1))); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_select_subquery_average_unidirectional(bool async) + => AssertQueryScalar( + async, + ss => ss.Set().Select(e => e.CompositeKeySkipFull.Average(e => e.Key1))); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_select_subquery_max_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQueryScalar( + // async, + // ss => ss.Set().Select(e => e.OneSkip.Max(e => e.Id))); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_select_subquery_min_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQueryScalar( + // async, + // ss => ss.Set().Select(e => e.OneSkipPayloadFull.Min(e => e.Id))); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_select_subquery_sum_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQueryScalar( + // async, + // ss => ss.Set().Select(e => e.OneSkipShared.Sum(e => e.Id))); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_order_by_first_or_default_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Select(e => e.OneSkipPayloadFullShared.OrderBy(i => i.Id).FirstOrDefault()), + // entryCount: 12); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_order_by_single_or_default_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Select(e => e.SelfSkipPayloadRight.OrderBy(i => i.Id).Take(1).SingleOrDefault()), + // entryCount: 9); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_order_by_last_or_default_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Select(e => e.OneSkip.OrderBy(i => i.Id).LastOrDefault()), + // entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_order_by_reverse_first_or_default_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Select(e => e.TwoSkipFull.OrderBy(i => i.Id).Reverse().FirstOrDefault()), + entryCount: 11); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_cast_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().OrderBy(e => e.Key1).Select(e => e.LeafSkipFull.Cast()), + // assertOrder: true, + // elementAsserter: (e, a) => AssertCollection(e, a), + // entryCount: 4); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Skip_navigation_of_type_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().OrderBy(e => e.Key1).Select(e => e.RootSkipShared.OfType()), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a), + entryCount: 3); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Join_with_skip_navigation_unidirectional(bool async) + => AssertQuery( + async, + ss => from t in ss.Set() + join s in ss.Set() + on t.Id equals s.SelfSkipSharedRight.OrderBy(e => e.Id).FirstOrDefault().Id + select new { t, s }, + elementSorter: e => (e.t.Id, e.s.Id), + elementAsserter: (e, a) => + { + AssertEqual(e.t, a.t); + AssertEqual(e.s, a.s); + }, + entryCount: 18); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Left_join_with_skip_navigation_unidirectional(bool async) + => AssertQuery( + async, + ss => from t in ss.Set() + join s in ss.Set() + on t.TwoSkipShared.OrderBy(e => e.Id).FirstOrDefault().Id equals s.ThreeSkipFull.OrderBy(e => e.Id) + .FirstOrDefault().Id into grouping + from s in grouping.DefaultIfEmpty() + orderby t.Key1, s.Key1, t.Key2, s.Key2 + select new { t, s }, + ss => from t in ss.Set() + join s in ss.Set() + on t.TwoSkipShared.OrderBy(e => e.Id).FirstOrDefault().MaybeScalar(e => e.Id) equals s.ThreeSkipFull + .OrderBy(e => e.Id).FirstOrDefault().MaybeScalar(e => e.Id) into grouping + from s in grouping.DefaultIfEmpty() + orderby t.Key1, s.MaybeScalar(e => e.Key1), t.Key2, s.Key2 + select new { t, s }, + assertOrder: true, + elementAsserter: (e, a) => + { + AssertEqual(e.t, a.t); + AssertEqual(e.s, a.s); + }, + entryCount: 20); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_unidirectional(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + from t in r.ThreeSkipShared + select t, + entryCount: 15); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_where_unidirectional(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + from t in r.TwoSkip.DefaultIfEmpty() + select t, + entryCount: 20); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_order_by_skip_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => from r in ss.Set() + // from t in r.ThreeSkipPayloadFull.OrderBy(e => e.Id).Skip(2) + // select t, + // entryCount: 16); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_order_by_take_unidirectional(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + from t in r.TwoSkipShared.OrderBy(e => e.Id).Take(2) + select t, + entryCount: 19); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_order_by_skip_take_unidirectional(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + from t in r.ThreeSkipPayloadFullShared.OrderBy(e => e.Id).Skip(2).Take(3) + select t, + entryCount: 7); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_of_type_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => from r in ss.Set() + // from t in r.RootSkipShared.OfType() + // select t, + // entryCount: 9); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_cast_unidirectional(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + from t in r.BranchSkip.Cast() + select t, + entryCount: 10); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_skip_navigation_unidirectional(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + orderby r.Id + select r.SelfSkipPayloadLeft, + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a), + entryCount: 13); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_skip_navigation_multiple_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => from r in ss.Set() + // orderby r.Id + // select new + // { + // r.ThreeSkipFull, + // r.SelfSkipSharedLeft, + // r.CompositeKeySkipShared + // }, + // assertOrder: true, + // elementAsserter: (e, a) => + // { + // AssertCollection(e.ThreeSkipFull, a.ThreeSkipFull); + // AssertCollection(e.SelfSkipSharedLeft, a.SelfSkipSharedLeft); + // AssertCollection(e.CompositeKeySkipShared, a.CompositeKeySkipShared); + // }, + // entryCount: 50); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_skip_navigation_first_or_default_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => from r in ss.Set() + // orderby r.Id + // select r.CompositeKeySkipFull.OrderBy(e => e.Key1).ThenBy(e => e.Key2).FirstOrDefault(), + // assertOrder: true, + // entryCount: 12); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Include_skip_navigation_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include(e => e.RootSkipShared), + elementAsserter: (e, a) => AssertInclude(e, a, new ExpectedInclude(et => et.RootSkipShared)), + entryCount: 76); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Include_skip_navigation_then_reference_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include("UnidirectionalEntityOne1.Reference"), + entryCount: 151); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Include_skip_navigation_then_include_skip_navigation_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include("UnidirectionalEntityLeaf.UnidirectionalEntityOne"), + entryCount: 83); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Include_skip_navigation_then_include_reference_and_skip_navigation_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include("UnidirectionalEntityOne.Reference") + .Include("UnidirectionalEntityOne.UnidirectionalEntityOne"), + entryCount: 192); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Include_skip_navigation_and_reference_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include("UnidirectionalEntityOne").Include(e => e.Reference), + entryCount: 93); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Include_skip_navigation_then_include_inverse_works_for_tracking_query_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include("UnidirectionalEntityOne1.ThreeSkipPayloadFullShared"), + entryCount: 76); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_skip_navigation_where_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include(e => e.OneSkipPayloadFullShared.Where(i => i.Id < 10)), + elementAsserter: (e, a) => AssertInclude( + e, a, + new ExpectedFilteredInclude( + et => et.OneSkipPayloadFullShared, includeFilter: x => x.Where(i => i.Id < 10))), + entryCount: 42); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_skip_navigation_order_by_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include(e => e.TwoSkipFull.OrderBy(i => i.Id)), + elementAsserter: (e, a) => AssertInclude( + e, a, + new ExpectedFilteredInclude( + et => et.TwoSkipFull, includeFilter: x => x.OrderBy(i => i.Id))), + entryCount: 91); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_skip_navigation_order_by_skip_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include(e => e.SelfSkipSharedRight.OrderBy(i => i.Id).Skip(2)), + elementAsserter: (e, a) => AssertInclude( + e, a, + new ExpectedFilteredInclude( + et => et.SelfSkipSharedRight, includeFilter: x => x.OrderBy(i => i.Id).Skip(2))), + entryCount: 31); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_skip_navigation_order_by_take_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include(e => e.TwoSkipShared.OrderBy(i => i.Id).Take(2)), + elementAsserter: (e, a) => AssertInclude( + e, a, + new ExpectedFilteredInclude( + et => et.TwoSkipShared, includeFilter: x => x.OrderBy(i => i.Id).Take(2))), + entryCount: 63); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_skip_navigation_order_by_skip_take_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include(e => e.ThreeSkipFull.OrderBy(i => i.Id).Skip(1).Take(2)), + elementAsserter: (e, a) => AssertInclude( + e, a, + new ExpectedFilteredInclude( + et => et.ThreeSkipFull, includeFilter: x => x.OrderBy(i => i.Id).Skip(1).Take(2))), + entryCount: 57); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_then_include_skip_navigation_where_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.ThreeSkipShared) + // .ThenInclude(e => e.OneSkipPayloadFullShared.Where(i => i.Id < 10)), + // elementAsserter: (e, a) => AssertInclude( + // e, a, + // new ExpectedInclude(et => et.ThreeSkipShared), + // new ExpectedFilteredInclude( + // et => et.OneSkipPayloadFullShared, "ThreeSkipShared", includeFilter: x => x.Where(i => i.Id < 10))), + // entryCount: 78); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_then_include_skip_navigation_order_by_skip_take_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.CompositeKeySkipShared) + // .ThenInclude(e => e.ThreeSkipFull.OrderBy(i => i.Id).Skip(1).Take(2)), + // elementAsserter: (e, a) => AssertInclude( + // e, a, + // new ExpectedInclude(et => et.CompositeKeySkipShared), + // new ExpectedFilteredInclude( + // et => et.ThreeSkipFull, "CompositeKeySkipShared", includeFilter: x => x.OrderBy(i => i.Id).Skip(1).Take(2))), + // entryCount: 104); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_skip_navigation_where_then_include_skip_navigation_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include(e => e.CompositeKeySkipFull.Where(i => i.Key1 < 5)).ThenInclude(e => e.TwoSkipShared), + elementAsserter: (e, a) => AssertInclude( + e, a, + new ExpectedFilteredInclude( + et => et.CompositeKeySkipFull, includeFilter: x => x.Where(i => i.Key1 < 5)), + new ExpectedInclude(et => et.TwoSkipShared, "CompositeKeySkipFull")), + entryCount: 44); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_skip_navigation_order_by_skip_take_then_include_skip_navigation_where_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.TwoSkip.OrderBy(i => i.Id).Skip(1).Take(2)) + // .ThenInclude(e => e.ThreeSkipFull.Where(i => i.Id < 10)), + // elementAsserter: (e, a) => AssertInclude( + // e, a, + // new ExpectedFilteredInclude( + // et => et.TwoSkip, includeFilter: x => x.OrderBy(i => i.Id).Skip(1).Take(2)), + // new ExpectedFilteredInclude( + // et => et.ThreeSkipFull, "TwoSkip", includeFilter: x => x.Where(i => i.Id < 10))), + // entryCount: 100); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_skip_navigation_where_then_include_skip_navigation_order_by_skip_take_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.TwoSkip.Where(i => i.Id < 10)) + // .ThenInclude(e => e.ThreeSkipFull.OrderBy(i => i.Id).Skip(1).Take(2)), + // elementAsserter: (e, a) => AssertInclude( + // e, a, + // new ExpectedFilteredInclude( + // et => et.TwoSkip, includeFilter: x => x.Where(i => i.Id < 10)), + // new ExpectedFilteredInclude( + // et => et.ThreeSkipFull, "TwoSkip", includeFilter: x => x.OrderBy(i => i.Id).Skip(1).Take(2))), + // entryCount: 106); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filter_include_on_skip_navigation_combined_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include(e => e.OneSkip.Where(i => i.Id < 10)).ThenInclude(e => e.Reference) + .Include(e => e.OneSkip).ThenInclude(e => e.Collection), + entryCount: 88); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filter_include_on_skip_navigation_combined_with_filtered_then_includes_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.OneSkipPayloadFull.Where(i => i.Id < 10)) + // .ThenInclude(e => e.TwoSkip.OrderBy(e => e.Id).Skip(1).Take(2)) + // .Include(e => e.OneSkipPayloadFull).ThenInclude(e => e.BranchSkip.Where(e => e.Id < 20)), + // elementAsserter: (e, a) => AssertInclude( + // e, a, + // new ExpectedFilteredInclude( + // et => et.OneSkipPayloadFull, includeFilter: x => x.Where(i => i.Id < 10)), + // new ExpectedFilteredInclude( + // et => et.TwoSkip, "OneSkipPayloadFull", includeFilter: x => x.OrderBy(e => e.Id).Skip(1).Take(2)), + // new ExpectedFilteredInclude( + // et => et.BranchSkip, "OneSkipPayloadFull", includeFilter: x => x.Where(e => e.Id < 20))), + // entryCount: 116); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Throws_when_different_filtered_include_unidirectional(bool async) + => Task.CompletedTask; + // => Assert.Equal( + // CoreStrings.MultipleFilteredIncludesOnSameNavigation( + // "navigation .Where(i => i.Id < 20)", "navigation .Where(i => i.Id < 10)") + // .Replace("\r", "").Replace("\n", ""), + // (await Assert.ThrowsAsync( + // () => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.OneSkip.Where(i => i.Id < 10)).ThenInclude(e => e.BranchSkip) + // .Include(e => e.OneSkip.Where(i => i.Id < 20)).ThenInclude(e => e.ThreeSkipPayloadFull)))).Message + // .Replace("\r", "").Replace("\n", "")); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Throws_when_different_filtered_then_include_unidirectional(bool async) + => Task.CompletedTask; + // => Assert.Equal( + // CoreStrings.MultipleFilteredIncludesOnSameNavigation( + // "navigation .Where(i => i.Id < 20)", "navigation .Where(i => i.Id < 10)") + // .Replace("\r", "").Replace("\n", ""), + // (await Assert.ThrowsAsync( + // () => AssertQuery( + // async, + // ss => ss.Set() + // .Include(e => e.TwoSkipShared) + // .ThenInclude(e => e.OneSkip.Where(i => i.Id < 10)).ThenInclude(e => e.BranchSkip) + // .Include(e => e.TwoSkipShared) + // .ThenInclude(e => e.OneSkip.Where(i => i.Id < 20)).ThenInclude(e => e.ThreeSkipPayloadFull)))).Message + // .Replace("\r", "").Replace("\n", "")); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_on_skip_navigation_then_filtered_include_on_navigation_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.OneSkipPayloadFull.Where(i => i.Id > 15)) + // .ThenInclude(e => e.Collection.Where(i => i.Id < 5)), + // elementAsserter: (e, a) => AssertInclude( + // e, a, + // new ExpectedFilteredInclude( + // et => et.OneSkipPayloadFull, includeFilter: x => x.Where(i => i.Id > 15)), + // new ExpectedFilteredInclude( + // et => et.Collection, "OneSkipPayloadFull", includeFilter: x => x.Where(i => i.Id < 5))), + // entryCount: 61); + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_on_navigation_then_filtered_include_on_skip_navigation_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.Collection.Where(i => i.Id > 15)) + // .ThenInclude(e => e.ThreeSkipFull.Where(i => i.Id < 5)), + // elementAsserter: (e, a) => AssertInclude( + // e, a, + // new ExpectedFilteredInclude(et => et.Collection, includeFilter: x => x.Where(i => i.Id > 15)), + // new ExpectedFilteredInclude( + // et => et.ThreeSkipFull, "Collection", includeFilter: x => x.Where(i => i.Id < 5))), + // entryCount: 29); + + [ConditionalTheory(Skip = "Issue#21332")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Includes_accessed_via_different_path_are_merged_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Include("ThreeSkipPayloadFull.CollectionInverse") + .Include(e => e.JoinThreePayloadFull).ThenInclude(e => e.Three).ThenInclude(e => e.ReferenceInverse), + entryCount: 0); + + [ConditionalTheory(Skip = "Issue#21332; TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filered_includes_accessed_via_different_path_are_merged_unidirectional(bool async) + => Task.CompletedTask; + // => AssertQuery( + // async, + // ss => ss.Set().Include(e => e.OneSkipPayloadFull).ThenInclude(e => e.Collection.Where(i => i.Id < 5)) + // .Include(e => e.JoinOnePayloadFull).ThenInclude(e => e.One).ThenInclude(e => e.Collection) + // .ThenInclude(e => e.Reference), + // elementAsserter: (e, a) => AssertInclude( + // e, a, + // new ExpectedInclude(e => e.OneSkipPayloadFull), + // new ExpectedFilteredInclude( + // e => e.Collection, "OneSkipPayloadFull", includeFilter: x => x.Where(i => i.Id < 5)), + // new ExpectedInclude(e => e.JoinOnePayloadFull), + // new ExpectedInclude(e => e.One, "JoinOnePayloadFull"), + // new ExpectedFilteredInclude( + // e => e.Collection, "JoinOnePayloadFull.One", includeFilter: x => x.Where(i => i.Id < 5)), + // new ExpectedInclude(e => e.Reference, "OneSkipPayloadFull.Collection"), + // new ExpectedInclude(e => e.Reference, "JoinOnePayloadFull.One.Collection")), + // entryCount: 0); + + [ConditionalTheory(Skip = "Issue#21332; TODOU: Needs #27493")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Throws_when_different_filtered_then_include_via_different_paths_unidirectional(bool async) + => Task.CompletedTask; + // => Assert.Equal( + // CoreStrings.MultipleFilteredIncludesOnSameNavigation( + // "navigation .Where(i => i.Id < 20)", "navigation .Where(i => i.Id < 10)") + // .Replace("\r", "").Replace("\n", ""), + // (await Assert.ThrowsAsync( + // () => AssertQuery( + // async, + // ss => ss.Set() + // .Include(e => e.OneSkipPayloadFull) + // .ThenInclude(e => e.Collection.Where(i => i.Id < 20)) + // .Include(e => e.JoinOnePayloadFull) + // .ThenInclude(e => e.One) + // .ThenInclude(e => e.Collection.Where(i => i.Id < 10))))).Message + // .Replace("\r", "").Replace("\n", "")); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Select_many_over_skip_navigation_where_non_equality_unidirectional(bool async) + => AssertQuery( + async, + ss => from r in ss.Set() + from t in r.TwoSkip.Where(x => x.Id != r.Id).DefaultIfEmpty() + select t, + entryCount: 20); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Contains_on_skip_collection_navigation_unidirectional(bool async) + { + var two = new UnidirectionalEntityTwo { Id = 1 }; + + return AssertQuery( + async, + ss => ss.Set().Where(e => e.TwoSkip.Contains(two)), + ss => ss.Set().Where(e => e.TwoSkip.Select(i => i.Id).Contains(two.Id)), + entryCount: 11); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_base_type_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(UnidirectionalEntityRoot)), + entryCount: 10); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_intermediate_type_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(UnidirectionalEntityBranch)), + entryCount: 6); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_leaf_type_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(UnidirectionalEntityLeaf)), + entryCount: 4); + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task GetType_in_hierarchy_in_querying_base_type_unidirectional(bool async) + => AssertQuery( + async, + ss => ss.Set().Where(e => e.GetType() == typeof(UnidirectionalEntityRoot)), + entryCount: 0); + // When adding include test here always add a tracking version and a split version in relational layer. // Keep this line at the bottom for next dev writing tests to see. diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/ManyToManyContext.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/ManyToManyContext.cs index 316911b4e05..2b004c33836 100644 --- a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/ManyToManyContext.cs +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/ManyToManyContext.cs @@ -21,6 +21,13 @@ public ManyToManyContext(DbContextOptions options) public DbSet ImplicitManyToManyBs { get; set; } public DbSet GeneratedKeysLefts { get; set; } public DbSet GeneratedKeysRights { get; set; } + public DbSet UnidirectionalEntityOnes { get; set; } + public DbSet UnidirectionalEntityTwos { get; set; } + public DbSet UnidirectionalEntityThrees { get; set; } + public DbSet UnidirectionalEntityCompositeKeys { get; set; } + public DbSet UnidirectionalEntityRoots { get; set; } + public DbSet UnidirectionalGeneratedKeysLefts { get; set; } + public DbSet UnidirectionalGeneratedKeysRights { get; set; } } public static class ManyToManyContextExtensions diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/ManyToManyData.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/ManyToManyData.cs index 18cfa3eb6d1..44201ef49d4 100644 --- a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/ManyToManyData.cs +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/ManyToManyData.cs @@ -13,6 +13,11 @@ public class ManyToManyData : ISetSource private readonly EntityThree[] _threes; private readonly EntityCompositeKey[] _compositeKeys; private readonly EntityRoot[] _roots; + private readonly UnidirectionalEntityOne[] _unidirectionalOnes; + private readonly UnidirectionalEntityTwo[] _unidirectionalTwos; + private readonly UnidirectionalEntityThree[] _unidirectionalThrees; + private readonly UnidirectionalEntityCompositeKey[] _unidirectionalCompositeKeys; + private readonly UnidirectionalEntityRoot[] _unidirectionalRoots; public ManyToManyData(ManyToManyContext context, bool useGeneratedKeys) { @@ -43,47 +48,65 @@ public ManyToManyData(ManyToManyContext context, bool useGeneratedKeys) context.Set>("EntityCompositeKeyEntityTwo").AddRange(CreateJoinTwoToCompositeKeyShareds(context)); context.Set>("EntityRootEntityThree").AddRange(CreateEntityRootEntityThrees(context)); context.Set>("EntityCompositeKeyEntityRoot").AddRange(CreateJoinCompositeKeyToRootShareds(context)); - } - public IQueryable Set() - where TEntity : class - { - if (typeof(TEntity) == typeof(EntityOne)) - { - return (IQueryable)_ones.AsQueryable(); - } + _unidirectionalOnes = CreateUnidirectionalOnes(context); + context.Set().AddRange(_unidirectionalOnes); + _unidirectionalTwos = CreateUnidirectionalTwos(context); + context.Set().AddRange(_unidirectionalTwos); + _unidirectionalThrees = CreateUnidirectionalThrees(context); + context.Set().AddRange(_unidirectionalThrees); + _unidirectionalCompositeKeys = CreateUnidirectionalCompositeKeys(context); + context.Set().AddRange(_unidirectionalCompositeKeys); + _unidirectionalRoots = CreateUnidirectionalRoots(context); + context.Set().AddRange(_unidirectionalRoots); - if (typeof(TEntity) == typeof(EntityTwo)) - { - return (IQueryable)_twos.AsQueryable(); - } + context.Set().AddRange(CreateUnidirectionalJoinCompositeKeyToLeaves(context)); + context.Set().AddRange(CreateUnidirectionalJoinOneSelfPayloads(context)); + context.Set().AddRange(CreateUnidirectionalJoinOneToBranches(context)); + context.Set().AddRange(CreateUnidirectionalJoinOneToThreePayloadFulls(context)); + context.Set().AddRange(CreateUnidirectionalJoinOneToTwos(context)); + context.Set().AddRange(CreateUnidirectionalJoinThreeToCompositeKeyFulls(context)); + context.Set().AddRange(CreateUnidirectionalJoinTwoToThrees(context)); - if (typeof(TEntity) == typeof(EntityThree)) - { - return (IQueryable)_threes.AsQueryable(); - } - - if (typeof(TEntity) == typeof(EntityCompositeKey)) - { - return (IQueryable)_compositeKeys.AsQueryable(); - } + context.Set>("UnidirectionalEntityOneUnidirectionalEntityTwo") + .AddRange(CreateUnidirectionalEntityOneEntityTwos(context)); + context.Set>("UnidirectionalJoinOneToThreePayloadFullShared") + .AddRange(CreateUnidirectionalJoinOneToThreePayloadFullShareds(context)); + context.Set>("UnidirectionalEntityTwoUnidirectionalEntityTwo") + .AddRange(CreateUnidirectionalJoinTwoSelfShareds(context)); + context.Set>("UnidirectionalEntityCompositeKeyUnidirectionalEntityTwo") + .AddRange(CreateUnidirectionalJoinTwoToCompositeKeyShareds(context)); + context.Set>("UnidirectionalEntityRootUnidirectionalEntityThree") + .AddRange(CreateUnidirectionalEntityRootEntityThrees(context)); + context.Set>("UnidirectionalEntityCompositeKeyUnidirectionalEntityRoot") + .AddRange(CreateUnidirectionalJoinCompositeKeyToRootShareds(context)); - if (typeof(TEntity) == typeof(EntityRoot)) - { - return (IQueryable)_roots.AsQueryable(); - } - - if (typeof(TEntity) == typeof(EntityBranch)) - { - return (IQueryable)_roots.OfType().AsQueryable(); - } + var entries = context.ChangeTracker.Entries().ToList(); + } - if (typeof(TEntity) == typeof(EntityLeaf)) + public IQueryable Set() + where TEntity : class + { + return typeof(TEntity).Name switch { - return (IQueryable)_roots.OfType().AsQueryable(); - } - - throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)); + nameof(EntityOne) => (IQueryable)_ones.AsQueryable(), + nameof(EntityTwo) => (IQueryable)_twos.AsQueryable(), + nameof(EntityThree) => (IQueryable)_threes.AsQueryable(), + nameof(EntityCompositeKey) => (IQueryable)_compositeKeys.AsQueryable(), + nameof(EntityRoot) => (IQueryable)_roots.AsQueryable(), + nameof(EntityBranch) => (IQueryable)_roots.OfType().AsQueryable(), + nameof(EntityLeaf) => (IQueryable)_roots.OfType().AsQueryable(), + nameof(UnidirectionalEntityOne) => (IQueryable)_unidirectionalOnes.AsQueryable(), + nameof(UnidirectionalEntityTwo) => (IQueryable)_unidirectionalTwos.AsQueryable(), + nameof(UnidirectionalEntityThree) => (IQueryable)_unidirectionalThrees.AsQueryable(), + nameof(UnidirectionalEntityCompositeKey) => (IQueryable)_unidirectionalCompositeKeys.AsQueryable(), + nameof(UnidirectionalEntityRoot) => (IQueryable)_unidirectionalRoots.AsQueryable(), + nameof(UnidirectionalEntityBranch) => (IQueryable)_unidirectionalRoots.OfType() + .AsQueryable(), + nameof(UnidirectionalEntityLeaf) => (IQueryable)_unidirectionalRoots.OfType() + .AsQueryable(), + _ => throw new InvalidOperationException("Invalid entity type: " + typeof(TEntity)) + }; } private EntityOne[] CreateOnes(ManyToManyContext context) @@ -1207,9 +1230,6 @@ private Dictionary[] CreateJoinCompositeKeyToRootShareds(ManyToM CreateJoinCompositeKeyToRootShared(context, _roots[5], _compositeKeys[19]) }; - private static ICollection CreateCollection(bool proxy) - => proxy ? new ObservableCollection() : new List(); - private static Dictionary CreateJoinCompositeKeyToRootShared( ManyToManyContext context, EntityRoot root, @@ -1223,6 +1243,1126 @@ private static Dictionary CreateJoinCompositeKeyToRootShared( e["CompositeKeySkipSharedKey3"] = composite.Key3; }); + private UnidirectionalEntityOne[] CreateUnidirectionalOnes(ManyToManyContext context) + => new[] + { + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 1, "EntityOne 1"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 2, "EntityOne 2"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 3, "EntityOne 3"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 4, "EntityOne 4"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 5, "EntityOne 5"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 6, "EntityOne 6"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 7, "EntityOne 7"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 8, "EntityOne 8"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 9, "EntityOne 9"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 10, "EntityOne 10"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 11, "EntityOne 11"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 12, "EntityOne 12"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 13, "EntityOne 13"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 14, "EntityOne 14"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 15, "EntityOne 15"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 16, "EntityOne 16"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 17, "EntityOne 17"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 18, "EntityOne 18"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 19, "EntityOne 19"), + CreateUnidirectionalEntityOne(context, _useGeneratedKeys ? 0 : 20, "EntityOne 20"), + }; + + private static UnidirectionalEntityOne CreateUnidirectionalEntityOne(ManyToManyContext context, int id, string name) + => CreateInstance( + context?.UnidirectionalEntityOnes, (e, p) => + { + e.Id = id; + e.Name = name; + e.Collection = CreateCollection(p); + e.TwoSkip = CreateCollection(p); + e.JoinThreePayloadFull = CreateCollection(p); + e.TwoSkipShared = CreateCollection(p); + e.ThreeSkipPayloadFullShared = CreateCollection(p); + e.JoinThreePayloadFullShared = CreateCollection>(p); + e.SelfSkipPayloadLeft = CreateCollection(p); + e.JoinSelfPayloadLeft = CreateCollection(p); + e.JoinSelfPayloadRight = CreateCollection(p); + e.BranchSkip = CreateCollection(p); + }); + + private UnidirectionalEntityTwo[] CreateUnidirectionalTwos(ManyToManyContext context) + => new[] + { + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 1, "EntityTwo 1", null, _unidirectionalOnes[0]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 2, "EntityTwo 2", null, _unidirectionalOnes[0]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 3, "EntityTwo 3", null, null), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 4, "EntityTwo 4", null, _unidirectionalOnes[2]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 5, "EntityTwo 5", null, _unidirectionalOnes[2]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 6, "EntityTwo 6", null, _unidirectionalOnes[4]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 7, "EntityTwo 7", null, _unidirectionalOnes[4]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 8, "EntityTwo 8", null, _unidirectionalOnes[6]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 9, "EntityTwo 9", null, _unidirectionalOnes[6]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 10, "EntityTwo 10", _unidirectionalOnes[19], _unidirectionalOnes[8]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 11, "EntityTwo 11", _unidirectionalOnes[17], _unidirectionalOnes[8]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 12, "EntityTwo 12", _unidirectionalOnes[15], _unidirectionalOnes[10]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 13, "EntityTwo 13", _unidirectionalOnes[13], _unidirectionalOnes[10]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 14, "EntityTwo 14", _unidirectionalOnes[11], _unidirectionalOnes[12]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 15, "EntityTwo 15", _unidirectionalOnes[10], _unidirectionalOnes[12]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 16, "EntityTwo 16", _unidirectionalOnes[8], _unidirectionalOnes[14]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 17, "EntityTwo 17", _unidirectionalOnes[6], _unidirectionalOnes[14]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 18, "EntityTwo 18", _unidirectionalOnes[4], _unidirectionalOnes[15]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 19, "EntityTwo 19", _unidirectionalOnes[2], _unidirectionalOnes[15]), + CreateUnidirectionalEntityTwo(context, _useGeneratedKeys ? 0 : 20, "EntityTwo 20", _unidirectionalOnes[0], _unidirectionalOnes[16]), + }; + + private static UnidirectionalEntityTwo CreateUnidirectionalEntityTwo( + ManyToManyContext context, + int id, + string name, + UnidirectionalEntityOne referenceInverse, + UnidirectionalEntityOne collectionInverse) + => CreateInstance( + context?.UnidirectionalEntityTwos, (e, p) => + { + e.Id = id; + e.Name = name; + e.ReferenceInverse = referenceInverse; + e.CollectionInverse = collectionInverse; + e.Collection = CreateCollection(p); + e.JoinThreeFull = CreateCollection(p); + e.SelfSkipSharedRight = CreateCollection(p); + }); + + private UnidirectionalEntityThree[] CreateUnidirectionalThrees(ManyToManyContext context) + => new[] + { + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 1, "EntityThree 1", null, null), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 2, "EntityThree 2", _unidirectionalTwos[18], _unidirectionalTwos[16]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 3, "EntityThree 3", _unidirectionalTwos[1], _unidirectionalTwos[15]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 4, "EntityThree 4", _unidirectionalTwos[19], _unidirectionalTwos[15]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 5, "EntityThree 5", _unidirectionalTwos[3], _unidirectionalTwos[14]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 6, "EntityThree 6", null, _unidirectionalTwos[14]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 7, "EntityThree 7", _unidirectionalTwos[5], _unidirectionalTwos[12]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 8, "EntityThree 8", null, _unidirectionalTwos[12]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 9, "EntityThree 9", _unidirectionalTwos[7], _unidirectionalTwos[10]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 10, "EntityThree 10", null, _unidirectionalTwos[10]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 11, "EntityThree 11", _unidirectionalTwos[18], _unidirectionalTwos[8]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 12, "EntityThree 12", null, _unidirectionalTwos[8]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 13, "EntityThree 13", _unidirectionalTwos[11], _unidirectionalTwos[6]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 14, "EntityThree 14", null, _unidirectionalTwos[6]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 15, "EntityThree 15", _unidirectionalTwos[13], _unidirectionalTwos[4]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 16, "EntityThree 16", null, _unidirectionalTwos[4]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 17, "EntityThree 17", _unidirectionalTwos[15], _unidirectionalTwos[2]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 18, "EntityThree 18", null, _unidirectionalTwos[2]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 19, "EntityThree 19", _unidirectionalTwos[17], _unidirectionalTwos[0]), + CreateUnidirectionalEntityThree(context, _useGeneratedKeys ? 0 : 20, "EntityThree 20", null, _unidirectionalTwos[0]), + }; + + private static UnidirectionalEntityThree CreateUnidirectionalEntityThree( + ManyToManyContext context, + int id, + string name, + UnidirectionalEntityTwo referenceInverse, + UnidirectionalEntityTwo collectionInverse) + => CreateInstance( + context?.UnidirectionalEntityThrees, (e, p) => + { + e.Id = id; + e.Name = name; + e.ReferenceInverse = referenceInverse; + e.CollectionInverse = collectionInverse; + e.JoinOnePayloadFull = CreateCollection(p); + e.TwoSkipFull = CreateCollection(p); + e.JoinTwoFull = CreateCollection(p); + e.JoinOnePayloadFullShared = CreateCollection>(p); + e.JoinCompositeKeyFull = CreateCollection(p); + }); + + private UnidirectionalEntityCompositeKey[] CreateUnidirectionalCompositeKeys(ManyToManyContext context) + => new[] + { + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 1, "1_1", new DateTime(2001, 1, 1), "Composite 1"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 1, "1_2", new DateTime(2001, 2, 1), "Composite 2"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 3, "3_1", new DateTime(2003, 1, 1), "Composite 3"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 3, "3_2", new DateTime(2003, 2, 1), "Composite 4"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 3, "3_3", new DateTime(2003, 3, 1), "Composite 5"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 6, "6_1", new DateTime(2006, 1, 1), "Composite 6"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 7, "7_1", new DateTime(2007, 1, 1), "Composite 7"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 7, "7_2", new DateTime(2007, 2, 1), "Composite 8"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 8, "8_1", new DateTime(2008, 1, 1), "Composite 9"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 8, "8_2", new DateTime(2008, 2, 1), "Composite 10"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 8, "8_3", new DateTime(2008, 3, 1), "Composite 11"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 8, "8_4", new DateTime(2008, 4, 1), "Composite 12"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 8, "8_5", new DateTime(2008, 5, 1), "Composite 13"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 9, "9_1", new DateTime(2009, 1, 1), "Composite 14"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 9, "9_2", new DateTime(2009, 2, 1), "Composite 15"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 9, "9_3", new DateTime(2009, 3, 1), "Composite 16"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 9, "9_4", new DateTime(2009, 4, 1), "Composite 17"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 9, "9_5", new DateTime(2009, 5, 1), "Composite 18"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 9, "9_6", new DateTime(2009, 6, 1), "Composite 19"), + CreateUnidirectionalEntityCompositeKey(context, _useGeneratedKeys ? 0 : 9, "9_7", new DateTime(2009, 7, 1), "Composite 20") + }; + + private static UnidirectionalEntityCompositeKey CreateUnidirectionalEntityCompositeKey( + ManyToManyContext context, + int key1, + string key2, + DateTime key3, + string name) + => CreateInstance( + context?.UnidirectionalEntityCompositeKeys, (e, p) => + { + e.Key1 = key1; + e.Key2 = key2; + e.Key3 = key3; + e.Name = name; + e.TwoSkipShared = CreateCollection(p); + e.ThreeSkipFull = CreateCollection(p); + e.RootSkipShared = CreateCollection(p); + e.JoinLeafFull = CreateCollection(p); + e.JoinThreeFull = CreateCollection(p); + }); + + private UnidirectionalEntityRoot[] CreateUnidirectionalRoots(ManyToManyContext context) + => new[] + { + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 1, "Root 1"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 2, "Root 2"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 3, "Root 3"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 4, "Root 4"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 5, "Root 5"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 6, "Root 6"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 7, "Root 7"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 8, "Root 8"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 9, "Root 9"), + CreateUnidirectionalEntityRoot(context, _useGeneratedKeys ? 0 : 10, "Root 10"), + CreateUnidirectionalEntityBranch(context, _useGeneratedKeys ? 0 : 11, "Branch 1", 7), + CreateUnidirectionalEntityBranch(context, _useGeneratedKeys ? 0 : 12, "Branch 2", 77), + CreateUnidirectionalEntityBranch(context, _useGeneratedKeys ? 0 : 13, "Branch 3", 777), + CreateUnidirectionalEntityBranch(context, _useGeneratedKeys ? 0 : 14, "Branch 4", 7777), + CreateUnidirectionalEntityBranch(context, _useGeneratedKeys ? 0 : 15, "Branch 5", 77777), + CreateUnidirectionalEntityBranch(context, _useGeneratedKeys ? 0 : 16, "Branch 6", 777777), + CreateUnidirectionalEntityLeaf(context, _useGeneratedKeys ? 0 : 21, "Leaf 1", 42, true), + CreateUnidirectionalEntityLeaf(context, _useGeneratedKeys ? 0 : 22, "Leaf 2", 421, true), + CreateUnidirectionalEntityLeaf(context, _useGeneratedKeys ? 0 : 23, "Leaf 3", 1337, false), + CreateUnidirectionalEntityLeaf(context, _useGeneratedKeys ? 0 : 24, "Leaf 4", 1729, false) + }; + + private static UnidirectionalEntityRoot CreateUnidirectionalEntityRoot( + ManyToManyContext context, + int id, + string name) + => CreateInstance( + context?.UnidirectionalEntityRoots, (e, p) => + { + e.Id = id; + e.Name = name; + e.ThreeSkipShared = CreateCollection(p); + }); + + private static UnidirectionalEntityBranch CreateUnidirectionalEntityBranch( + ManyToManyContext context, + int id, + string name, + long number) + => CreateInstance( + context?.Set(), (e, p) => + { + e.Id = id; + e.Name = name; + e.Number = number; + e.ThreeSkipShared = CreateCollection(p); + }); + + private static UnidirectionalEntityLeaf CreateUnidirectionalEntityLeaf( + ManyToManyContext context, + int id, + string name, + long number, + bool? isGreen) + => CreateInstance( + context?.Set(), (e, p) => + { + e.Id = id; + e.Name = name; + e.Number = number; + e.IsGreen = isGreen; + e.ThreeSkipShared = CreateCollection(p); + e.CompositeKeySkipFull = CreateCollection(p); + e.JoinCompositeKeyFull = CreateCollection(p); + }); + + private UnidirectionalJoinCompositeKeyToLeaf[] CreateUnidirectionalJoinCompositeKeyToLeaves(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[17], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[19], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[17], _unidirectionalCompositeKeys[4]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[5]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[17], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[19], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[8]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[9]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[17], _unidirectionalCompositeKeys[10]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[10]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[19], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[13]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[17], _unidirectionalCompositeKeys[13]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[13]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[17], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[19], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[16]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[19], _unidirectionalCompositeKeys[16]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[18], _unidirectionalCompositeKeys[17]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[19], _unidirectionalCompositeKeys[17]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[16], _unidirectionalCompositeKeys[18]), + CreateUnidirectionalJoinCompositeKeyToLeaf(context, (UnidirectionalEntityLeaf)_unidirectionalRoots[17], _unidirectionalCompositeKeys[18]) + }; + + private static UnidirectionalJoinCompositeKeyToLeaf CreateUnidirectionalJoinCompositeKeyToLeaf( + ManyToManyContext context, + UnidirectionalEntityLeaf leaf, + UnidirectionalEntityCompositeKey composite) + => CreateInstance( + context?.Set(), (e, p) => + { + e.Leaf = leaf; + e.Composite = composite; + }); + + private UnidirectionalJoinOneSelfPayload[] CreateUnidirectionalJoinOneSelfPayloads(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[2], _unidirectionalOnes[3], DateTime.Parse("2020-01-11 19:26:36")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[2], _unidirectionalOnes[5], DateTime.Parse("2005-10-03 12:57:54")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[2], _unidirectionalOnes[7], DateTime.Parse("2015-12-20 01:09:24")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[2], _unidirectionalOnes[17], DateTime.Parse("1999-12-26 02:51:57")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[2], _unidirectionalOnes[19], DateTime.Parse("2011-06-15 19:08:00")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[4], _unidirectionalOnes[2], DateTime.Parse("2019-12-08 05:40:16")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[4], _unidirectionalOnes[3], DateTime.Parse("2014-03-09 12:58:26")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[5], _unidirectionalOnes[4], DateTime.Parse("2014-05-15 16:34:38")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[5], _unidirectionalOnes[6], DateTime.Parse("2014-03-08 18:59:49")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[5], _unidirectionalOnes[12], DateTime.Parse("2013-12-10 07:01:53")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[6], _unidirectionalOnes[12], DateTime.Parse("2005-05-31 02:21:16")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[7], _unidirectionalOnes[8], DateTime.Parse("2011-12-31 19:37:25")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[7], _unidirectionalOnes[10], DateTime.Parse("2012-08-02 16:33:07")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[7], _unidirectionalOnes[11], DateTime.Parse("2018-07-19 09:10:12")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[9], _unidirectionalOnes[6], DateTime.Parse("2018-12-28 01:21:23")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[12], _unidirectionalOnes[1], DateTime.Parse("2014-03-22 02:20:06")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[12], _unidirectionalOnes[17], DateTime.Parse("2005-03-21 14:45:37")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[13], _unidirectionalOnes[8], DateTime.Parse("2016-06-26 08:03:32")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[14], _unidirectionalOnes[12], DateTime.Parse("2018-09-18 12:51:22")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[15], _unidirectionalOnes[4], DateTime.Parse("2016-12-17 14:20:25")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[15], _unidirectionalOnes[5], DateTime.Parse("2008-07-30 03:43:17")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[16], _unidirectionalOnes[13], DateTime.Parse("2019-08-01 16:26:31")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[18], _unidirectionalOnes[0], DateTime.Parse("2010-02-19 13:24:07")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[18], _unidirectionalOnes[7], DateTime.Parse("2004-07-28 09:06:02")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[18], _unidirectionalOnes[11], DateTime.Parse("2004-08-21 11:07:20")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[19], _unidirectionalOnes[0], DateTime.Parse("2014-11-21 18:13:02")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[19], _unidirectionalOnes[6], DateTime.Parse("2009-08-24 21:44:46")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[19], _unidirectionalOnes[13], DateTime.Parse("2013-02-18 02:19:19")), + CreateUnidirectionalJoinOneSelfPayload(context, _unidirectionalOnes[19], _unidirectionalOnes[15], DateTime.Parse("2016-02-05 14:18:12")) + }; + + private static UnidirectionalJoinOneSelfPayload CreateUnidirectionalJoinOneSelfPayload( + ManyToManyContext context, + UnidirectionalEntityOne left, + UnidirectionalEntityOne right, + DateTime payload) + => CreateInstance( + context?.Set(), (e, p) => + { + e.Left = left; + e.Right = right; + e.Payload = payload; + }); + + private UnidirectionalJoinOneToBranch[] CreateUnidirectionalJoinOneToBranches(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[1], _unidirectionalRoots[15]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[1], _unidirectionalRoots[19]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[2], _unidirectionalRoots[13]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[2], _unidirectionalRoots[15]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[2], _unidirectionalRoots[17]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[2], _unidirectionalRoots[19]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[4], _unidirectionalRoots[12]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[5], _unidirectionalRoots[15]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[5], _unidirectionalRoots[17]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[5], _unidirectionalRoots[18]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[7], _unidirectionalRoots[10]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[7], _unidirectionalRoots[11]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[7], _unidirectionalRoots[12]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[8], _unidirectionalRoots[10]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[8], _unidirectionalRoots[11]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[8], _unidirectionalRoots[13]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[8], _unidirectionalRoots[15]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[8], _unidirectionalRoots[16]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[8], _unidirectionalRoots[19]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[9], _unidirectionalRoots[11]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[9], _unidirectionalRoots[12]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[9], _unidirectionalRoots[13]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[9], _unidirectionalRoots[16]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[11], _unidirectionalRoots[10]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[11], _unidirectionalRoots[11]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[11], _unidirectionalRoots[13]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[11], _unidirectionalRoots[18]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[12], _unidirectionalRoots[14]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[13], _unidirectionalRoots[11]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[13], _unidirectionalRoots[13]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[13], _unidirectionalRoots[15]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[13], _unidirectionalRoots[18]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[14], _unidirectionalRoots[14]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[14], _unidirectionalRoots[15]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[14], _unidirectionalRoots[19]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[15], _unidirectionalRoots[10]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[16], _unidirectionalRoots[10]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[16], _unidirectionalRoots[16]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[17], _unidirectionalRoots[11]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[17], _unidirectionalRoots[14]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[17], _unidirectionalRoots[19]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[18], _unidirectionalRoots[10]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[18], _unidirectionalRoots[11]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[18], _unidirectionalRoots[15]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[18], _unidirectionalRoots[18]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[19], _unidirectionalRoots[16]), + CreateUnidirectionalJoinOneToBranch(context, _unidirectionalOnes[19], _unidirectionalRoots[18]) + }; + + private static UnidirectionalJoinOneToBranch CreateUnidirectionalJoinOneToBranch( + ManyToManyContext context, + UnidirectionalEntityOne one, + UnidirectionalEntityRoot branch) + => CreateInstance( + context?.Set(), (e, p) => + { + e.UnidirectionalEntityOneId = context?.Entry(one).Property(e => e.Id).CurrentValue ?? one.Id; + e.UnidirectionalEntityBranchId = context?.Entry(branch).Property(e => e.Id).CurrentValue ?? branch.Id; + }); + + private UnidirectionalJoinOneToThreePayloadFull[] CreateUnidirectionalJoinOneToThreePayloadFulls(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[0], _unidirectionalThrees[1], "Ira Watts"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[0], _unidirectionalThrees[5], "Harold May"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[0], _unidirectionalThrees[8], "Freda Vaughn"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[0], _unidirectionalThrees[12], "Pedro Mccarthy"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[0], _unidirectionalThrees[16], "Elaine Simon"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[1], _unidirectionalThrees[8], "Melvin Maldonado"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[1], _unidirectionalThrees[10], "Lora George"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[1], _unidirectionalThrees[12], "Joey Cohen"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[1], _unidirectionalThrees[13], "Erik Carroll"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[1], _unidirectionalThrees[15], "April Rodriguez"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[2], _unidirectionalThrees[4], "Gerardo Colon"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[2], _unidirectionalThrees[11], "Alexander Willis"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[2], _unidirectionalThrees[15], "Laura Wheeler"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[2], _unidirectionalThrees[18], "Lester Summers"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[3], _unidirectionalThrees[1], "Raquel Curry"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[3], _unidirectionalThrees[3], "Steven Fisher"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[3], _unidirectionalThrees[10], "Casey Williams"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[3], _unidirectionalThrees[12], "Lauren Clayton"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[3], _unidirectionalThrees[18], "Maureen Weber"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[4], _unidirectionalThrees[3], "Joyce Ford"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[4], _unidirectionalThrees[5], "Willie Mccormick"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[4], _unidirectionalThrees[8], "Geraldine Jackson"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[6], _unidirectionalThrees[0], "Victor Aguilar"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[6], _unidirectionalThrees[3], "Cathy Allen"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[6], _unidirectionalThrees[8], "Edwin Burke"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[6], _unidirectionalThrees[9], "Eugene Flores"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[6], _unidirectionalThrees[10], "Ginger Patton"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[6], _unidirectionalThrees[11], "Israel Mitchell"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[6], _unidirectionalThrees[17], "Joy Francis"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[7], _unidirectionalThrees[0], "Orville Parker"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[7], _unidirectionalThrees[2], "Alyssa Mann"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[7], _unidirectionalThrees[3], "Hugh Daniel"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[7], _unidirectionalThrees[12], "Kim Craig"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[7], _unidirectionalThrees[13], "Lucille Moreno"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[7], _unidirectionalThrees[16], "Virgil Drake"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[7], _unidirectionalThrees[17], "Josephine Dawson"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[7], _unidirectionalThrees[19], "Milton Huff"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[8], _unidirectionalThrees[1], "Jody Clarke"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[8], _unidirectionalThrees[8], "Elisa Cooper"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[8], _unidirectionalThrees[10], "Grace Owen"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[8], _unidirectionalThrees[11], "Donald Welch"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[8], _unidirectionalThrees[14], "Marian Day"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[8], _unidirectionalThrees[16], "Cory Cortez"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[1], "Chad Rowe"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[2], "Simon Reyes"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[3], "Shari Jensen"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[7], "Ricky Bradley"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[9], "Debra Gibbs"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[10], "Everett Mckenzie"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[13], "Kirk Graham"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[15], "Paulette Adkins"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[17], "Raul Holloway"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[9], _unidirectionalThrees[18], "Danielle Ross"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[10], _unidirectionalThrees[0], "Frank Garner"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[10], _unidirectionalThrees[5], "Stella Thompson"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[10], _unidirectionalThrees[7], "Peggy Wagner"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[10], _unidirectionalThrees[8], "Geneva Holmes"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[10], _unidirectionalThrees[9], "Ignacio Black"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[10], _unidirectionalThrees[12], "Phillip Wells"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[10], _unidirectionalThrees[13], "Hubert Lambert"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[10], _unidirectionalThrees[18], "Courtney Gregory"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[11], _unidirectionalThrees[1], "Esther Carter"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[5], "Thomas Benson"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[8], "Kara Baldwin"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[9], "Yvonne Sparks"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[10], "Darin Mathis"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[11], "Glenda Castillo"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[12], "Larry Walters"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[14], "Meredith Yates"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[15], "Rosemarie Henry"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[12], _unidirectionalThrees[17], "Nora Leonard"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[13], _unidirectionalThrees[16], "Corey Delgado"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[13], _unidirectionalThrees[17], "Kari Strickland"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[14], _unidirectionalThrees[7], "Joann Stanley"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[14], _unidirectionalThrees[10], "Camille Gordon"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[14], _unidirectionalThrees[13], "Flora Anderson"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[14], _unidirectionalThrees[14], "Wilbur Soto"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[14], _unidirectionalThrees[17], "Shirley Andrews"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[14], _unidirectionalThrees[19], "Marcus Mcguire"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[15], _unidirectionalThrees[0], "Saul Dixon"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[15], _unidirectionalThrees[5], "Cynthia Hart"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[15], _unidirectionalThrees[9], "Elbert Spencer"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[15], _unidirectionalThrees[12], "Darrell Norris"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[15], _unidirectionalThrees[13], "Jamie Kelley"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[15], _unidirectionalThrees[14], "Francis Briggs"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[15], _unidirectionalThrees[15], "Lindsey Morris"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[16], _unidirectionalThrees[1], "James Castro"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[16], _unidirectionalThrees[4], "Carlos Chavez"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[16], _unidirectionalThrees[6], "Janis Valdez"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[16], _unidirectionalThrees[12], "Alfredo Bowen"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[16], _unidirectionalThrees[13], "Viola Torres"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[16], _unidirectionalThrees[14], "Dianna Lowe"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[17], _unidirectionalThrees[2], "Craig Howell"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[17], _unidirectionalThrees[6], "Sandy Curtis"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[17], _unidirectionalThrees[11], "Alonzo Pierce"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[17], _unidirectionalThrees[17], "Albert Harper"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[1], "Frankie Baker"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[4], "Candace Tucker"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[5], "Willis Christensen"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[6], "Juan Joseph"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[9], "Thelma Sanders"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[10], "Kerry West"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[14], "Sheri Castro"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[15], "Mark Schultz"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[16], "Priscilla Summers"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[18], _unidirectionalThrees[19], "Allan Valdez"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[2], "Bill Peters"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[4], "Cora Stone"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[5], "Frankie Pope"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[9], "Christian Young"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[10], "Shari Brewer"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[11], "Antonia Wolfe"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[13], "Lawrence Matthews"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[17], "Van Hubbard"), + CreateUnidirectionalJoinOneToThreePayloadFull(context, _unidirectionalOnes[19], _unidirectionalThrees[19], "Lindsay Pena") + }; + + private static UnidirectionalJoinOneToThreePayloadFull CreateUnidirectionalJoinOneToThreePayloadFull( + ManyToManyContext context, + UnidirectionalEntityOne one, + UnidirectionalEntityThree three, + string payload) + => CreateInstance( + context?.Set(), (e, p) => + { + e.One = one; + e.Three = three; + e.Payload = payload; + }); + + private UnidirectionalJoinOneToTwo[] CreateUnidirectionalJoinOneToTwos(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[1]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[2]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[3]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[4]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[5]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[6]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[7]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[8]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[9]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[10]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[11]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[12]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[13]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[14]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[15]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[16]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[17]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[18]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[2]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[4]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[6]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[8]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[10]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[12]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[14]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[16]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[18]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[3]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[6]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[9]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[12]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[15]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[18]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[3], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[3], _unidirectionalTwos[4]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[3], _unidirectionalTwos[8]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[3], _unidirectionalTwos[12]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[3], _unidirectionalTwos[16]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[5]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[10]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[15]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[5], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[5], _unidirectionalTwos[6]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[5], _unidirectionalTwos[12]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[5], _unidirectionalTwos[18]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[6], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[6], _unidirectionalTwos[7]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[6], _unidirectionalTwos[14]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[7], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[7], _unidirectionalTwos[8]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[7], _unidirectionalTwos[16]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[8], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[8], _unidirectionalTwos[9]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[8], _unidirectionalTwos[18]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[9], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[9], _unidirectionalTwos[10]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[18]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[17]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[16]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[15]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[14]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[13]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[12]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[11]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[10]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[9]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[8]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[7]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[6]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[5]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[4]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[3]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[2]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[1]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[0]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[16]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[13]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[10]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[7]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[4]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[1]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[15]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[11]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[7]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[3]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[13], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[13], _unidirectionalTwos[14]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[13], _unidirectionalTwos[9]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[13], _unidirectionalTwos[4]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[14], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[14], _unidirectionalTwos[13]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[14], _unidirectionalTwos[7]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[14], _unidirectionalTwos[1]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[15], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[15], _unidirectionalTwos[12]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[15], _unidirectionalTwos[5]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[16], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[16], _unidirectionalTwos[11]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[16], _unidirectionalTwos[3]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[17], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[17], _unidirectionalTwos[10]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[17], _unidirectionalTwos[1]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[18], _unidirectionalTwos[19]), + CreateUnidirectionalJoinOneToTwo(context, _unidirectionalOnes[18], _unidirectionalTwos[9]) + }; + + private static UnidirectionalJoinOneToTwo CreateUnidirectionalJoinOneToTwo( + ManyToManyContext context, + UnidirectionalEntityOne one, + UnidirectionalEntityTwo two) + => CreateInstance( + context?.Set(), (e, p) => + { + e.OneId = context?.Entry(one).Property(e => e.Id).CurrentValue ?? one.Id; + e.TwoId = context?.Entry(two).Property(e => e.Id).CurrentValue ?? two.Id; + }); + + private UnidirectionalJoinThreeToCompositeKeyFull[] CreateUnidirectionalJoinThreeToCompositeKeyFulls(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[0], _unidirectionalCompositeKeys[5]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[1], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[1], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[1], _unidirectionalCompositeKeys[19]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[2], _unidirectionalCompositeKeys[5]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[2], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[2], _unidirectionalCompositeKeys[19]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[4], _unidirectionalCompositeKeys[11]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[4], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[4], _unidirectionalCompositeKeys[17]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[5], _unidirectionalCompositeKeys[5]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[6], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[6], _unidirectionalCompositeKeys[8]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[7], _unidirectionalCompositeKeys[10]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[7], _unidirectionalCompositeKeys[18]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[8], _unidirectionalCompositeKeys[8]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[8], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[9], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[10], _unidirectionalCompositeKeys[6]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[10], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[11], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[11], _unidirectionalCompositeKeys[10]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[11], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[12], _unidirectionalCompositeKeys[5]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[12], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[12], _unidirectionalCompositeKeys[13]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[12], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[13], _unidirectionalCompositeKeys[9]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[13], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[13], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[14], _unidirectionalCompositeKeys[9]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[14], _unidirectionalCompositeKeys[13]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[14], _unidirectionalCompositeKeys[18]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[15], _unidirectionalCompositeKeys[4]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[15], _unidirectionalCompositeKeys[6]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[15], _unidirectionalCompositeKeys[18]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[16], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[16], _unidirectionalCompositeKeys[9]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[17], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[18], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[18], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[18], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[18], _unidirectionalCompositeKeys[19]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[19], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinThreeToCompositeKeyFull(context, _unidirectionalThrees[19], _unidirectionalCompositeKeys[6]) + }; + + private static UnidirectionalJoinThreeToCompositeKeyFull CreateUnidirectionalJoinThreeToCompositeKeyFull( + ManyToManyContext context, + UnidirectionalEntityThree three, + UnidirectionalEntityCompositeKey composite) + => CreateInstance( + context?.Set(), (e, p) => + { + e.Three = three; + e.Composite = composite; + }); + + private UnidirectionalJoinTwoToThree[] CreateUnidirectionalJoinTwoToThrees(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[0], _unidirectionalThrees[1]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[0], _unidirectionalThrees[2]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[0], _unidirectionalThrees[12]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[0], _unidirectionalThrees[17]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[1], _unidirectionalThrees[0]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[1], _unidirectionalThrees[8]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[1], _unidirectionalThrees[14]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[2], _unidirectionalThrees[10]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[2], _unidirectionalThrees[16]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[3], _unidirectionalThrees[1]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[3], _unidirectionalThrees[4]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[3], _unidirectionalThrees[10]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[4], _unidirectionalThrees[3]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[4], _unidirectionalThrees[4]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[5], _unidirectionalThrees[2]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[5], _unidirectionalThrees[9]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[5], _unidirectionalThrees[15]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[5], _unidirectionalThrees[17]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[6], _unidirectionalThrees[11]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[6], _unidirectionalThrees[14]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[6], _unidirectionalThrees[19]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[7], _unidirectionalThrees[0]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[7], _unidirectionalThrees[2]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[7], _unidirectionalThrees[19]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[8], _unidirectionalThrees[2]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[8], _unidirectionalThrees[12]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[8], _unidirectionalThrees[18]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[9], _unidirectionalThrees[16]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[10], _unidirectionalThrees[5]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[10], _unidirectionalThrees[6]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[10], _unidirectionalThrees[7]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[10], _unidirectionalThrees[12]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[11], _unidirectionalThrees[8]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[12], _unidirectionalThrees[0]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[12], _unidirectionalThrees[10]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[12], _unidirectionalThrees[18]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[13], _unidirectionalThrees[1]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[14], _unidirectionalThrees[16]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[15], _unidirectionalThrees[2]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[15], _unidirectionalThrees[15]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[17], _unidirectionalThrees[0]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[17], _unidirectionalThrees[4]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[17], _unidirectionalThrees[9]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[18], _unidirectionalThrees[4]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[18], _unidirectionalThrees[15]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[18], _unidirectionalThrees[17]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[19], _unidirectionalThrees[5]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[19], _unidirectionalThrees[9]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[19], _unidirectionalThrees[11]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[19], _unidirectionalThrees[15]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[19], _unidirectionalThrees[16]), + CreateUnidirectionalJoinTwoToThree(context, _unidirectionalTwos[19], _unidirectionalThrees[17]) + }; + + private static UnidirectionalJoinTwoToThree CreateUnidirectionalJoinTwoToThree( + ManyToManyContext context, + UnidirectionalEntityTwo two, + UnidirectionalEntityThree three) + => CreateInstance( + context?.Set(), (e, p) => + { + e.Two = two; + e.Three = three; + }); + + private Dictionary[] CreateUnidirectionalEntityOneEntityTwos(ManyToManyContext context) + => new[] + { + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[2]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[0], _unidirectionalTwos[15]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[2]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[9]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[1], _unidirectionalTwos[17]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[9]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[10]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[2], _unidirectionalTwos[15]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[1]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[4]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[6]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[8]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[4], _unidirectionalTwos[13]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[5], _unidirectionalTwos[11]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[6], _unidirectionalTwos[2]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[6], _unidirectionalTwos[15]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[6], _unidirectionalTwos[16]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[7], _unidirectionalTwos[18]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[8], _unidirectionalTwos[8]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[8], _unidirectionalTwos[10]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[9], _unidirectionalTwos[5]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[9], _unidirectionalTwos[16]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[9], _unidirectionalTwos[19]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[16]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[10], _unidirectionalTwos[17]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[5]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[11], _unidirectionalTwos[18]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[6]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[7]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[8]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[12], _unidirectionalTwos[12]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[13], _unidirectionalTwos[3]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[13], _unidirectionalTwos[8]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[13], _unidirectionalTwos[18]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[14], _unidirectionalTwos[9]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[15], _unidirectionalTwos[0]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[15], _unidirectionalTwos[6]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[15], _unidirectionalTwos[18]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[16], _unidirectionalTwos[7]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[16], _unidirectionalTwos[14]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[17], _unidirectionalTwos[3]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[17], _unidirectionalTwos[12]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[17], _unidirectionalTwos[13]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[18], _unidirectionalTwos[3]), + CreateUnidirectionalEntityOneEntityTwo(context, _unidirectionalOnes[18], _unidirectionalTwos[13]) + }; + + private static Dictionary CreateUnidirectionalEntityOneEntityTwo( + ManyToManyContext context, + UnidirectionalEntityOne one, + UnidirectionalEntityTwo two) + => CreateInstance( + context?.Set>("UnidirectionalEntityOneUnidirectionalEntityTwo"), (e, p) => + { + e["UnidirectionalEntityOneId"] = context?.Entry(one).Property(e => e.Id).CurrentValue ?? one.Id; + e["TwoSkipSharedId"] = context?.Entry(two).Property(e => e.Id).CurrentValue ?? two.Id; + }); + + private Dictionary[] CreateUnidirectionalJoinOneToThreePayloadFullShareds(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[2], _unidirectionalThrees[0], "Capbrough"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[2], _unidirectionalThrees[1], "East Eastdol"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[2], _unidirectionalThrees[3], "Southingville"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[2], _unidirectionalThrees[8], "Goldbrough"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[3], _unidirectionalThrees[4], "Readingworth"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[3], _unidirectionalThrees[17], "Skillpool"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[4], _unidirectionalThrees[0], "Lawgrad"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[4], _unidirectionalThrees[3], "Kettleham Park"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[4], _unidirectionalThrees[8], "Sayford Park"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[4], _unidirectionalThrees[15], "Hamstead"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[5], _unidirectionalThrees[10], "North Starside"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[5], _unidirectionalThrees[12], "Goldfolk"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[6], _unidirectionalThrees[3], "Winstead"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[7], _unidirectionalThrees[10], "Transworth"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[7], _unidirectionalThrees[17], "Parkpool"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[7], _unidirectionalThrees[18], "Fishham"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[9], _unidirectionalThrees[0], "Passmouth"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[9], _unidirectionalThrees[4], "Valenfield"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[9], _unidirectionalThrees[19], "Passford Park"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[10], _unidirectionalThrees[9], "Chatfield"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[11], _unidirectionalThrees[10], "Hosview"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[11], _unidirectionalThrees[16], "Dodgewich"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[12], _unidirectionalThrees[2], "Skillhampton"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[12], _unidirectionalThrees[13], "Hardcaster"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[12], _unidirectionalThrees[15], "Hollowmouth"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[13], _unidirectionalThrees[5], "Cruxcaster"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[13], _unidirectionalThrees[10], "Elcaster"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[13], _unidirectionalThrees[16], "Clambrough"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[14], _unidirectionalThrees[9], "Millwich"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[14], _unidirectionalThrees[12], "Hapcester"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[15], _unidirectionalThrees[6], "Sanddol Beach"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[15], _unidirectionalThrees[12], "Hamcaster"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[16], _unidirectionalThrees[8], "New Foxbrough"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[16], _unidirectionalThrees[12], "Chatpool"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[17], _unidirectionalThrees[7], "Duckworth"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[17], _unidirectionalThrees[11], "Snowham"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[17], _unidirectionalThrees[12], "Bannview Island"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[19], _unidirectionalThrees[3], "Rockbrough"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[19], _unidirectionalThrees[4], "Sweetfield"), + CreateUnidirectionalJoinOneToThreePayloadFullShared(context, _unidirectionalOnes[19], _unidirectionalThrees[15], "Bayburgh Hills") + }; + + private static Dictionary CreateUnidirectionalJoinOneToThreePayloadFullShared( + ManyToManyContext context, + UnidirectionalEntityOne one, + UnidirectionalEntityThree three, + string payload) + => CreateInstance( + context?.Set>("JoinOneToThreePayloadFullShared"), (e, p) => + { + e["OneId"] = context?.Entry(one).Property(e => e.Id).CurrentValue ?? one.Id; + e["ThreeId"] = context?.Entry(three).Property(e => e.Id).CurrentValue ?? three.Id; + e["Payload"] = payload; + }); + + private Dictionary[] CreateUnidirectionalJoinTwoSelfShareds(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[0], _unidirectionalTwos[8]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[0], _unidirectionalTwos[9]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[0], _unidirectionalTwos[10]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[0], _unidirectionalTwos[17]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[2], _unidirectionalTwos[1]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[2], _unidirectionalTwos[4]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[2], _unidirectionalTwos[7]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[2], _unidirectionalTwos[17]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[2], _unidirectionalTwos[18]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[3], _unidirectionalTwos[10]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[4], _unidirectionalTwos[7]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[5], _unidirectionalTwos[17]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[7], _unidirectionalTwos[1]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[7], _unidirectionalTwos[13]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[7], _unidirectionalTwos[14]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[7], _unidirectionalTwos[19]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[8], _unidirectionalTwos[3]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[8], _unidirectionalTwos[13]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[9], _unidirectionalTwos[4]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[11], _unidirectionalTwos[12]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[11], _unidirectionalTwos[13]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[12], _unidirectionalTwos[13]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[12], _unidirectionalTwos[17]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[12], _unidirectionalTwos[18]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[15], _unidirectionalTwos[5]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[16], _unidirectionalTwos[8]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[16], _unidirectionalTwos[18]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[16], _unidirectionalTwos[19]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[17], _unidirectionalTwos[1]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[17], _unidirectionalTwos[4]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[17], _unidirectionalTwos[15]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[17], _unidirectionalTwos[16]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[18], _unidirectionalTwos[1]), + CreateUnidirectionalJoinTwoSelfShared(context, _unidirectionalTwos[19], _unidirectionalTwos[3]) + }; + + private static Dictionary CreateUnidirectionalJoinTwoSelfShared( + ManyToManyContext context, + UnidirectionalEntityTwo left, + UnidirectionalEntityTwo right) + => CreateInstance( + context?.Set>("UnidirectionalEntityTwoUnidirectionalEntityTwo"), (e, p) => + { + e["UnidirectionalEntityTwoId"] = context?.Entry(left).Property(e => e.Id).CurrentValue ?? left.Id; + e["SelfSkipSharedRightId"] = context?.Entry(right).Property(e => e.Id).CurrentValue ?? right.Id; + }); + + private Dictionary[] CreateUnidirectionalJoinTwoToCompositeKeyShareds(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[0], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[0], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[0], _unidirectionalCompositeKeys[4]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[1], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[2], _unidirectionalCompositeKeys[5]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[3], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[3], _unidirectionalCompositeKeys[18]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[5], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[5], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[6], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[8], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[8], _unidirectionalCompositeKeys[8]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[9], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[9], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[9], _unidirectionalCompositeKeys[17]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[10], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[10], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[11], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[11], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[11], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[12], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[12], _unidirectionalCompositeKeys[6]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[12], _unidirectionalCompositeKeys[16]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[14], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[15], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[15], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[15], _unidirectionalCompositeKeys[18]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[16], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[16], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[16], _unidirectionalCompositeKeys[13]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[16], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[18], _unidirectionalCompositeKeys[4]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[19], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[19], _unidirectionalCompositeKeys[4]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[19], _unidirectionalCompositeKeys[5]), + CreateUnidirectionalJoinTwoToCompositeKeyShared(context, _unidirectionalTwos[19], _unidirectionalCompositeKeys[13]) + }; + + private static Dictionary CreateUnidirectionalJoinTwoToCompositeKeyShared( + ManyToManyContext context, + UnidirectionalEntityTwo two, + UnidirectionalEntityCompositeKey composite) + => CreateInstance( + context?.Set>("UnidirectionalEntityCompositeKeyUnidirectionalEntityTwo"), (e, p) => + { + e["TwoSkipSharedId"] = context?.Entry(two).Property(e => e.Id).CurrentValue ?? two.Id; + e["UnidirectionalEntityCompositeKeyKey1"] = context?.Entry(composite).Property(e => e.Key1).CurrentValue ?? composite.Key1; + e["UnidirectionalEntityCompositeKeyKey2"] = composite.Key2; + e["UnidirectionalEntityCompositeKeyKey3"] = composite.Key3; + }); + + private Dictionary[] CreateUnidirectionalEntityRootEntityThrees(ManyToManyContext context) + => new[] + { + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[0], _unidirectionalRoots[6]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[0], _unidirectionalRoots[7]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[0], _unidirectionalRoots[14]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[1], _unidirectionalRoots[3]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[1], _unidirectionalRoots[15]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[2], _unidirectionalRoots[11]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[2], _unidirectionalRoots[13]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[2], _unidirectionalRoots[19]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[4], _unidirectionalRoots[13]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[4], _unidirectionalRoots[14]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[4], _unidirectionalRoots[15]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[5], _unidirectionalRoots[16]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[6], _unidirectionalRoots[0]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[6], _unidirectionalRoots[5]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[6], _unidirectionalRoots[12]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[6], _unidirectionalRoots[19]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[7], _unidirectionalRoots[9]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[9], _unidirectionalRoots[2]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[9], _unidirectionalRoots[7]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[12], _unidirectionalRoots[4]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[13], _unidirectionalRoots[0]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[13], _unidirectionalRoots[13]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[15], _unidirectionalRoots[4]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[15], _unidirectionalRoots[6]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[16], _unidirectionalRoots[13]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[17], _unidirectionalRoots[5]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[17], _unidirectionalRoots[18]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[18], _unidirectionalRoots[10]), + CreateUnidirectionalEntityRootEntityThree(context, _unidirectionalThrees[19], _unidirectionalRoots[13]) + }; + + private static Dictionary CreateUnidirectionalEntityRootEntityThree( + ManyToManyContext context, + UnidirectionalEntityThree three, + UnidirectionalEntityRoot root) + => CreateInstance( + context?.Set>("UnidirectionalEntityRootUnidirectionalEntityThree"), (e, p) => + { + e["ThreeSkipSharedId"] = context?.Entry(three).Property(e => e.Id).CurrentValue ?? three.Id; + e["UnidirectionalEntityRootId"] = context?.Entry(root).Property(e => e.Id).CurrentValue ?? root.Id; + }); + + private Dictionary[] CreateUnidirectionalJoinCompositeKeyToRootShareds(ManyToManyContext context) + => new[] + { + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[5], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[8], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[19], _unidirectionalCompositeKeys[0]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[0], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[1], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[3], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[5], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[10], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[17], _unidirectionalCompositeKeys[1]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[3], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[13], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[15], _unidirectionalCompositeKeys[2]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[1], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[2], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[3], _unidirectionalCompositeKeys[3]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[1], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[7], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[15], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[17], _unidirectionalCompositeKeys[7]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[6], _unidirectionalCompositeKeys[8]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[7], _unidirectionalCompositeKeys[8]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[18], _unidirectionalCompositeKeys[8]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[2], _unidirectionalCompositeKeys[9]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[11], _unidirectionalCompositeKeys[9]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[17], _unidirectionalCompositeKeys[9]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[1], _unidirectionalCompositeKeys[10]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[3], _unidirectionalCompositeKeys[10]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[4], _unidirectionalCompositeKeys[10]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[6], _unidirectionalCompositeKeys[11]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[2], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[7], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[13], _unidirectionalCompositeKeys[12]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[3], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[10], _unidirectionalCompositeKeys[14]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[0], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[6], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[14], _unidirectionalCompositeKeys[15]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[0], _unidirectionalCompositeKeys[18]), + CreateUnidirectionalJoinCompositeKeyToRootShared(context, _unidirectionalRoots[5], _unidirectionalCompositeKeys[19]) + }; + + private static Dictionary CreateUnidirectionalJoinCompositeKeyToRootShared( + ManyToManyContext context, + UnidirectionalEntityRoot root, + UnidirectionalEntityCompositeKey composite) + => CreateInstance( + context?.Set>("UnidirectionalEntityCompositeKeyUnidirectionalEntityRoot"), (e, p) => + { + e["RootSkipSharedId"] = context?.Entry(root).Property(e => e.Id).CurrentValue ?? root.Id; + e["UnidirectionalEntityCompositeKeyKey1"] = context?.Entry(composite).Property(e => e.Key1).CurrentValue ?? composite.Key1; + e["UnidirectionalEntityCompositeKeyKey2"] = composite.Key2; + e["UnidirectionalEntityCompositeKeyKey3"] = composite.Key3; + }); + + private static ICollection CreateCollection(bool proxy) + => proxy ? new ObservableCollection() : new List(); + private static TEntity CreateInstance(DbSet set, Action configureEntity) where TEntity : class, new() { diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityBranch.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityBranch.cs new file mode 100644 index 00000000000..3cba8395aed --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityBranch.cs @@ -0,0 +1,9 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalEntityBranch : UnidirectionalEntityRoot +{ + public virtual long Number { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityCompositeKey.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityCompositeKey.cs new file mode 100644 index 00000000000..3c0f3aa546c --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityCompositeKey.cs @@ -0,0 +1,19 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalEntityCompositeKey +{ + public virtual int Key1 { get; set; } + public virtual string Key2 { get; set; } + public virtual DateTime Key3 { get; set; } + + public virtual string Name { get; set; } + + public virtual ICollection TwoSkipShared { get; set; } + public virtual ICollection ThreeSkipFull { get; set; } + public virtual ICollection JoinThreeFull { get; set; } + public virtual ICollection RootSkipShared { get; set; } + public virtual ICollection JoinLeafFull { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityLeaf.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityLeaf.cs new file mode 100644 index 00000000000..97c0700deba --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityLeaf.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalEntityLeaf : UnidirectionalEntityBranch +{ + public virtual bool? IsGreen { get; set; } + + public virtual ICollection CompositeKeySkipFull { get; set; } + public virtual ICollection JoinCompositeKeyFull { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityOne.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityOne.cs new file mode 100644 index 00000000000..7ca25e127f0 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityOne.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations.Schema; + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalEntityOne +{ + public virtual int Id { get; set; } + public virtual string Name { get; set; } + + public virtual UnidirectionalEntityTwo Reference { get; set; } + public virtual ICollection Collection { get; set; } + public virtual ICollection TwoSkip { get; set; } + public virtual ICollection JoinThreePayloadFull { get; set; } + + public virtual ICollection TwoSkipShared { get; set; } + + public virtual ICollection ThreeSkipPayloadFullShared { get; set; } + public virtual ICollection> JoinThreePayloadFullShared { get; set; } + public virtual ICollection SelfSkipPayloadLeft { get; set; } + public virtual ICollection JoinSelfPayloadLeft { get; set; } + public virtual ICollection JoinSelfPayloadRight { get; set; } + public virtual ICollection BranchSkip { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityRoot.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityRoot.cs new file mode 100644 index 00000000000..464b70a1588 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityRoot.cs @@ -0,0 +1,11 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalEntityRoot +{ + public virtual int Id { get; set; } + public virtual string Name { get; set; } + public virtual ICollection ThreeSkipShared { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityThree.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityThree.cs new file mode 100644 index 00000000000..80395714a69 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityThree.cs @@ -0,0 +1,24 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations.Schema; + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalEntityThree +{ + public virtual int Id { get; set; } + public virtual string Name { get; set; } + + public virtual int? ReferenceInverseId { get; set; } + public virtual UnidirectionalEntityTwo ReferenceInverse { get; set; } + + public virtual int? CollectionInverseId { get; set; } + public virtual UnidirectionalEntityTwo CollectionInverse { get; set; } + + public virtual ICollection JoinOnePayloadFull { get; set; } + public virtual ICollection TwoSkipFull { get; set; } + public virtual ICollection JoinTwoFull { get; set; } + public virtual ICollection> JoinOnePayloadFullShared { get; set; } + public virtual ICollection JoinCompositeKeyFull { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityTwo.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityTwo.cs new file mode 100644 index 00000000000..f1261ff39bb --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalEntityTwo.cs @@ -0,0 +1,26 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.ComponentModel.DataAnnotations.Schema; + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalEntityTwo +{ + public virtual int Id { get; set; } + public virtual string Name { get; set; } + + public virtual int? ReferenceInverseId { get; set; } + public virtual UnidirectionalEntityOne ReferenceInverse { get; set; } + + public virtual int? CollectionInverseId { get; set; } + public virtual UnidirectionalEntityOne CollectionInverse { get; set; } + + public virtual UnidirectionalEntityThree Reference { get; set; } + public virtual ICollection Collection { get; set; } + public virtual ICollection JoinThreeFull { get; set; } + public virtual ICollection SelfSkipSharedRight { get; set; } + + public virtual int? ExtraId { get; set; } + public virtual UnidirectionalJoinOneToTwoExtra Extra { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalGeneratedKeysLeft.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalGeneratedKeysLeft.cs new file mode 100644 index 00000000000..00b06f70411 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalGeneratedKeysLeft.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.ObjectModel; + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalGeneratedKeysLeft +{ + public virtual int Id { get; set; } + public virtual string Name { get; set; } + + public virtual ICollection Rights { get; } = new ObservableCollection(); +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalGeneratedKeysRight.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalGeneratedKeysRight.cs new file mode 100644 index 00000000000..5447624ba5c --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalGeneratedKeysRight.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.ObjectModel; + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalGeneratedKeysRight +{ + public virtual int Id { get; set; } + public virtual string Name { get; set; } + + public virtual ICollection Lefts { get; } = new ObservableCollection(); +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinCompositeKeyToLeaf.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinCompositeKeyToLeaf.cs new file mode 100644 index 00000000000..313b56bb2e1 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinCompositeKeyToLeaf.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalJoinCompositeKeyToLeaf +{ + public virtual int CompositeId1 { get; set; } + public virtual string CompositeId2 { get; set; } + public virtual DateTime CompositeId3 { get; set; } + public virtual int LeafId { get; set; } + + public virtual UnidirectionalEntityCompositeKey Composite { get; set; } + public virtual UnidirectionalEntityLeaf Leaf { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneSelfPayload.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneSelfPayload.cs new file mode 100644 index 00000000000..2a5f17aa920 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneSelfPayload.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalJoinOneSelfPayload +{ + public virtual int LeftId { get; set; } + public virtual int RightId { get; set; } + public virtual DateTime Payload { get; set; } + public virtual UnidirectionalEntityOne Right { get; set; } + public virtual UnidirectionalEntityOne Left { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToBranch.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToBranch.cs new file mode 100644 index 00000000000..d213553e7f2 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToBranch.cs @@ -0,0 +1,10 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalJoinOneToBranch +{ + public virtual int UnidirectionalEntityOneId { get; set; } + public virtual int UnidirectionalEntityBranchId { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToThreePayloadFull.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToThreePayloadFull.cs new file mode 100644 index 00000000000..5cf13ce5056 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToThreePayloadFull.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalJoinOneToThreePayloadFull +{ + public virtual int OneId { get; set; } + public virtual int ThreeId { get; set; } + public virtual UnidirectionalEntityOne One { get; set; } + public virtual UnidirectionalEntityThree Three { get; set; } + + public virtual string Payload { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToTwo.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToTwo.cs new file mode 100644 index 00000000000..f71fe0cb9e4 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToTwo.cs @@ -0,0 +1,13 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalJoinOneToTwo +{ + public virtual int OneId { get; set; } + public virtual int TwoId { get; set; } + + public virtual UnidirectionalEntityOne One { get; set; } + public virtual UnidirectionalEntityTwo Two { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToTwoExtra.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToTwoExtra.cs new file mode 100644 index 00000000000..def58b12450 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinOneToTwoExtra.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalJoinOneToTwoExtra +{ + public virtual int Id { get; set; } + public virtual string Name { get; set; } + + public virtual ICollection JoinEntities { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinThreeToCompositeKeyFull.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinThreeToCompositeKeyFull.cs new file mode 100644 index 00000000000..7eea5b8b519 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinThreeToCompositeKeyFull.cs @@ -0,0 +1,16 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalJoinThreeToCompositeKeyFull +{ + public virtual Guid Id { get; set; } + public virtual int ThreeId { get; set; } + public virtual int CompositeId1 { get; set; } + public virtual string CompositeId2 { get; set; } + public virtual DateTime CompositeId3 { get; set; } + + public virtual UnidirectionalEntityThree Three { get; set; } + public virtual UnidirectionalEntityCompositeKey Composite { get; set; } +} diff --git a/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinTwoToThree.cs b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinTwoToThree.cs new file mode 100644 index 00000000000..ba077be1381 --- /dev/null +++ b/test/EFCore.Specification.Tests/TestModels/ManyToManyModel/UnidirectionalJoinTwoToThree.cs @@ -0,0 +1,12 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +public class UnidirectionalJoinTwoToThree +{ + public virtual int TwoId { get; set; } + public virtual int ThreeId { get; set; } + public virtual UnidirectionalEntityTwo Two { get; set; } + public virtual UnidirectionalEntityThree Three { get; set; } +} diff --git a/test/EFCore.Specification.Tests/UnidirectionalManyToManyLoadTestBase.cs b/test/EFCore.Specification.Tests/UnidirectionalManyToManyLoadTestBase.cs new file mode 100644 index 00000000000..9a7055e78df --- /dev/null +++ b/test/EFCore.Specification.Tests/UnidirectionalManyToManyLoadTestBase.cs @@ -0,0 +1,1037 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.ObjectModel; +using Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +namespace Microsoft.EntityFrameworkCore; + +public abstract partial class ManyToManyLoadTestBase +{ + [ConditionalTheory] + [InlineData(EntityState.Unchanged, QueryTrackingBehavior.TrackAll, true)] + [InlineData(EntityState.Unchanged, QueryTrackingBehavior.TrackAll, false)] + [InlineData(EntityState.Modified, QueryTrackingBehavior.TrackAll, true)] + [InlineData(EntityState.Modified, QueryTrackingBehavior.TrackAll, false)] + [InlineData(EntityState.Deleted, QueryTrackingBehavior.TrackAll, true)] + [InlineData(EntityState.Deleted, QueryTrackingBehavior.TrackAll, false)] + [InlineData(EntityState.Unchanged, QueryTrackingBehavior.NoTracking, true)] + [InlineData(EntityState.Unchanged, QueryTrackingBehavior.NoTracking, false)] + [InlineData(EntityState.Modified, QueryTrackingBehavior.NoTracking, true)] + [InlineData(EntityState.Modified, QueryTrackingBehavior.NoTracking, false)] + [InlineData(EntityState.Deleted, QueryTrackingBehavior.NoTracking, true)] + [InlineData(EntityState.Deleted, QueryTrackingBehavior.NoTracking, false)] + [InlineData(EntityState.Unchanged, QueryTrackingBehavior.NoTrackingWithIdentityResolution, true)] + [InlineData(EntityState.Unchanged, QueryTrackingBehavior.NoTrackingWithIdentityResolution, false)] + [InlineData(EntityState.Modified, QueryTrackingBehavior.NoTrackingWithIdentityResolution, true)] + [InlineData(EntityState.Modified, QueryTrackingBehavior.NoTrackingWithIdentityResolution, false)] + [InlineData(EntityState.Deleted, QueryTrackingBehavior.NoTrackingWithIdentityResolution, true)] + [InlineData(EntityState.Deleted, QueryTrackingBehavior.NoTrackingWithIdentityResolution, false)] + public virtual async Task Load_collection_unidirectional(EntityState state, QueryTrackingBehavior queryTrackingBehavior, bool async) + { + using var context = Fixture.CreateContext(); + + context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + var left = context.Set().Find(3); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection(e => e.TwoSkip); + + context.Entry(left).State = state; + + Assert.False(collectionEntry.IsLoaded); + + if (ExpectLazyLoading) + { + Assert.Equal(7, left.TwoSkip.Count); + } + else + { + if (async) + { + await collectionEntry.LoadAsync(); + } + else + { + collectionEntry.Load(); + } + } + + Assert.True(collectionEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkip) + { + Assert.False(context.Entry(entityTwo).Collection("UnidirectionalEntityOne1").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(7, left.TwoSkip.Count); + foreach (var right in left.TwoSkip) + { + Assert.Contains(left, ((IEnumerable)context.Entry(right).Collection("UnidirectionalEntityOne1").CurrentValue)!); + } + + Assert.Equal(1 + 7 + 7, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_using_Query_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Find(3); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection(e => e.TwoSkipShared); + + context.Entry(left).State = state; + + Assert.False(collectionEntry.IsLoaded); + + var children = async + ? await collectionEntry.Query().ToListAsync() + : collectionEntry.Query().ToList(); + + Assert.False(collectionEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkipShared) + { + Assert.False(context.Entry(entityTwo).Collection("UnidirectionalEntityOne").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(3, left.TwoSkipShared.Count); + foreach (var right in left.TwoSkipShared) + { + Assert.Contains(left, context.Entry(right).Collection("UnidirectionalEntityOne").CurrentValue!.Cast()); + } + + Assert.Equal(children, left.TwoSkipShared.ToList()); + + Assert.Equal(1 + 3 + 3, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Added, false)] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Added, true)] + public virtual void Attached_collections_are_not_marked_as_loaded_unidirectional(EntityState state, bool lazy) + { + using var context = Fixture.CreateContext(); + + context.ChangeTracker.LazyLoadingEnabled = false; + + var left = ExpectLazyLoading + ? context.CreateProxy( + b => + { + b.Id = 7776; + b.TwoSkip = new ObservableCollection { new() { Id = 7777 } }; + b.TwoSkipShared = new ObservableCollection { new() { Id = 7778 } }; + b.SelfSkipPayloadLeft = new ObservableCollection { new() { Id = 7779 } }; + b.BranchSkip = new ObservableCollection { new() { Id = 7781 } }; + b.ThreeSkipPayloadFullShared = new ObservableCollection { new() { Id = 7783 } }; + }) + : new UnidirectionalEntityOne + { + Id = 7776, + TwoSkip = new List { new() { Id = 7777 } }, + TwoSkipShared = new List { new() { Id = 7778 } }, + SelfSkipPayloadLeft = new List { new() { Id = 7779 } }, + BranchSkip = new List { new() { Id = 7781 } }, + ThreeSkipPayloadFullShared = new List { new() { Id = 7783 } } + }; + + var entityThreeCollection = context.Entry(left).Collection("UnidirectionalEntityThree"); + entityThreeCollection.CurrentValue = ExpectLazyLoading + ? new ObservableCollection() + : new List(); + ((ICollection)entityThreeCollection.CurrentValue!).Add(new UnidirectionalEntityThree { Id = 7782 }); + + var entityOneCollection = context.Entry(left).Collection("UnidirectionalEntityOne"); + entityOneCollection.CurrentValue = ExpectLazyLoading + ? new ObservableCollection() + : new List(); + ((ICollection)entityOneCollection.CurrentValue!).Add(new UnidirectionalEntityOne { Id = 7780 }); + + context.Attach(left); + + if (state != EntityState.Unchanged) + { + foreach (var child in left.TwoSkip.Cast() + .Concat(left.TwoSkipShared) + .Concat(left.SelfSkipPayloadLeft) + .Concat(entityOneCollection.CurrentValue!) + .Concat(left.BranchSkip) + .Concat(entityThreeCollection.CurrentValue!) + .Concat(left.TwoSkipShared) + .Concat(left.ThreeSkipPayloadFullShared)) + { + context.Entry(child).State = state; + } + + context.Entry(left).State = state; + } + + context.ChangeTracker.LazyLoadingEnabled = true; + + Assert.False(context.Entry(left).Collection(e => e.TwoSkip).IsLoaded); + Assert.False(context.Entry(left).Collection(e => e.TwoSkipShared).IsLoaded); + Assert.False(context.Entry(left).Collection(e => e.SelfSkipPayloadLeft).IsLoaded); + Assert.False(entityOneCollection.IsLoaded); + Assert.False(context.Entry(left).Collection(e => e.BranchSkip).IsLoaded); + Assert.False(entityThreeCollection.IsLoaded); + Assert.False(context.Entry(left).Collection(e => e.ThreeSkipPayloadFullShared).IsLoaded); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_already_loaded_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Include("UnidirectionalEntityThree").Single(e => e.Id == 3); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection("UnidirectionalEntityThree"); + + context.Entry(left).State = state; + + Assert.True(collectionEntry.IsLoaded); + + if (ExpectLazyLoading) + { + Assert.Equal(4, collectionEntry.CurrentValue!.Count()); + } + else + { + if (async) + { + await collectionEntry.LoadAsync(); + } + else + { + collectionEntry.Load(); + } + } + + Assert.True(collectionEntry.IsLoaded); + foreach (var entityTwo in collectionEntry.CurrentValue!) + { + Assert.False(context.Entry(entityTwo).Collection("UnidirectionalEntityOne").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(4, collectionEntry.CurrentValue!.Count()); + foreach (var right in collectionEntry.CurrentValue!) + { + Assert.Contains(left, context.Entry(right).Collection("UnidirectionalEntityOne").CurrentValue!); + } + + Assert.Equal(1 + 4 + 4, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_using_Query_already_loaded_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Include(e => e.TwoSkip).Single(e => e.Id == 3); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection(e => e.TwoSkip); + + context.Entry(left).State = state; + + Assert.True(collectionEntry.IsLoaded); + + var children = async + ? await collectionEntry.Query().ToListAsync() + : collectionEntry.Query().ToList(); + + Assert.True(collectionEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + foreach (var entityTwo in left.TwoSkip) + { + Assert.False(context.Entry(entityTwo).Collection("UnidirectionalEntityOne1").IsLoaded); + } + + Assert.Equal(7, left.TwoSkip.Count); + foreach (var right in left.TwoSkip) + { + Assert.Contains(left, ((IEnumerable)context.Entry(right).Navigation("UnidirectionalEntityOne1").CurrentValue)!); + } + + Assert.Equal(children, left.TwoSkip.ToList()); + + Assert.Equal(1 + 7 + 7, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_untyped_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Find(3); + + ClearLog(); + + var navigationEntry = context.Entry(left).Navigation("TwoSkip"); + + context.Entry(left).State = state; + + Assert.False(navigationEntry.IsLoaded); + + if (ExpectLazyLoading) + { + Assert.Equal(7, left.TwoSkip.Count); + } + else + { + if (async) + { + await navigationEntry.LoadAsync(); + } + else + { + navigationEntry.Load(); + } + } + + Assert.True(navigationEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkip) + { + Assert.False(context.Entry((object)entityTwo).Collection("UnidirectionalEntityOne1").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(7, left.TwoSkip.Count); + foreach (var right in left.TwoSkip) + { + Assert.Contains(left, ((IEnumerable)context.Entry(right).Member("UnidirectionalEntityOne1").CurrentValue)!); + } + + Assert.Equal(1 + 7 + 7, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_using_Query_untyped_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Find(3); + + ClearLog(); + + var collectionEntry = context.Entry(left).Navigation("TwoSkipShared"); + + context.Entry(left).State = state; + + Assert.False(collectionEntry.IsLoaded); + + var children = async + ? await collectionEntry.Query().ToListAsync() + : collectionEntry.Query().ToList(); + + Assert.False(collectionEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkipShared) + { + Assert.False(context.Entry((object)entityTwo).Collection("UnidirectionalEntityOne").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(3, left.TwoSkipShared.Count); + foreach (var right in left.TwoSkipShared) + { + Assert.Contains(left, context.Entry(right).Collection("UnidirectionalEntityOne").CurrentValue!.Cast()); + } + + Assert.Equal(children, left.TwoSkipShared.ToList()); + + Assert.Equal(1 + 3 + 3, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_not_found_untyped_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Attach( + ExpectLazyLoading + ? context.CreateProxy(b => b.Id = 999) + : new UnidirectionalEntityOne { Id = 999 }).Entity; + + ClearLog(); + + var navigationEntry = context.Entry(left).Navigation("TwoSkip"); + + context.Entry(left).State = state; + + Assert.False(navigationEntry.IsLoaded); + + if (ExpectLazyLoading) + { + Assert.Equal(0, left.TwoSkip.Count); + } + else + { + if (async) + { + await navigationEntry.LoadAsync(); + } + else + { + navigationEntry.Load(); + } + } + + Assert.True(navigationEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Empty(left.TwoSkip); + Assert.Single(context.ChangeTracker.Entries()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_using_Query_not_found_untyped_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Attach( + ExpectLazyLoading + ? context.CreateProxy(b => b.Id = 999) + : new UnidirectionalEntityOne { Id = 999 }).Entity; + + ClearLog(); + + var navigationEntry = context.Entry(left).Navigation("TwoSkip"); + + context.Entry(left).State = state; + + Assert.False(navigationEntry.IsLoaded); + + var children = async + ? await navigationEntry.Query().ToListAsync() + : navigationEntry.Query().ToList(); + + Assert.False(navigationEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Empty(children); + Assert.Empty(left.TwoSkip); + + Assert.Single(context.ChangeTracker.Entries()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_collection_already_loaded_untyped_unidirectional(EntityState state, bool async, CascadeTiming deleteOrphansTiming) + { + using var context = Fixture.CreateContext(); + + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var left = context.Set().Include("UnidirectionalEntityThree").Single(e => e.Id == 3); + + ClearLog(); + + var navigationEntry = context.Entry(left).Collection("UnidirectionalEntityThree"); + + context.Entry(left).State = state; + + Assert.True(navigationEntry.IsLoaded); + + if (ExpectLazyLoading) + { + Assert.Equal(4, navigationEntry.CurrentValue!.Count()); + } + else + { + if (async) + { + await navigationEntry.LoadAsync(); + } + else + { + navigationEntry.Load(); + } + } + + Assert.True(navigationEntry.IsLoaded); + foreach (var entityTwo in navigationEntry.CurrentValue!) + { + Assert.False(context.Entry((object)entityTwo).Collection("UnidirectionalEntityOne").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(4, navigationEntry.CurrentValue!.Count()); + foreach (var right in navigationEntry.CurrentValue!) + { + Assert.Contains(left, context.Entry((object)right).Collection("UnidirectionalEntityOne") + .CurrentValue!.Cast()); + } + + Assert.Equal(1 + 4 + 4, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Modified, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, true, CascadeTiming.Immediate)] + [InlineData(EntityState.Deleted, false, CascadeTiming.Immediate)] + [InlineData(EntityState.Unchanged, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Unchanged, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Modified, false, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, true, CascadeTiming.OnSaveChanges)] + [InlineData(EntityState.Deleted, false, CascadeTiming.OnSaveChanges)] + public virtual async Task Load_collection_using_Query_already_loaded_untyped_unidirectional( + EntityState state, + bool async, + CascadeTiming deleteOrphansTiming) + { + using var context = Fixture.CreateContext(); + + context.ChangeTracker.DeleteOrphansTiming = deleteOrphansTiming; + + var left = context.Set().Include(e => e.TwoSkip).Single(e => e.Id == 3); + + ClearLog(); + + var navigationEntry = context.Entry(left).Navigation("TwoSkip"); + + context.Entry(left).State = state; + + Assert.True(navigationEntry.IsLoaded); + + // Issue #16429 + var children = async + ? await navigationEntry.Query().ToListAsync() + : navigationEntry.Query().ToList(); + + Assert.True(navigationEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkip) + { + Assert.False(context.Entry((object)entityTwo).Collection("UnidirectionalEntityOne1").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(7, left.TwoSkip.Count); + foreach (var right in left.TwoSkip) + { + Assert.Contains(left, ((IEnumerable)context.Entry(right).Collection("UnidirectionalEntityOne1").CurrentValue)!); + } + + Assert.Equal(children, left.TwoSkip.ToList()); + + Assert.Equal(1 + 7 + 7, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_composite_key_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Find(7, "7_2", new DateTime(2007, 2, 1)); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection(e => e.ThreeSkipFull); + + context.Entry(left).State = state; + + Assert.False(collectionEntry.IsLoaded); + + if (ExpectLazyLoading) + { + Assert.Equal(2, left.ThreeSkipFull.Count); + } + else + { + if (async) + { + await collectionEntry.LoadAsync(); + } + else + { + collectionEntry.Load(); + } + } + + Assert.True(collectionEntry.IsLoaded); + foreach (var entityTwo in left.ThreeSkipFull) + { + Assert.False(context.Entry(entityTwo).Collection("UnidirectionalEntityCompositeKey").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, left.ThreeSkipFull.Count); + foreach (var right in left.ThreeSkipFull) + { + Assert.Contains(left, context.Entry(right).Collection("UnidirectionalEntityCompositeKey").CurrentValue!.Cast()); + } + + Assert.Equal(1 + 2 + 2, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(EntityState.Unchanged, true)] + [InlineData(EntityState.Unchanged, false)] + [InlineData(EntityState.Modified, true)] + [InlineData(EntityState.Modified, false)] + [InlineData(EntityState.Deleted, true)] + [InlineData(EntityState.Deleted, false)] + public virtual async Task Load_collection_using_Query_composite_key_unidirectional(EntityState state, bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Find(7, "7_2", new DateTime(2007, 2, 1)); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection(e => e.ThreeSkipFull); + + context.Entry(left).State = state; + + Assert.False(collectionEntry.IsLoaded); + + var children = async + ? await collectionEntry.Query().ToListAsync() + : collectionEntry.Query().ToList(); + + Assert.False(collectionEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, left.ThreeSkipFull.Count); + Assert.Equal(children, left.ThreeSkipFull.ToList()); + Assert.Equal(1 + 2 + 2, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(true, QueryTrackingBehavior.NoTracking)] + [InlineData(true, QueryTrackingBehavior.TrackAll)] + [InlineData(true, QueryTrackingBehavior.NoTrackingWithIdentityResolution)] + [InlineData(false, QueryTrackingBehavior.NoTracking)] + [InlineData(false, QueryTrackingBehavior.TrackAll)] + [InlineData(false, QueryTrackingBehavior.NoTrackingWithIdentityResolution)] + public virtual async Task Load_collection_for_detached_throws_unidirectional(bool async, QueryTrackingBehavior queryTrackingBehavior) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().AsTracking(queryTrackingBehavior).Single(e => e.Id == 3); + + var collectionEntry = context.Entry(left).Collection(e => e.TwoSkip); + + if (queryTrackingBehavior == QueryTrackingBehavior.TrackAll) + { + context.Entry(left).State = EntityState.Detached; + } + + Assert.Equal( + CoreStrings.CannotLoadDetached(nameof(left.TwoSkip), nameof(UnidirectionalEntityOne)), + (await Assert.ThrowsAsync( + async () => + { + if (async) + { + await collectionEntry.LoadAsync(); + } + else + { + collectionEntry.Load(); + } + })).Message); + } + + [ConditionalTheory] + [InlineData(QueryTrackingBehavior.NoTracking)] + [InlineData(QueryTrackingBehavior.TrackAll)] + [InlineData(QueryTrackingBehavior.NoTrackingWithIdentityResolution)] + public virtual void Query_collection_for_detached_throws_unidirectional(QueryTrackingBehavior queryTrackingBehavior) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().AsTracking(queryTrackingBehavior).Single(e => e.Id == 3); + + var collectionEntry = context.Entry(left).Collection(e => e.TwoSkip); + + if (queryTrackingBehavior == QueryTrackingBehavior.TrackAll) + { + context.Entry(left).State = EntityState.Detached; + } + + Assert.Equal( + CoreStrings.CannotLoadDetached(nameof(left.TwoSkip), nameof(UnidirectionalEntityOne)), + Assert.Throws(() => collectionEntry.Query()).Message); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Load_collection_using_Query_with_Include_unidirectional(bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Find(3); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection(e => e.TwoSkipShared); + + Assert.False(collectionEntry.IsLoaded); + + var children = async + ? await collectionEntry.Query().Include("UnidirectionalEntityThree").ToListAsync() + : collectionEntry.Query().Include("UnidirectionalEntityThree").ToList(); + + Assert.False(collectionEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkipShared) + { + var threeNav = context.Entry(entityTwo).Collection("UnidirectionalEntityThree"); + Assert.True(threeNav.IsLoaded); + foreach (var entityThree in threeNav.CurrentValue!) + { + Assert.False(context.Entry(entityThree).Collection(e => e.TwoSkipFull).IsLoaded); + } + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(3, left.TwoSkipShared.Count); + foreach (var right in left.TwoSkipShared) + { + foreach (var three in context.Entry(right).Collection("UnidirectionalEntityThree").CurrentValue!) + { + Assert.Contains(right, three.TwoSkipFull); + } + } + + Assert.Equal(children, left.TwoSkipShared.ToList()); + + Assert.Equal(21, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Load_collection_using_Query_with_Include_for_inverse_unidirectional(bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Find(3); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection(e => e.TwoSkipShared); + + Assert.False(collectionEntry.IsLoaded); + + var queryable = collectionEntry.Query().Include("UnidirectionalEntityOne"); + var children = async + ? await queryable.ToListAsync() + : queryable.ToList(); + + Assert.False(collectionEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkipShared) + { + Assert.True(context.Entry(entityTwo).Collection("UnidirectionalEntityOne").IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(3, left.TwoSkipShared.Count); + foreach (var right in left.TwoSkipShared) + { + Assert.Contains( + left, + context.Entry(right).Collection("UnidirectionalEntityOne").CurrentValue!.Cast()); + } + + Assert.Equal(children, left.TwoSkipShared.ToList()); + + // TODOU: Should be 7, when #27493 is implemented. + Assert.Equal(17, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory(Skip = "TODOU: Needs #27493")] + [InlineData(true)] + [InlineData(false)] + public virtual Task Load_collection_using_Query_with_filtered_Include_unidirectional(bool async) + { + return Task.CompletedTask; + // using var context = Fixture.CreateContext(); + // + // var left = context.Set().Find(3); + // + // ClearLog(); + // + // var collectionEntry = context.Entry(left).Collection(e => e.TwoSkipShared); + // + // Assert.False(collectionEntry.IsLoaded); + // + // var children = async + // ? await collectionEntry.Query().Include(e => e.ThreeSkipFull.Where(e => e.Id == 13 || e.Id == 11)).ToListAsync() + // : collectionEntry.Query().Include(e => e.ThreeSkipFull.Where(e => e.Id == 13 || e.Id == 11)).ToList(); + // + // Assert.False(collectionEntry.IsLoaded); + // foreach (var entityTwo in left.TwoSkipShared) + // { + // Assert.False(context.Entry(entityTwo).Collection("UnidirectionalEntityOne").IsLoaded); + // Assert.True(context.Entry(entityTwo).Collection(e => e.ThreeSkipFull).IsLoaded); + // + // foreach (var entityThree in entityTwo.ThreeSkipFull) + // { + // Assert.False(context.Entry(entityThree).Collection(e => e.TwoSkipFull).IsLoaded); + // } + // } + // + // RecordLog(); + // context.ChangeTracker.LazyLoadingEnabled = false; + // + // Assert.Equal(3, left.TwoSkipShared.Count); + // foreach (var right in left.TwoSkipShared) + // { + // Assert.Contains(left, context.Entry(right).Collection("UnidirectionalEntityOne").CurrentValue!.Cast()); + // foreach (var three in right.ThreeSkipFull) + // { + // Assert.True(three.Id == 11 || three.Id == 13); + // Assert.Contains(right, three.TwoSkipFull); + // } + // } + // + // Assert.Equal(children, left.TwoSkipShared.ToList()); + // + // Assert.Equal(9, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory (Skip = "TODOU: Needs #27493")] + [InlineData(true)] + [InlineData(false)] + public virtual Task Load_collection_using_Query_with_filtered_Include_and_projection_unidirectional(bool async) + { + return Task.CompletedTask; + // using var context = Fixture.CreateContext(); + // + // var left = context.Set().Find(3); + // + // ClearLog(); + // + // var collectionEntry = context.Entry(left).Collection(e => e.TwoSkipShared); + // + // Assert.False(collectionEntry.IsLoaded); + // + // var queryable = collectionEntry + // .Query() + // .Include(e => e.ThreeSkipFull.Where(e => e.Id == 13 || e.Id == 11)) + // .OrderBy(e => e.Id) + // .Select( + // e => new + // { + // e.Id, + // e.Name, + // Count3 = e.ThreeSkipFull.Count + // }); + // + // var projected = async + // ? await queryable.ToListAsync() + // : queryable.ToList(); + // + // RecordLog(); + // context.ChangeTracker.LazyLoadingEnabled = false; + // Assert.False(collectionEntry.IsLoaded); + // Assert.Empty(left.TwoSkipShared); + // Assert.Single(context.ChangeTracker.Entries()); + // + // Assert.Equal(3, projected.Count); + // + // Assert.Equal(10, projected[0].Id); + // Assert.Equal("EntityTwo 10", projected[0].Name); + // Assert.Equal(1, projected[0].Count3); + // + // Assert.Equal(11, projected[1].Id); + // Assert.Equal("EntityTwo 11", projected[1].Name); + // Assert.Equal(4, projected[1].Count3); + // + // Assert.Equal(16, projected[2].Id); + // Assert.Equal("EntityTwo 16", projected[2].Name); + // Assert.Equal(2, projected[2].Count3); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Load_collection_using_Query_with_join_unidirectional(bool async) + { + using var context = Fixture.CreateContext(); + + var left = context.Set().Find(3); + + ClearLog(); + + var collectionEntry = context.Entry(left).Collection(e => e.TwoSkipShared); + + Assert.False(collectionEntry.IsLoaded); + + var queryable = from t in collectionEntry.Query() + join s in context.Set().SelectMany(e => e.TwoSkipShared) + on t.Id equals s.Id + select new { t, s }; + + var projected = async + ? await queryable.ToListAsync() + : queryable.ToList(); + + Assert.False(collectionEntry.IsLoaded); + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(7, context.ChangeTracker.Entries().Count()); + Assert.Equal(8, projected.Count); + + foreach (var pair in projected) + { + Assert.Same(pair.s, pair.t); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Query_with_Include_marks_only_left_as_loaded_unidirectional(bool async) + { + using var context = Fixture.CreateContext(); + + var queryable = context.UnidirectionalEntityOnes.Include(e => e.TwoSkip); + var left = async + ? await queryable.SingleAsync(e => e.Id == 1) + : queryable.Single(e => e.Id == 1); + + Assert.True(context.Entry(left).Collection(e => e.TwoSkip).IsLoaded); + + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(20, left.TwoSkip.Count); + foreach (var right in left.TwoSkip) + { + Assert.False(context.Entry(right).Navigation("UnidirectionalEntityOne1").IsLoaded); + Assert.Same(left, context.Entry(right).Collection("UnidirectionalEntityOne1").CurrentValue!.Cast().Single()); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Query_with_filtered_Include_marks_only_left_as_loaded_unidirectional(bool async) + { + using var context = Fixture.CreateContext(); + + var queryable = context.UnidirectionalEntityOnes.Include(e => e.TwoSkip.Where(e => e.Id == 1 || e.Id == 2)); + var left = async + ? await queryable.SingleAsync(e => e.Id == 1) + : queryable.Single(e => e.Id == 1); + + Assert.True(context.Entry(left).Collection(e => e.TwoSkip).IsLoaded); + + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(2, left.TwoSkip.Count); + foreach (var right in left.TwoSkip) + { + Assert.False(context.Entry(right).Collection("UnidirectionalEntityOne1").IsLoaded); + Assert.Same(left, context.Entry(right).Collection("UnidirectionalEntityOne1").CurrentValue!.Cast().Single()); + } + } +} diff --git a/test/EFCore.Specification.Tests/UnidirectionalManyToManyTrackingTestBase.cs b/test/EFCore.Specification.Tests/UnidirectionalManyToManyTrackingTestBase.cs new file mode 100644 index 00000000000..8584432325e --- /dev/null +++ b/test/EFCore.Specification.Tests/UnidirectionalManyToManyTrackingTestBase.cs @@ -0,0 +1,2934 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.ObjectModel; +using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.TestModels.ManyToManyModel; + +namespace Microsoft.EntityFrameworkCore; + +public abstract partial class ManyToManyTrackingTestBase +{ + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_composite_with_navs_unidirectional(bool async) + { + List leftKeys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Key2 = "7711"; + e.Key3 = new DateTime(7711, 1, 1); + }), + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Key2 = "7712"; + e.Key3 = new DateTime(7712, 1, 1); + }), + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Key2 = "7713"; + e.Key3 = new DateTime(7713, 1, 1); + }), + }; + var rightEntities = new[] + { + context.Set().CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7721), + context.Set().CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7722), + context.Set().CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7723) + }; + + rightEntities[0].CompositeKeySkipFull = CreateCollection(); + rightEntities[1].CompositeKeySkipFull = CreateCollection(); + rightEntities[2].CompositeKeySkipFull = CreateCollection(); + + rightEntities[0].CompositeKeySkipFull.Add(leftEntities[0]); // 21 - 11 (Dupe) + rightEntities[1].CompositeKeySkipFull.Add(leftEntities[0]); // 22 - 11 + rightEntities[2].CompositeKeySkipFull.Add(leftEntities[0]); // 23 - 11 + rightEntities[0].CompositeKeySkipFull.Add(leftEntities[1]); // 21 - 12 + rightEntities[0].CompositeKeySkipFull.Add(leftEntities[2]); // 21 - 13 + + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } + + ValidateFixup(context, leftEntities, rightEntities); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities); + + leftKeys = leftEntities.Select(e => e.Key1).ToList(); + }, + async context => + { + var queryable = context.Set().Where(e => leftKeys.Contains(e.Key1)); + context.Set().Where(e => leftKeys.Contains(e.CompositeId1)).Include(e => e.Leaf).Load(); + + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(3, results.Count); + + var leftEntities = context.ChangeTracker.Entries() + .Select(e => e.Entity).OrderBy(e => e.Key2).ToList(); + + var rightEntities = context.ChangeTracker.Entries() + .Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + ValidateFixup(context, leftEntities, rightEntities); + }); + + void ValidateFixup( + DbContext context, + IList leftEntities, + IList rightEntities) + { + Assert.Equal(11, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries().Count()); + + Assert.Equal(3, rightEntities[0].CompositeKeySkipFull.Count); + Assert.Single(rightEntities[1].CompositeKeySkipFull); + Assert.Single(rightEntities[2].CompositeKeySkipFull); + + var joinEntities = context.ChangeTracker.Entries().Select(e => e.Entity).ToList(); + foreach (var joinEntity in joinEntities) + { + Assert.Equal(joinEntity.Composite.Key1, joinEntity.CompositeId1); + Assert.Equal(joinEntity.Composite.Key2, joinEntity.CompositeId2); + Assert.Equal(joinEntity.Composite.Key3, joinEntity.CompositeId3); + Assert.Equal(joinEntity.Leaf.Id, joinEntity.LeafId); + + Assert.Contains(joinEntity, joinEntity.Composite.JoinLeafFull); + Assert.Contains(joinEntity, joinEntity.Leaf.JoinCompositeKeyFull); + } + + VerifyRelationshipSnapshots(context, joinEntities); + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + } + } + + [ConditionalFact] + public virtual void Can_update_many_to_many_composite_with_navs_unidirectional() + { + ExecuteWithStrategyInTransaction( + context => + { + var leftEntities = context.Set().ToList(); + var rightEntities = context.Set().Include(e => e.CompositeKeySkipFull).OrderBy(e => e.Name) + .ToList(); + + rightEntities[0].CompositeKeySkipFull.Add( + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Key2 = "7711"; + e.Key3 = new DateTime(7711, 1, 1); + e.Name = "Z7711"; + })); + rightEntities[0].CompositeKeySkipFull.Add( + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Key2 = "7712"; + e.Key3 = new DateTime(7712, 1, 1); + e.Name = "Z7712"; + })); + rightEntities[0].CompositeKeySkipFull.Add( + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Key2 = "7713"; + e.Key3 = new DateTime(7713, 1, 1); + e.Name = "Z7713"; + })); + + rightEntities[1].CompositeKeySkipFull.Remove(rightEntities[1].CompositeKeySkipFull.Single(e => e.Key2 == "3_1")); + + rightEntities[2].CompositeKeySkipFull.Remove(rightEntities[2].CompositeKeySkipFull.Single(e => e.Key2 == "8_3")); + rightEntities[2].CompositeKeySkipFull.Add( + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7714; + e.Key2 = "7714"; + e.Key3 = new DateTime(7714, 1, 1); + e.Name = "Z7714"; + })); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities, 24, 4, 35); + + context.SaveChanges(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 4, 35 - 2); + }, + context => + { + var leftEntities = context.Set().ToList(); + var rightEntities = context.Set().Include(e => e.CompositeKeySkipFull).OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 4, 35 - 2); + }); + + void ValidateFixup( + DbContext context, + List leftEntities, + List rightEntities, + int leftCount, + int rightCount, + int joinCount) + { + Assert.Equal(leftCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(rightCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(joinCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(leftCount + rightCount + joinCount, context.ChangeTracker.Entries().Count()); + + Assert.Contains(rightEntities[0].CompositeKeySkipFull, e => e.Name == "Z7711"); + Assert.Contains(rightEntities[0].CompositeKeySkipFull, e => e.Name == "Z7712"); + Assert.Contains(rightEntities[0].CompositeKeySkipFull, e => e.Name == "Z7713"); + + Assert.DoesNotContain(rightEntities[1].CompositeKeySkipFull, e => e.Key2 == "3_1"); + + Assert.DoesNotContain(rightEntities[2].CompositeKeySkipFull, e => e.Key2 == "8_1"); + Assert.Contains(rightEntities[2].CompositeKeySkipFull, e => e.Key2 == "7714"); + + var joinEntries = context.ChangeTracker.Entries().ToList(); + foreach (var joinEntry in joinEntries) + { + var joinEntity = joinEntry.Entity; + + Assert.Equal(joinEntity.Composite.Key1, joinEntity.CompositeId1); + Assert.Equal(joinEntity.Composite.Key2, joinEntity.CompositeId2); + Assert.Equal(joinEntity.Composite.Key3, joinEntity.CompositeId3); + Assert.Equal(joinEntity.Leaf.Id, joinEntity.LeafId); + + Assert.Contains(joinEntity, joinEntity.Composite.JoinLeafFull); + Assert.Contains(joinEntity, joinEntity.Leaf.JoinCompositeKeyFull); + } + + var allLeft = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Key2) + .ToList(); + var allRight = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + VerifyRelationshipSnapshots(context, joinEntries.Select(e => e.Entity)); + VerifyRelationshipSnapshots(context, allLeft); + VerifyRelationshipSnapshots(context, allRight); + + var count = 0; + foreach (var left in allLeft) + { + foreach (var right in allRight) + { + if (right.CompositeKeySkipFull?.Contains(left) == true) + { + count++; + } + } + } + + var deleted = context.ChangeTracker.Entries().Count(e => e.State == EntityState.Deleted); + Assert.Equal(joinCount, count + deleted); + } + } + + [ConditionalFact] + public virtual void Can_delete_with_many_to_many_composite_with_navs_unidirectional() + { + var key1 = 0; + var key2 = ""; + var key3 = default(DateTime); + var id = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var ones = context.Set().Include(e => e.RootSkipShared).OrderBy(e => e.Key2).ToList(); + var threes = context.Set().Include(e => e.CompositeKeySkipFull).OrderBy(e => e.Name).ToList(); + + // Make sure other related entities are loaded for delete fixup + context.Set().Load(); + + var toRemoveOne = context.UnidirectionalEntityCompositeKeys.Single(e => e.Name == "Composite 6"); + key1 = toRemoveOne.Key1; + key2 = toRemoveOne.Key2; + key3 = toRemoveOne.Key3; + var refCountOnes = threes.SelectMany(e => e.CompositeKeySkipFull).Count(e => e == toRemoveOne); + + var toRemoveThree = (UnidirectionalEntityLeaf)context.UnidirectionalEntityRoots.Single(e => e.Name == "Leaf 3"); + id = toRemoveThree.Id; + var refCountThrees = ones.SelectMany(e => e.RootSkipShared).Count(e => e == toRemoveThree); + + foreach (var joinEntity in context.ChangeTracker.Entries().Select(e => e.Entity) + .ToList()) + { + Assert.Equal(joinEntity.Composite.Key1, joinEntity.CompositeId1); + Assert.Equal(joinEntity.Composite.Key2, joinEntity.CompositeId2); + Assert.Equal(joinEntity.Composite.Key3, joinEntity.CompositeId3); + Assert.Equal(joinEntity.Leaf.Id, joinEntity.LeafId); + Assert.Contains(joinEntity, joinEntity.Composite.JoinLeafFull); + Assert.Contains(joinEntity, joinEntity.Leaf.JoinCompositeKeyFull); + } + + context.Remove(toRemoveOne); + context.Remove(toRemoveThree); + + Assert.Equal(refCountOnes, threes.SelectMany(e => e.CompositeKeySkipFull).Count(e => e == toRemoveOne)); + Assert.Equal(refCountThrees, ones.SelectMany(e => e.RootSkipShared).Count(e => e == toRemoveThree)); + + ValidateJoinNavigations(context); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + Assert.Equal(refCountOnes, threes.SelectMany(e => e.CompositeKeySkipFull).Count(e => e == toRemoveOne)); + Assert.Equal(refCountThrees, ones.SelectMany(e => e.RootSkipShared).Count(e => e == toRemoveThree)); + + ValidateJoinNavigations(context); + + Assert.All( + context.ChangeTracker.Entries(), e => Assert.Equal( + (e.Entity.CompositeId1 == key1 + && e.Entity.CompositeId2 == key2 + && e.Entity.CompositeId3 == key3) + || e.Entity.LeafId == id + ? EntityState.Deleted + : EntityState.Unchanged, e.State)); + + context.SaveChanges(); + + Assert.Equal(0, threes.SelectMany(e => e.CompositeKeySkipFull).Count(e => e == toRemoveOne)); + Assert.Equal(0, ones.SelectMany(e => e.RootSkipShared).Count(e => e == toRemoveThree)); + + ValidateJoinNavigations(context); + + ones.Remove(toRemoveOne); + threes.Remove(toRemoveThree); + + Assert.Equal(0, threes.SelectMany(e => e.CompositeKeySkipFull).Count(e => e == toRemoveOne)); + Assert.Equal(0, ones.SelectMany(e => e.RootSkipShared).Count(e => e == toRemoveThree)); + + Assert.DoesNotContain( + context.ChangeTracker.Entries(), + e => (e.Entity.CompositeId1 == key1 + && e.Entity.CompositeId2 == key2 + && e.Entity.CompositeId3 == key3) + || e.Entity.LeafId == id); + }, + context => + { + var ones = context.Set().Include(e => e.RootSkipShared).OrderBy(e => e.Key2).ToList(); + var threes = context.Set().Include(e => e.CompositeKeySkipFull).OrderBy(e => e.Name).ToList(); + + ValidateNavigations(ones, threes); + + Assert.DoesNotContain( + context.ChangeTracker.Entries(), + e => (e.Entity.CompositeId1 == key1 + && e.Entity.CompositeId2 == key2 + && e.Entity.CompositeId3 == key3) + || e.Entity.LeafId == id); + }); + + void ValidateNavigations(List ones, List threes) + { + foreach (var one in ones) + { + if (one.RootSkipShared != null) + { + Assert.DoesNotContain(one.RootSkipShared, e => e.Id == id); + } + + if (one.JoinLeafFull != null) + { + Assert.DoesNotContain( + one.JoinLeafFull, + e => e.CompositeId1 == key1 + && e.CompositeId2 == key2 + && e.CompositeId3 == key3); + + Assert.DoesNotContain(one.JoinLeafFull, e => e.LeafId == id); + } + } + + foreach (var three in threes) + { + if (three.CompositeKeySkipFull != null) + { + Assert.DoesNotContain( + three.CompositeKeySkipFull, + e => e.Key1 == key1 + && e.Key2 == key2 + && e.Key3 == key3); + } + + if (three.JoinCompositeKeyFull != null) + { + Assert.DoesNotContain( + three.JoinCompositeKeyFull, + e => e.CompositeId1 == key1 + && e.CompositeId2 == key2 + && e.CompositeId3 == key3); + + Assert.DoesNotContain(three.JoinCompositeKeyFull, e => e.LeafId == id); + } + } + } + + static void ValidateJoinNavigations(DbContext context) + { + foreach (var joinEntity in context.ChangeTracker.Entries().Select(e => e.Entity).ToList()) + { + Assert.Equal(joinEntity.Composite.Key1, joinEntity.CompositeId1); + Assert.Equal(joinEntity.Composite.Key2, joinEntity.CompositeId2); + Assert.Equal(joinEntity.Composite.Key3, joinEntity.CompositeId3); + Assert.Equal(joinEntity.Leaf.Id, joinEntity.LeafId); + + Assert.Contains(joinEntity, joinEntity.Composite.JoinLeafFull); + Assert.Contains(joinEntity, joinEntity.Leaf.JoinCompositeKeyFull); + } + } + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_composite_additional_pk_with_navs_unidirectional(bool async) + { + List keys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Key2 = "7711"; + e.Key3 = new DateTime(7711, 1, 1); + }), + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Key2 = "7712"; + e.Key3 = new DateTime(7712, 1, 1); + }), + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Key2 = "7713"; + e.Key3 = new DateTime(7713, 1, 1); + }), + }; + var rightEntities = new[] + { + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7721; + e.Name = "Z7721"; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7722; + e.Name = "Z7722"; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7723; + e.Name = "Z7723"; + }) + }; + + leftEntities[0].ThreeSkipFull = CreateCollection(); + leftEntities[1].ThreeSkipFull = CreateCollection(); + leftEntities[2].ThreeSkipFull = CreateCollection(); + + leftEntities[0].ThreeSkipFull.Add(rightEntities[0]); // 11 - 21 + leftEntities[1].ThreeSkipFull.Add(rightEntities[0]); // 12 - 21 + leftEntities[2].ThreeSkipFull.Add(rightEntities[0]); // 13 - 21 + leftEntities[0].ThreeSkipFull.Add(rightEntities[1]); // 11 - 22 + leftEntities[0].ThreeSkipFull.Add(rightEntities[2]); // 11 - 23 + + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } + + ValidateFixup(context, leftEntities, rightEntities, postSave: false); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities, postSave: true); + + keys = leftEntities.Select(e => e.Key2).ToList(); + }, + async context => + { + var queryable = context.Set().Where(e => keys.Contains(e.Key2)) + .Include(e => e.ThreeSkipFull); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(3, results.Count); + + var leftEntities = context.ChangeTracker.Entries() + .Select(e => e.Entity).OrderBy(e => e.Key2).ToList(); + + var rightEntities = context.ChangeTracker.Entries() + .Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, postSave: true); + }); + + void ValidateFixup( + DbContext context, + IList leftEntities, + IList rightEntities, + bool postSave) + { + var entries = context.ChangeTracker.Entries(); + Assert.Equal(11, entries.Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries().Count()); + + Assert.Equal(3, leftEntities[0].ThreeSkipFull.Count); + Assert.Single(leftEntities[1].ThreeSkipFull); + Assert.Single(leftEntities[2].ThreeSkipFull); + + Assert.Equal( + 3, context.Entry(rightEntities[0]).Collection("UnidirectionalEntityCompositeKey").CurrentValue!.Cast().Count()); + Assert.Single(context.Entry(rightEntities[1]).Collection("UnidirectionalEntityCompositeKey").CurrentValue!.Cast()); + Assert.Single(context.Entry(rightEntities[2]).Collection("UnidirectionalEntityCompositeKey").CurrentValue!.Cast()); + + var joinEntities = context.ChangeTracker.Entries().Select(e => e.Entity).ToList(); + foreach (var joinEntity in joinEntities) + { + Assert.Equal(joinEntity.Composite.Key1, joinEntity.CompositeId1); + Assert.Equal(joinEntity.Composite.Key2, joinEntity.CompositeId2); + Assert.Equal(joinEntity.Composite.Key3, joinEntity.CompositeId3); + Assert.Equal(joinEntity.Three.Id, joinEntity.ThreeId); + + Assert.Contains(joinEntity, joinEntity.Composite.JoinThreeFull); + Assert.Contains(joinEntity, joinEntity.Three.JoinCompositeKeyFull); + } + + VerifyRelationshipSnapshots(context, joinEntities); + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + + foreach (var entry in context.ChangeTracker.Entries()) + { + Assert.Equal(postSave ? EntityState.Unchanged : EntityState.Added, entry.State); + } + } + } + + [ConditionalFact] + public virtual void Can_update_many_to_many_composite_additional_pk_with_navs_unidirectional() + { + List threeIds = null; + + ExecuteWithStrategyInTransaction( + context => + { + var leftEntities = context.Set().Include(e => e.ThreeSkipFull).OrderBy(e => e.Key2) + .ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityCompositeKey").OrderBy(e => e.Name).ToList(); + + var threes = new[] + { + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7721; + e.Name = "Z7721"; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7722; + e.Name = "Z7722"; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7723; + e.Name = "Z7723"; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7724; + e.Name = "Z7724"; + }) + }; + + var composites = new[] + { + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Key2 = "Z7711"; + e.Key3 = new DateTime(7711, 1, 1); + }), + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Key2 = "Z7712"; + e.Key3 = new DateTime(7712, 1, 1); + }), + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Key2 = "Z7713"; + e.Key3 = new DateTime(7713, 1, 1); + }), + context.UnidirectionalEntityCompositeKeys.CreateInstance( + (e, p) => + { + e.Key1 = Fixture.UseGeneratedKeys ? 0 : 7714; + e.Key2 = "Z7714"; + e.Key3 = new DateTime(7714, 1, 1); + }) + }; + + leftEntities[0].ThreeSkipFull.Add(threes[0]); + leftEntities[0].ThreeSkipFull.Add(threes[1]); + leftEntities[0].ThreeSkipFull.Add(threes[2]); + + var rightNav0 = (ICollection)context.Entry(rightEntities[0]) + .Collection("UnidirectionalEntityCompositeKey").CurrentValue!; + rightNav0.Add(composites[0]); + rightNav0.Add(composites[1]); + rightNav0.Add(composites[2]); + + leftEntities[0].ThreeSkipFull.Remove(leftEntities[0].ThreeSkipFull.Single(e => e.Name == "EntityThree 2")); + var rightNav1 = (ICollection)context.Entry(rightEntities[1]) + .Collection("UnidirectionalEntityCompositeKey").CurrentValue!; + rightNav1.Remove(rightNav1.Single(e => e.Name == "Composite 16")); + + leftEntities[3].ThreeSkipFull.Remove(leftEntities[3].ThreeSkipFull.Single(e => e.Name == "EntityThree 7")); + leftEntities[3].ThreeSkipFull.Add(threes[3]); + + var rightNav2 = (ICollection)context.Entry(rightEntities[2]) + .Collection("UnidirectionalEntityCompositeKey").CurrentValue!; + rightNav2.Remove(rightNav2.Single(e => e.Name == "Composite 7")); + rightNav2.Add(composites[3]); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + threeIds = threes.Select(e => context.Entry(e).Property(e => e.Id).CurrentValue).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 53); + + context.SaveChanges(); + + threeIds = threes.Select(e => e.Id).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 53 - 4); + }, + context => + { + var leftEntities = context.Set().Include(e => e.ThreeSkipFull).OrderBy(e => e.Key2) + .ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityCompositeKey").OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 53 - 4); + }); + + void ValidateFixup( + DbContext context, + List leftEntities, + List rightEntities, + int leftCount, + int rightCount, + int joinCount) + { + Assert.Equal(leftCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(rightCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(joinCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(leftCount + rightCount + joinCount, context.ChangeTracker.Entries().Count()); + + Assert.Contains(leftEntities[0].ThreeSkipFull, e => context.Entry(e).Property(e => e.Id).CurrentValue == threeIds[0]); + Assert.Contains(leftEntities[0].ThreeSkipFull, e => context.Entry(e).Property(e => e.Id).CurrentValue == threeIds[1]); + Assert.Contains(leftEntities[0].ThreeSkipFull, e => context.Entry(e).Property(e => e.Id).CurrentValue == threeIds[2]); + + var rightNav0 = context.Entry(rightEntities[0]) + .Collection("UnidirectionalEntityCompositeKey").CurrentValue!; + Assert.Contains(rightNav0, e => e.Key2 == "Z7711"); + Assert.Contains(rightNav0, e => e.Key2 == "Z7712"); + Assert.Contains(rightNav0, e => e.Key2 == "Z7713"); + + Assert.DoesNotContain(leftEntities[0].ThreeSkipFull, e => e.Name == "EntityThree 9"); + var rightNav1 = context.Entry(rightEntities[1]) + .Collection("UnidirectionalEntityCompositeKey").CurrentValue; + if (rightNav1 != null) + { + Assert.DoesNotContain(rightNav1, e => e.Key2 == "9_2"); + } + + Assert.DoesNotContain(leftEntities[3].ThreeSkipFull, e => e.Name == "EntityThree 23"); + Assert.Contains(leftEntities[3].ThreeSkipFull, e => context.Entry(e).Property(e => e.Id).CurrentValue == threeIds[3]); + + var rightNav2 = context.Entry(rightEntities[2]) + .Collection("UnidirectionalEntityCompositeKey").CurrentValue!; + Assert.DoesNotContain(rightNav2, e => e.Key2 == "6_1"); + Assert.Contains(rightNav2, e => e.Key2 == "Z7714"); + + var joinEntries = context.ChangeTracker.Entries().ToList(); + foreach (var joinEntry in joinEntries) + { + var joinEntity = joinEntry.Entity; + + Assert.Equal(joinEntity.Composite.Key1, joinEntity.CompositeId1); + Assert.Equal(joinEntity.Composite.Key2, joinEntity.CompositeId2); + Assert.Equal(joinEntity.Composite.Key3, joinEntity.CompositeId3); + Assert.Equal(joinEntity.Three.Id, joinEntity.ThreeId); + + Assert.Contains(joinEntity, joinEntity.Composite.JoinThreeFull); + Assert.Contains(joinEntity, joinEntity.Three.JoinCompositeKeyFull); + } + + var allLeft = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Key2) + .ToList(); + var allRight = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + VerifyRelationshipSnapshots(context, joinEntries.Select(e => e.Entity)); + VerifyRelationshipSnapshots(context, allLeft); + VerifyRelationshipSnapshots(context, allRight); + + var count = 0; + foreach (var left in allLeft) + { + foreach (var right in allRight) + { + var rightNav = context.Entry(right) + .Collection("UnidirectionalEntityCompositeKey").CurrentValue; + if (left.ThreeSkipFull?.Contains(right) == true) + { + Assert.Contains(left, rightNav!); + count++; + } + + if (rightNav?.Contains(left) == true) + { + Assert.Contains(right, left.ThreeSkipFull); + count++; + } + } + } + + var deleted = context.ChangeTracker.Entries() + .Count(e => e.State == EntityState.Deleted); + Assert.Equal(joinCount, (count / 2) + deleted); + } + } + + [ConditionalFact] + public virtual void Can_delete_with_many_to_many_composite_additional_pk_with_navs_unidirectional() + { + var threeId = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var ones = context.Set().Include(e => e.ThreeSkipFull).OrderBy(e => e.Key2).ToList(); + var threes = context.Set().Include("UnidirectionalEntityCompositeKey").OrderBy(e => e.Name).ToList(); + + // Make sure other related entities are loaded for delete fixup + context.Set().Load(); + + var toRemoveOne = context.UnidirectionalEntityCompositeKeys.Single(e => e.Name == "Composite 6"); + + var toRemoveThree = context.UnidirectionalEntityThrees.Single(e => e.Name == "EntityThree 17"); + threeId = toRemoveThree.Id; + var refCountThrees = ones.SelectMany(e => e.ThreeSkipFull).Count(e => e == toRemoveThree); + + foreach (var joinEntity in context.ChangeTracker.Entries().Select(e => e.Entity) + .ToList()) + { + Assert.Equal(joinEntity.Composite.Key1, joinEntity.CompositeId1); + Assert.Equal(joinEntity.Composite.Key2, joinEntity.CompositeId2); + Assert.Equal(joinEntity.Composite.Key3, joinEntity.CompositeId3); + Assert.Equal(joinEntity.Three.Id, joinEntity.ThreeId); + Assert.Contains(joinEntity, joinEntity.Composite.JoinThreeFull); + Assert.Contains(joinEntity, joinEntity.Three.JoinCompositeKeyFull); + } + + context.Remove(toRemoveOne); + context.Remove(toRemoveThree); + + Assert.Equal(refCountThrees, ones.SelectMany(e => e.ThreeSkipFull).Count(e => e == toRemoveThree)); + + ValidateJoinNavigations(context); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + Assert.Equal(refCountThrees, ones.SelectMany(e => e.ThreeSkipFull).Count(e => e == toRemoveThree)); + + ValidateJoinNavigations(context); + + Assert.All( + context.ChangeTracker.Entries(), e => Assert.Equal( + (e.Entity.CompositeId2 == "6_1" + && e.Entity.CompositeId3 == new DateTime(2006, 1, 1)) + || e.Entity.ThreeId == threeId + ? EntityState.Deleted + : EntityState.Unchanged, e.State)); + + context.SaveChanges(); + + Assert.Equal(0, ones.SelectMany(e => e.ThreeSkipFull).Count(e => e == toRemoveThree)); + + ValidateJoinNavigations(context); + + ones.Remove(toRemoveOne); + threes.Remove(toRemoveThree); + + Assert.Equal(0, ones.SelectMany(e => e.ThreeSkipFull).Count(e => e == toRemoveThree)); + + Assert.DoesNotContain( + context.ChangeTracker.Entries(), + e => (e.Entity.CompositeId2 == "6_1" + && e.Entity.CompositeId3 == new DateTime(2006, 1, 1)) + || e.Entity.ThreeId == threeId); + }, + context => + { + var ones = context.Set().Include(e => e.ThreeSkipFull).OrderBy(e => e.Key2).ToList(); + var threes = context.Set().Include("UnidirectionalEntityCompositeKey").OrderBy(e => e.Name).ToList(); + + ValidateNavigations(context, ones, threes); + + Assert.DoesNotContain( + context.ChangeTracker.Entries(), + e => (e.Entity.CompositeId2 == "6_1" + && e.Entity.CompositeId3 == new DateTime(2006, 1, 1)) + || e.Entity.ThreeId == threeId); + }); + + void ValidateNavigations(DbContext context, List ones, List threes) + { + foreach (var one in ones) + { + if (one.ThreeSkipFull != null) + { + Assert.DoesNotContain(one.ThreeSkipFull, e => e.Id == threeId); + } + } + + foreach (var three in threes) + { + if (three.JoinCompositeKeyFull != null) + { + Assert.DoesNotContain( + three.JoinCompositeKeyFull, + e => e.CompositeId2 == "6_1" + && e.CompositeId3 == new DateTime(2006, 1, 1)); + + Assert.DoesNotContain(three.JoinCompositeKeyFull, e => e.ThreeId == threeId); + } + } + + foreach (var three in threes) + { + var threeNav = context.Entry(three) + .Collection("UnidirectionalEntityCompositeKey").CurrentValue; + + if (threeNav != null) + { + Assert.DoesNotContain( + threeNav, + e => e.Key2 == "6_1" + && e.Key3 == new DateTime(2006, 1, 1)); + } + + if (three.JoinCompositeKeyFull != null) + { + Assert.DoesNotContain( + three.JoinCompositeKeyFull, + e => e.CompositeId2 == "6_1" + && e.CompositeId3 == new DateTime(2006, 1, 1)); + + Assert.DoesNotContain(three.JoinCompositeKeyFull, e => e.ThreeId == threeId); + } + } + } + + static void ValidateJoinNavigations(DbContext context) + { + foreach (var joinEntity in context.ChangeTracker.Entries().Select(e => e.Entity) + .ToList()) + { + Assert.Equal(joinEntity.Composite.Key1, joinEntity.CompositeId1); + Assert.Equal(joinEntity.Composite.Key2, joinEntity.CompositeId2); + Assert.Equal(joinEntity.Composite.Key3, joinEntity.CompositeId3); + Assert.Equal(joinEntity.Three.Id, joinEntity.ThreeId); + + Assert.Contains(joinEntity, joinEntity.Composite.JoinThreeFull); + Assert.Contains(joinEntity, joinEntity.Three.JoinCompositeKeyFull); + } + } + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_self_shared_unidirectional(bool async) + { + List leftKeys = null; + List rightKeys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7711), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7712), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7713) + }; + var rightEntities = new[] + { + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7721), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7722), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7723) + }; + + var collectionEntry = context.Entry(leftEntities[0]).Collection("UnidirectionalEntityTwo"); + collectionEntry.CurrentValue = CreateCollection(); + var nav0 = (ICollection)collectionEntry.CurrentValue!; + + nav0.Add(rightEntities[0]); // 11 - 21 + nav0.Add(rightEntities[1]); // 11 - 22 + nav0.Add(rightEntities[2]); // 11 - 23 + + rightEntities[0].SelfSkipSharedRight = CreateCollection(); + + rightEntities[0].SelfSkipSharedRight.Add(leftEntities[0]); // 21 - 11 (Dupe) + rightEntities[0].SelfSkipSharedRight.Add(leftEntities[1]); // 21 - 12 + rightEntities[0].SelfSkipSharedRight.Add(leftEntities[2]); // 21 - 13 + + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } + + ValidateFixup(context, leftEntities, rightEntities); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities); + + leftKeys = leftEntities.Select(e => e.Id).ToList(); + rightKeys = rightEntities.Select(e => e.Id).ToList(); + }, + async context => + { + var queryable = context.Set() + .Where(e => leftKeys.Contains(e.Id) || rightKeys.Contains(e.Id)) + .Include("UnidirectionalEntityTwo"); + + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(6, results.Count); + + var leftEntities = context.ChangeTracker.Entries() + .Select(e => e.Entity) + .Where(e => leftKeys.Contains(e.Id)) + .OrderBy(e => e.Name) + .ToList(); + + var rightEntities = context.ChangeTracker.Entries() + .Select(e => e.Entity) + .Where(e => rightKeys.Contains(e.Id)) + .OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities); + }); + + void ValidateFixup(DbContext context, IList leftEntities, IList rightEntities) + { + Assert.Equal(11, context.ChangeTracker.Entries().Count()); + Assert.Equal(6, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries>().Count()); + + Assert.Equal( + 3, context.Entry(leftEntities[0]).Collection("UnidirectionalEntityTwo").CurrentValue!.Count()); + Assert.Single(context.Entry(leftEntities[1]).Collection("UnidirectionalEntityTwo").CurrentValue!); + Assert.Single(context.Entry(leftEntities[2]).Collection("UnidirectionalEntityTwo").CurrentValue!); + + Assert.Equal(3, rightEntities[0].SelfSkipSharedRight.Count); + Assert.Single(rightEntities[1].SelfSkipSharedRight); + Assert.Single(rightEntities[2].SelfSkipSharedRight); + + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + } + } + + [ConditionalFact] + public virtual void Can_update_many_to_many_self_unidirectional() + { + List ids = null; + + ExecuteWithStrategyInTransaction( + context => + { + var leftEntities = context.Set().Include(e => e.SelfSkipSharedRight).OrderBy(e => e.Name).ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityTwo").OrderBy(e => e.Name).ToList(); + + var twos = new[] + { + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7721; + e.Name = "Z7721"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7722; + e.Name = "Z7722"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7723; + e.Name = "Z7723"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7724; + e.Name = "Z7724"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Name = "Z7711"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Name = "Z7712"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Name = "Z7713"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7714; + e.Name = "Z7714"; + }) + }; + + leftEntities[0].SelfSkipSharedRight.Add(twos[0]); + leftEntities[0].SelfSkipSharedRight.Add(twos[1]); + leftEntities[0].SelfSkipSharedRight.Add(twos[2]); + + var nav0 = (ICollection)context.Entry(rightEntities[0]) + .Collection("UnidirectionalEntityTwo").CurrentValue!; + nav0.Add(twos[4]); + nav0.Add(twos[5]); + nav0.Add(twos[6]); + + leftEntities[0].SelfSkipSharedRight.Remove(leftEntities[0].SelfSkipSharedRight.Single(e => e.Name == "EntityTwo 9")); + var nav1 = (ICollection)context.Entry(rightEntities[1]) + .Collection("UnidirectionalEntityTwo").CurrentValue!; + nav1.Remove(nav1.Single(e => e.Name == "EntityTwo 1")); + + leftEntities[4].SelfSkipSharedRight.Remove(leftEntities[4].SelfSkipSharedRight.Single(e => e.Name == "EntityTwo 18")); + leftEntities[4].SelfSkipSharedRight.Add(twos[3]); + + var nav5 = (ICollection)context.Entry(rightEntities[5]) + .Collection("UnidirectionalEntityTwo").CurrentValue!; + nav5.Remove(nav5.Single(e => e.Name == "EntityTwo 12")); + nav5.Add(twos[7]); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + ids = twos.Select(e => context.Entry(e).Property(e => e.Id).CurrentValue).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 28, 42); + + context.SaveChanges(); + + ids = twos.Select(e => e.Id).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 28, 42 - 4); + }, + context => + { + var leftEntities = context.Set().Include(e => e.SelfSkipSharedRight).OrderBy(e => e.Name).ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityTwo").OrderBy(e => e.Name).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 28, 42 - 4); + }); + + void ValidateFixup( + DbContext context, + List leftEntities, + List rightEntities, + int count, + int joinCount) + { + Assert.Equal(count, context.ChangeTracker.Entries().Count()); + Assert.Equal(joinCount, context.ChangeTracker.Entries>().Count()); + Assert.Equal(count + joinCount, context.ChangeTracker.Entries().Count()); + + Assert.Contains(leftEntities[0].SelfSkipSharedRight, e => context.Entry(e).Property(e => e.Id).CurrentValue == ids[0]); + Assert.Contains(leftEntities[0].SelfSkipSharedRight, e => context.Entry(e).Property(e => e.Id).CurrentValue == ids[1]); + Assert.Contains(leftEntities[0].SelfSkipSharedRight, e => context.Entry(e).Property(e => e.Id).CurrentValue == ids[2]); + + var nav0 = context.Entry(rightEntities[0]).Collection("UnidirectionalEntityTwo").CurrentValue!; + Assert.Contains(nav0, e => context.Entry(e).Property(e => e.Id).CurrentValue == ids[4]); + Assert.Contains(nav0, e => context.Entry(e).Property(e => e.Id).CurrentValue == ids[5]); + Assert.Contains(nav0, e => context.Entry(e).Property(e => e.Id).CurrentValue == ids[6]); + + var nav1 = context.Entry(rightEntities[1]).Collection("UnidirectionalEntityTwo").CurrentValue!; + Assert.DoesNotContain(leftEntities[0].SelfSkipSharedRight, e => e.Name == "EntityTwo 9"); + Assert.DoesNotContain(nav1, e => e.Name == "EntityTwo 1"); + + Assert.DoesNotContain(leftEntities[4].SelfSkipSharedRight, e => e.Name == "EntityTwo 18"); + Assert.Contains(leftEntities[4].SelfSkipSharedRight, e => context.Entry(e).Property(e => e.Id).CurrentValue == ids[3]); + + var nav5 = context.Entry(rightEntities[5]).Collection("UnidirectionalEntityTwo").CurrentValue!; + Assert.DoesNotContain(nav5, e => e.Name == "EntityTwo 12"); + Assert.Contains(nav5, e => context.Entry(e).Property(e => e.Id).CurrentValue == ids[7]); + + var allLeft = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + var allRight = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + VerifyRelationshipSnapshots(context, allLeft); + VerifyRelationshipSnapshots(context, allRight); + + var joins = 0; + foreach (var left in allLeft) + { + foreach (var right in allRight) + { + var rightNav = context.Entry(right).Collection("UnidirectionalEntityTwo").CurrentValue; + if (left.SelfSkipSharedRight?.Contains(right) == true) + { + Assert.Contains(left, rightNav!); + joins++; + } + + if (rightNav?.Contains(left) == true) + { + Assert.Contains(right, left.SelfSkipSharedRight); + joins++; + } + } + } + + var deleted = context.ChangeTracker.Entries>().Count(e => e.State == EntityState.Deleted); + Assert.Equal(joinCount, (joins / 2) + deleted); + } + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_with_inheritance_unidirectional(bool async) + { + List keys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7711), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7712), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7713), + }; + var rightEntities = new[] + { + context.Set().CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7721), + context.Set().CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7722), + context.Set().CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7723) + }; + + leftEntities[0].BranchSkip = CreateCollection(); + leftEntities[1].BranchSkip = CreateCollection(); + leftEntities[2].BranchSkip = CreateCollection(); + + leftEntities[0].BranchSkip.Add(rightEntities[0]); // 11 - 21 + leftEntities[1].BranchSkip.Add(rightEntities[0]); // 12 - 21 + leftEntities[2].BranchSkip.Add(rightEntities[0]); // 13 - 21 + leftEntities[0].BranchSkip.Add(rightEntities[1]); // 11 - 22 + leftEntities[0].BranchSkip.Add(rightEntities[2]); // 11 - 23 + + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } + + ValidateFixup(context, leftEntities, rightEntities); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities); + + keys = leftEntities.Select(e => e.Id).ToList(); + }, + async context => + { + var queryable = context.Set().Where(e => keys.Contains(e.Id)).Include(e => e.BranchSkip); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(3, results.Count); + + var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities); + }); + + void ValidateFixup(DbContext context, IList leftEntities, IList rightEntities) + { + Assert.Equal(11, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries().Count()); + + Assert.Equal(3, leftEntities[0].BranchSkip.Count); + Assert.Single(leftEntities[1].BranchSkip); + Assert.Single(leftEntities[2].BranchSkip); + + Assert.Equal(3, context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne").CurrentValue!.Cast().Count()); + Assert.Single(context.Entry(rightEntities[1]).Collection("UnidirectionalEntityOne").CurrentValue!.Cast()); + Assert.Single(context.Entry(rightEntities[2]).Collection("UnidirectionalEntityOne").CurrentValue!.Cast()); + + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + } + } + + [ConditionalFact] + public virtual void Can_update_many_to_many_with_inheritance_unidirectional() + { + ExecuteWithStrategyInTransaction( + context => + { + var leftEntities = context.Set().Include(e => e.BranchSkip).OrderBy(e => e.Name).ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityOne").OrderBy(e => e.Name).ToList(); + + leftEntities[0].BranchSkip.Add( + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7721; + e.Name = "Z7721"; + })); + leftEntities[0].BranchSkip.Add( + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7722; + e.Name = "Z7722"; + })); + leftEntities[0].BranchSkip.Add( + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7723; + e.Name = "Z7723"; + })); + + var rightNav0 = (ICollection)context.Entry(rightEntities[0]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + + rightNav0.Add( + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Name = "Z7711"; + })); + rightNav0.Add( + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Name = "Z7712"; + })); + rightNav0.Add( + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Name = "Z7713"; + })); + + leftEntities[1].BranchSkip.Remove(leftEntities[1].BranchSkip.Single(e => e.Name == "Branch 4")); + var rightNav1 = (ICollection)context.Entry(rightEntities[1]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + rightNav1.Remove(rightNav1.Single(e => e.Name == "EntityOne 9")); + + leftEntities[4].BranchSkip.Remove(leftEntities[4].BranchSkip.Single(e => e.Name == "Branch 5")); + leftEntities[2].BranchSkip.Add( + context.Set().CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7724; + e.Name = "Z7724"; + })); + + var rightNav2 = (ICollection)context.Entry(rightEntities[2]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + rightNav2.Remove(rightNav2.Single(e => e.Name == "EntityOne 8")); + rightNav2.Add( + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7714; + e.Name = "Z7714"; + })); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities, 24, 14, 55); + + context.SaveChanges(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 14, 55 - 4); + }, + context => + { + var leftEntities = context.Set().Include(e => e.BranchSkip).OrderBy(e => e.Name).ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityOne").OrderBy(e => e.Name).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 14, 55 - 4); + }); + + void ValidateFixup( + DbContext context, + List leftEntities, + List rightEntities, + int leftCount, + int rightCount, + int joinCount) + { + Assert.Equal(leftCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(rightCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(joinCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(leftCount + rightCount + joinCount, context.ChangeTracker.Entries().Count()); + + Assert.Contains(leftEntities[0].BranchSkip, e => e.Name == "Z7721"); + Assert.Contains(leftEntities[0].BranchSkip, e => e.Name == "Z7722"); + Assert.Contains(leftEntities[0].BranchSkip, e => e.Name == "Z7723"); + + var rightNav0 = context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne").CurrentValue!; + Assert.Contains(rightNav0, e => e.Name == "Z7711"); + Assert.Contains(rightNav0, e => e.Name == "Z7712"); + Assert.Contains(rightNav0, e => e.Name == "Z7713"); + + Assert.DoesNotContain(leftEntities[1].BranchSkip, e => e.Name == "Branch 4"); + var rightNav1 = context.Entry(rightEntities[1]).Collection("UnidirectionalEntityOne").CurrentValue!; + Assert.DoesNotContain(rightNav1, e => e.Name == "EntityOne 9"); + + Assert.DoesNotContain(leftEntities[4].BranchSkip, e => e.Name == "Branch 5"); + Assert.Contains(leftEntities[2].BranchSkip, e => e.Name == "Z7724"); + + var allLeft = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + var allRight = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + VerifyRelationshipSnapshots(context, allLeft); + VerifyRelationshipSnapshots(context, allRight); + + var count = 0; + foreach (var left in allLeft) + { + foreach (var right in allRight) + { + var rightNav = context.Entry(right).Collection("UnidirectionalEntityOne").CurrentValue; + if (left.BranchSkip?.Contains(right) == true) + { + Assert.Contains(left, rightNav); + count++; + } + + if (rightNav?.Contains(left) == true) + { + Assert.Contains(right, left.BranchSkip); + count++; + } + } + } + + var deleted = context.ChangeTracker.Entries().Count(e => e.State == EntityState.Deleted); + Assert.Equal(joinCount, (count / 2) + deleted); + } + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_self_with_payload_unidirectional(bool async) + { + List leftKeys = null; + List rightKeys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7711), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7712), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7713) + }; + var rightEntities = new[] + { + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7721), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7722), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7723) + }; + + leftEntities[0].SelfSkipPayloadLeft = CreateCollection(); + + leftEntities[0].SelfSkipPayloadLeft.Add(rightEntities[0]); // 11 - 21 + leftEntities[0].SelfSkipPayloadLeft.Add(rightEntities[1]); // 11 - 22 + leftEntities[0].SelfSkipPayloadLeft.Add(rightEntities[2]); // 11 - 23 + + var rightNav0 = new ObservableCollection(); + context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne").CurrentValue = rightNav0; + + rightNav0.Add(leftEntities[0]); // 21 - 11 (Dupe) + rightNav0.Add(leftEntities[1]); // 21 - 12 + rightNav0.Add(leftEntities[2]); // 21 - 13 + + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } + + ValidateFixup(context, leftEntities, rightEntities, postSave: false); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities, postSave: true); + + leftKeys = leftEntities.Select(e => e.Id).ToList(); + rightKeys = rightEntities.Select(e => e.Id).ToList(); + }, + async context => + { + var queryable = context.Set() + .Where(e => leftKeys.Contains(e.Id) || rightKeys.Contains(e.Id)) + .Include(e => e.SelfSkipPayloadLeft); + + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(6, results.Count); + + var leftEntities = context.ChangeTracker.Entries() + .Select(e => e.Entity) + .Where(e => leftKeys.Contains(e.Id)) + .OrderBy(e => e.Name) + .ToList(); + + var rightEntities = context.ChangeTracker.Entries() + .Select(e => e.Entity) + .Where(e => rightKeys.Contains(e.Id)) + .OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities, postSave: true); + }); + + void ValidateFixup( + DbContext context, + IList leftEntities, + IList rightEntities, + bool postSave) + { + Assert.Equal(11, context.ChangeTracker.Entries().Count()); + Assert.Equal(6, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries().Count()); + + Assert.Equal(3, leftEntities[0].SelfSkipPayloadLeft.Count); + Assert.Single(leftEntities[1].SelfSkipPayloadLeft); + Assert.Single(leftEntities[2].SelfSkipPayloadLeft); + + + Assert.Equal( + 3, context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne").CurrentValue!.Count()); + Assert.Single(context.Entry(rightEntities[1]).Collection("UnidirectionalEntityOne").CurrentValue!); + Assert.Single(context.Entry(rightEntities[2]).Collection("UnidirectionalEntityOne").CurrentValue!); + + var joinEntities = context.ChangeTracker.Entries().Select(e => e.Entity).ToList(); + foreach (var joinEntity in joinEntities) + { + Assert.Equal(joinEntity.Left.Id, joinEntity.LeftId); + Assert.Equal(joinEntity.Right.Id, joinEntity.RightId); + Assert.Contains(joinEntity, joinEntity.Left.JoinSelfPayloadLeft); + Assert.Contains(joinEntity, joinEntity.Right.JoinSelfPayloadRight); + + if (postSave + && SupportsDatabaseDefaults) + { + Assert.True(joinEntity.Payload >= DateTime.Now - new TimeSpan(7, 0, 0, 0)); + } + else + { + Assert.Equal(default, joinEntity.Payload); + } + } + + VerifyRelationshipSnapshots(context, joinEntities); + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + } + } + + [ConditionalFact] + public virtual void Can_update_many_to_many_self_with_payload_unidirectional() + { + List keys = null; + + ExecuteWithStrategyInTransaction( + context => + { + var leftEntities = context.Set().Include("UnidirectionalEntityOne").OrderBy(e => e.Name).ToList(); + var rightEntities = context.Set().Include(e => e.SelfSkipPayloadLeft).OrderBy(e => e.Name) + .ToList(); + + var ones = new[] + { + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7721; + e.Name = "Z7721"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7722; + e.Name = "Z7722"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7723; + e.Name = "Z7723"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7724; + e.Name = "Z7724"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Name = "Z7711"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Name = "Z7712"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Name = "Z7713"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7714; + e.Name = "Z7714"; + }) + }; + + var leftNav0 = (ICollection)context.Entry(leftEntities[0]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + leftNav0.Add(ones[0]); + leftNav0.Add(ones[1]); + leftNav0.Add(ones[2]); + + rightEntities[0].SelfSkipPayloadLeft.Add(ones[4]); + rightEntities[0].SelfSkipPayloadLeft.Add(ones[5]); + rightEntities[0].SelfSkipPayloadLeft.Add(ones[6]); + + var leftNav7 = (ICollection)context.Entry(leftEntities[7]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + leftNav7.Remove(leftNav7.Single(e => e.Name == "EntityOne 6")); + rightEntities[11].SelfSkipPayloadLeft + .Remove(rightEntities[11].SelfSkipPayloadLeft.Single(e => e.Name == "EntityOne 13")); + + var leftNav4 = (ICollection)context.Entry(leftEntities[4]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + leftNav4.Remove(leftNav4.Single(e => e.Name == "EntityOne 18")); + leftNav4.Add(ones[3]); + + rightEntities[4].SelfSkipPayloadLeft.Remove(rightEntities[4].SelfSkipPayloadLeft.Single(e => e.Name == "EntityOne 6")); + rightEntities[4].SelfSkipPayloadLeft.Add(ones[7]); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + keys = ones.Select(e => context.Entry(e).Property(e => e.Id).CurrentValue).ToList(); + + context.Find( + keys[5], + context.Entry(context.UnidirectionalEntityOnes.Local.Single(e => e.Name == "EntityOne 1")).Property(e => e.Id) + .CurrentValue) + .Payload = new DateTime(1973, 9, 3); + + context.Find( + context.Entry(context.UnidirectionalEntityOnes.Local.Single(e => e.Name == "EntityOne 20")).Property(e => e.Id) + .CurrentValue, + context.Entry(context.UnidirectionalEntityOnes.Local.Single(e => e.Name == "EntityOne 16")).Property(e => e.Id) + .CurrentValue) + .Payload = new DateTime(1969, 8, 3); + + ValidateFixup(context, leftEntities, rightEntities, 28, 37, postSave: false); + + context.SaveChanges(); + + keys = ones.Select(e => e.Id).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 28, 37 - 4, postSave: true); + }, + context => + { + var leftEntities = context.Set().Include("UnidirectionalEntityOne").OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.Set().Include(e => e.SelfSkipPayloadLeft).OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 28, 37 - 4, postSave: true); + }); + + void ValidateFixup( + DbContext context, + List leftEntities, + List rightEntities, + int count, + int joinCount, + bool postSave) + { + Assert.Equal(count, context.ChangeTracker.Entries().Count()); + Assert.Equal(joinCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(count + joinCount, context.ChangeTracker.Entries().Count()); + + var leftNav0 = context.Entry(leftEntities[0]).Collection("UnidirectionalEntityOne").CurrentValue!; + Assert.Contains(leftNav0, e => context.Entry(e).Property(e => e.Id).CurrentValue == keys[0]); + Assert.Contains(leftNav0, e => context.Entry(e).Property(e => e.Id).CurrentValue == keys[1]); + Assert.Contains(leftNav0, e => context.Entry(e).Property(e => e.Id).CurrentValue == keys[2]); + + Assert.Contains(rightEntities[0].SelfSkipPayloadLeft, e => context.Entry(e).Property(e => e.Id).CurrentValue == keys[4]); + Assert.Contains(rightEntities[0].SelfSkipPayloadLeft, e => context.Entry(e).Property(e => e.Id).CurrentValue == keys[5]); + Assert.Contains(rightEntities[0].SelfSkipPayloadLeft, e => context.Entry(e).Property(e => e.Id).CurrentValue == keys[6]); + + var leftNav7 = context.Entry(leftEntities[7]).Collection("UnidirectionalEntityOne").CurrentValue!; + Assert.DoesNotContain(leftNav7, e => e.Name == "EntityOne 6"); + Assert.DoesNotContain(rightEntities[11].SelfSkipPayloadLeft, e => e.Name == "EntityOne 13"); + + var leftNav4 = context.Entry(leftEntities[4]).Collection("UnidirectionalEntityOne").CurrentValue!; + Assert.DoesNotContain(leftNav4, e => e.Name == "EntityOne 2"); + Assert.Contains(leftNav4, e => context.Entry(e).Property(e => e.Id).CurrentValue == keys[3]); + + Assert.DoesNotContain(rightEntities[4].SelfSkipPayloadLeft, e => e.Name == "EntityOne 6"); + Assert.Contains(rightEntities[4].SelfSkipPayloadLeft, e => context.Entry(e).Property(e => e.Id).CurrentValue == keys[7]); + + var joinEntries = context.ChangeTracker.Entries().ToList(); + foreach (var joinEntry in joinEntries) + { + var joinEntity = joinEntry.Entity; + Assert.Equal(joinEntity.Left.Id, joinEntity.LeftId); + Assert.Equal(joinEntity.Right.Id, joinEntity.RightId); + Assert.Contains(joinEntity, joinEntity.Left.JoinSelfPayloadLeft); + Assert.Contains(joinEntity, joinEntity.Right.JoinSelfPayloadRight); + + if (joinEntity.LeftId == keys[5] + && joinEntity.RightId == 1) + { + Assert.Equal(postSave ? EntityState.Unchanged : EntityState.Added, joinEntry.State); + Assert.Equal(new DateTime(1973, 9, 3), joinEntity.Payload); + } + else if (joinEntity.LeftId == 20 + && joinEntity.RightId == 20) + { + Assert.Equal(postSave ? EntityState.Unchanged : EntityState.Modified, joinEntry.State); + Assert.Equal(!postSave, joinEntry.Property(e => e.Payload).IsModified); + Assert.Equal(new DateTime(1969, 8, 3), joinEntity.Payload); + } + } + + var allLeft = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + var allRight = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + VerifyRelationshipSnapshots(context, joinEntries.Select(e => e.Entity)); + VerifyRelationshipSnapshots(context, allLeft); + VerifyRelationshipSnapshots(context, allRight); + + var joins = 0; + foreach (var left in allLeft) + { + var leftNav = context.Entry(left).Collection("UnidirectionalEntityOne").CurrentValue; + foreach (var right in allRight) + { + if (leftNav?.Contains(right) == true) + { + Assert.Contains(left, right.SelfSkipPayloadLeft); + joins++; + } + + if (right.SelfSkipPayloadLeft?.Contains(left) == true) + { + Assert.Contains(right, leftNav!); + joins++; + } + } + } + + var deleted = context.ChangeTracker.Entries().Count(e => e.State == EntityState.Deleted); + Assert.Equal(joinCount, (joins / 2) + deleted); + } + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_shared_with_payload_unidirectional(bool async) + { + List keys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7711), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7712), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7713) + }; + var rightEntities = new[] + { + context.UnidirectionalEntityThrees.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7721), + context.UnidirectionalEntityThrees.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7722), + context.UnidirectionalEntityThrees.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7723) + }; + + leftEntities[0].ThreeSkipPayloadFullShared = CreateCollection(); + + leftEntities[0].ThreeSkipPayloadFullShared.Add(rightEntities[0]); // 11 - 21 + leftEntities[0].ThreeSkipPayloadFullShared.Add(rightEntities[1]); // 11 - 22 + leftEntities[0].ThreeSkipPayloadFullShared.Add(rightEntities[2]); // 11 - 23 + + var rightNav0 = (ICollection)context.Entry(rightEntities[0]) + .Collection("UnidirectionalEntityOne1").CurrentValue!; + rightNav0.Add(leftEntities[0]); // 21 - 11 (Dupe) + rightNav0.Add(leftEntities[1]); // 21 - 12 + rightNav0.Add(leftEntities[2]); // 21 - 13 + + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } + + ValidateFixup(context, leftEntities, rightEntities, postSave: false); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities, postSave: true); + + keys = leftEntities.Select(e => e.Id).ToList(); + }, + async context => + { + var queryable = context.Set().Where(e => keys.Contains(e.Id)) + .Include(e => e.ThreeSkipPayloadFullShared); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(3, results.Count); + + var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities, postSave: true); + }); + + void ValidateFixup( + DbContext context, + IList leftEntities, + IList rightEntities, + bool postSave) + { + Assert.Equal(11, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries>().Count()); + + Assert.Equal(3, leftEntities[0].ThreeSkipPayloadFullShared.Count); + Assert.Single(leftEntities[1].ThreeSkipPayloadFullShared); + Assert.Single(leftEntities[2].ThreeSkipPayloadFullShared); + + Assert.Equal(3, context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne1").CurrentValue!.Cast().Count()); + Assert.Single(context.Entry(rightEntities[1]).Collection("UnidirectionalEntityOne1").CurrentValue!.Cast()); + Assert.Single(context.Entry(rightEntities[2]).Collection("UnidirectionalEntityOne1").CurrentValue!.Cast()); + + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + + if (postSave + && SupportsDatabaseDefaults) + { + foreach (var joinEntity in context.ChangeTracker + .Entries>().Select(e => e.Entity).ToList()) + { + Assert.Equal("Generated", joinEntity["Payload"]); + } + } + } + } + + [ConditionalFact] + public virtual void Can_update_many_to_many_shared_with_payload_unidirectional() + { + ExecuteWithStrategyInTransaction( + context => + { + var leftEntities = context.Set().Include(e => e.ThreeSkipPayloadFullShared).OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityOne1").OrderBy(e => e.Name) + .ToList(); + + leftEntities[0].ThreeSkipPayloadFullShared.Add( + context.UnidirectionalEntityThrees.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7721; + e.Name = "Z7721"; + })); + leftEntities[0].ThreeSkipPayloadFullShared.Add( + context.UnidirectionalEntityThrees.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7722; + e.Name = "Z7722"; + })); + leftEntities[0].ThreeSkipPayloadFullShared.Add( + context.UnidirectionalEntityThrees.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7723; + e.Name = "Z7723"; + })); + + var rightNav0 = (ICollection)context.Entry(rightEntities[0]) + .Collection("UnidirectionalEntityOne1").CurrentValue!; + + rightNav0.Add( + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Name = "Z7711"; + })); + rightNav0.Add( + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Name = "Z7712"; + })); + rightNav0.Add( + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Name = "Z7713"; + })); + + leftEntities[2].ThreeSkipPayloadFullShared + .Remove(leftEntities[2].ThreeSkipPayloadFullShared.Single(e => e.Name == "EntityThree 10")); + var rightNav4 = (ICollection)context.Entry(rightEntities[4]) + .Collection("UnidirectionalEntityOne1").CurrentValue!; + rightNav4.Remove(rightNav4.Single(e => e.Name == "EntityOne 6")); + + leftEntities[3].ThreeSkipPayloadFullShared + .Remove(leftEntities[3].ThreeSkipPayloadFullShared.Single(e => e.Name == "EntityThree 17")); + leftEntities[3].ThreeSkipPayloadFullShared + .Add( + context.UnidirectionalEntityThrees.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7724; + e.Name = "Z7724"; + })); + + var rightNav2 = (ICollection)context.Entry(rightEntities[2]) + .Collection("UnidirectionalEntityOne1").CurrentValue!; + rightNav2.Remove(rightNav2.Single(e => e.Name == "EntityOne 12")); + rightNav2.Add( + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7714; + e.Name = "Z7714"; + })); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + var joinSet = context.Set>("UnidirectionalJoinOneToThreePayloadFullShared"); + joinSet.Find(GetEntityOneId(context, "Z7712"), GetEntityThreeId(context, "EntityThree 1"))["Payload"] = "Set!"; + joinSet.Find(GetEntityOneId(context, "EntityOne 20"), GetEntityThreeId(context, "EntityThree 16"))["Payload"] = "Changed!"; + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 48, postSave: false); + + context.SaveChanges(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 48 - 4, postSave: true); + }, + context => + { + var leftEntities = context.Set().Include(e => e.ThreeSkipPayloadFullShared).OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityOne1").OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 48 - 4, postSave: true); + }); + + static int GetEntityOneId(ManyToManyContext context, string name) + => context.Entry(context.UnidirectionalEntityOnes.Local.Single(e => e.Name == name)).Property(e => e.Id).CurrentValue; + + static int GetEntityThreeId(ManyToManyContext context, string name) + => context.Entry(context.UnidirectionalEntityThrees.Local.Single(e => e.Name == name)).Property(e => e.Id).CurrentValue; + + void ValidateFixup( + ManyToManyContext context, + List leftEntities, + List rightEntities, + int leftCount, + int rightCount, + int joinCount, + bool postSave) + { + Assert.Equal(leftCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(rightCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(joinCount, context.ChangeTracker.Entries>().Count()); + Assert.Equal(leftCount + rightCount + joinCount, context.ChangeTracker.Entries().Count()); + + Assert.Contains(leftEntities[0].ThreeSkipPayloadFullShared, e => e.Name == "Z7721"); + Assert.Contains(leftEntities[0].ThreeSkipPayloadFullShared, e => e.Name == "Z7722"); + Assert.Contains(leftEntities[0].ThreeSkipPayloadFullShared, e => e.Name == "Z7723"); + + var rightNav0 = context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne1").CurrentValue!; + Assert.Contains(rightNav0, e => e.Name == "Z7711"); + Assert.Contains(rightNav0, e => e.Name == "Z7712"); + Assert.Contains(rightNav0, e => e.Name == "Z7713"); + + Assert.DoesNotContain(leftEntities[2].ThreeSkipPayloadFullShared, e => e.Name == "EntityThree 10"); + var rightNav4 = context.Entry(rightEntities[4]).Collection("UnidirectionalEntityOne1").CurrentValue!; + Assert.DoesNotContain(rightNav4, e => e.Name == "EntityOne 6"); + + Assert.DoesNotContain(leftEntities[3].ThreeSkipPayloadFullShared, e => e.Name == "EntityThree 17"); + Assert.Contains(leftEntities[3].ThreeSkipPayloadFullShared, e => e.Name == "Z7724"); + + var rightNav2 = context.Entry(rightEntities[2]).Collection("UnidirectionalEntityOne1").CurrentValue!; + Assert.DoesNotContain(rightNav2, e => e.Name == "EntityOne 12"); + Assert.Contains(rightNav2, e => e.Name == "Z7714"); + + var oneId1 = GetEntityOneId(context, "Z7712"); + var threeId1 = GetEntityThreeId(context, "EntityThree 1"); + var oneId2 = GetEntityOneId(context, "EntityOne 20"); + var threeId2 = GetEntityThreeId(context, "EntityThree 20"); + + var joinEntries = context.ChangeTracker.Entries>().ToList(); + foreach (var joinEntry in joinEntries) + { + var joinEntity = joinEntry.Entity; + + if (context.Entry(joinEntity).Property("OneId").CurrentValue == oneId1 + && context.Entry(joinEntity).Property("ThreeId").CurrentValue == threeId1) + { + Assert.Equal(postSave ? EntityState.Unchanged : EntityState.Added, joinEntry.State); + Assert.Equal("Set!", joinEntity["Payload"]); + } + else if (context.Entry(joinEntity).Property("OneId").CurrentValue == oneId2 + && context.Entry(joinEntity).Property("ThreeId").CurrentValue == threeId2) + { + Assert.Equal(postSave ? EntityState.Unchanged : EntityState.Modified, joinEntry.State); + Assert.Equal(!postSave, joinEntry.Property("Payload").IsModified); + Assert.Equal("Changed!", joinEntity["Payload"]); + } + } + + var allLeft = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + var allRight = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + VerifyRelationshipSnapshots(context, joinEntries.Select(e => e.Entity)); + VerifyRelationshipSnapshots(context, allLeft); + VerifyRelationshipSnapshots(context, allRight); + + var count = 0; + foreach (var left in allLeft) + { + foreach (var right in allRight) + { + var rightNav = context.Entry(right).Collection("UnidirectionalEntityOne1").CurrentValue; + if (left.ThreeSkipPayloadFullShared?.Contains(right) == true) + { + Assert.Contains(left, rightNav!); + count++; + } + + if (rightNav?.Contains(left) == true) + { + Assert.Contains(right, left.ThreeSkipPayloadFullShared); + count++; + } + } + } + + var deleted = context.ChangeTracker.Entries>().Count(e => e.State == EntityState.Deleted); + Assert.Equal(joinCount, (count / 2) + deleted); + } + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_shared_unidirectional(bool async) + { + List keys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7711), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7712), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7713) + }; + var rightEntities = new[] + { + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7721), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7722), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7723) + }; + + leftEntities[0].TwoSkipShared = CreateCollection(); + leftEntities[1].TwoSkipShared = CreateCollection(); + leftEntities[2].TwoSkipShared = CreateCollection(); + + leftEntities[0].TwoSkipShared.Add(rightEntities[0]); // 11 - 21 + leftEntities[1].TwoSkipShared.Add(rightEntities[0]); // 12 - 21 + leftEntities[2].TwoSkipShared.Add(rightEntities[0]); // 13 - 21 + leftEntities[0].TwoSkipShared.Add(rightEntities[1]); // 11 - 22 + leftEntities[0].TwoSkipShared.Add(rightEntities[2]); // 11 - 23 + + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + await context.AddRangeAsync(rightEntities[0], rightEntities[1], rightEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + context.AddRange(rightEntities[0], rightEntities[1], rightEntities[2]); + } + + ValidateFixup(context, leftEntities, rightEntities); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities); + + keys = leftEntities.Select(e => e.Id).ToList(); + }, + async context => + { + var queryable = context.Set().Where(e => keys.Contains(e.Id)).Include(e => e.TwoSkipShared); + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(3, results.Count); + + var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities); + }); + + void ValidateFixup(DbContext context, IList leftEntities, IList rightEntities) + { + Assert.Equal(11, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries>().Count()); + + Assert.Equal(3, leftEntities[0].TwoSkipShared.Count); + Assert.Single(leftEntities[1].TwoSkipShared); + Assert.Single(leftEntities[2].TwoSkipShared); + + Assert.Equal(3, context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne").CurrentValue!.Cast().Count()); + Assert.Single(context.Entry(rightEntities[1]).Collection("UnidirectionalEntityOne").CurrentValue!.Cast()); + Assert.Single(context.Entry(rightEntities[2]).Collection("UnidirectionalEntityOne").CurrentValue!.Cast()); + + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + } + } + + [ConditionalFact] + public virtual void Can_update_many_to_many_shared_unidirectional() + { + ExecuteWithStrategyInTransaction( + context => + { + var leftEntities = context.Set().Include(e => e.TwoSkipShared).OrderBy(e => e.Name).ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityOne").OrderBy(e => e.Name).ToList(); + + var twos = new[] + { + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7721; + e.Name = "Z7721"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7722; + e.Name = "Z7722"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7723; + e.Name = "Z7723"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7724; + e.Name = "Z7724"; + }), + }; + + var ones = new[] + { + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Name = "Z7711"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Name = "Z7712"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Name = "Z7713"; + }), + context.UnidirectionalEntityOnes.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7714; + e.Name = "Z7714"; + }), + }; + + leftEntities[0].TwoSkipShared.Add(twos[0]); + leftEntities[0].TwoSkipShared.Add(twos[1]); + leftEntities[0].TwoSkipShared.Add(twos[2]); + + var oneSkipNav0 = (ICollection)context.Entry(rightEntities[0]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + oneSkipNav0.Add(ones[0]); + oneSkipNav0.Add(ones[1]); + oneSkipNav0.Add(ones[2]); + + leftEntities[1].TwoSkipShared.Remove(leftEntities[1].TwoSkipShared.Single(e => e.Name == "EntityTwo 17")); + var oneSkipNav1 = (ICollection)context.Entry(rightEntities[1]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + oneSkipNav1.Remove(oneSkipNav1.Single(e => e.Name == "EntityOne 3")); + + leftEntities[2].TwoSkipShared.Remove(leftEntities[2].TwoSkipShared.Single(e => e.Name == "EntityTwo 18")); + leftEntities[2].TwoSkipShared.Add(twos[3]); + + var oneSkipNav2 = (ICollection)context.Entry(rightEntities[2]) + .Collection("UnidirectionalEntityOne").CurrentValue!; + oneSkipNav2.Remove(oneSkipNav2.Single(e => e.Name == "EntityOne 9")); + oneSkipNav2.Add(ones[3]); + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 53); + + context.SaveChanges(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 49); + }, + context => + { + var leftEntities = context.Set().Include(e => e.TwoSkipShared).OrderBy(e => e.Name).ToList(); + var rightEntities = context.Set().Include("UnidirectionalEntityOne").OrderBy(e => e.Name).ToList(); + + ValidateFixup(context, leftEntities, rightEntities, 24, 24, 49); + }); + + void ValidateFixup( + DbContext context, + List leftEntities, + List rightEntities, + int leftCount, + int rightCount, + int joinCount) + { + Assert.Equal(leftCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(rightCount, context.ChangeTracker.Entries().Count()); + Assert.Equal(joinCount, context.ChangeTracker.Entries>().Count()); + Assert.Equal(leftCount + rightCount + joinCount, context.ChangeTracker.Entries().Count()); + + Assert.Contains(leftEntities[0].TwoSkipShared, e => e.Name == "Z7721"); + Assert.Contains(leftEntities[0].TwoSkipShared, e => e.Name == "Z7722"); + Assert.Contains(leftEntities[0].TwoSkipShared, e => e.Name == "Z7723"); + + var oneSkipNav0 = context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne").CurrentValue!; + Assert.Contains(oneSkipNav0, e => e.Name == "Z7711"); + Assert.Contains(oneSkipNav0, e => e.Name == "Z7712"); + Assert.Contains(oneSkipNav0, e => e.Name == "Z7713"); + + Assert.DoesNotContain(leftEntities[1].TwoSkipShared, e => e.Name == "EntityTwo 17"); + + var oneSkipNav1 = context.Entry(rightEntities[1]).Collection("UnidirectionalEntityOne").CurrentValue!; + Assert.DoesNotContain(oneSkipNav1, e => e.Name == "EntityOne 3"); + + Assert.DoesNotContain(leftEntities[2].TwoSkipShared, e => e.Name == "EntityTwo 18"); + Assert.Contains(leftEntities[2].TwoSkipShared, e => e.Name == "Z7724"); + + var oneSkipNav2 = context.Entry(rightEntities[2]).Collection("UnidirectionalEntityOne").CurrentValue!; + Assert.DoesNotContain(oneSkipNav2, e => e.Name == "EntityOne 9"); + Assert.Contains(oneSkipNav2, e => e.Name == "Z7714"); + + var allLeft = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + var allRight = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name).ToList(); + + VerifyRelationshipSnapshots(context, allLeft); + VerifyRelationshipSnapshots(context, allRight); + + var count = 0; + foreach (var left in allLeft) + { + foreach (var right in allRight) + { + var oneSkipNav = context.Entry(right).Collection("UnidirectionalEntityOne").CurrentValue?.Cast(); + if (left.TwoSkipShared?.Contains(right) == true) + { + Assert.Contains(left, oneSkipNav!); + count++; + } + + if (oneSkipNav?.Contains(left) == true) + { + Assert.Contains(right, left.TwoSkipShared); + count++; + } + } + } + + var deleted = context.ChangeTracker.Entries>().Count(e => e.State == EntityState.Deleted); + Assert.Equal(joinCount, (count / 2) + deleted); + } + } + + [ConditionalTheory] + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + [InlineData(false, false, true)] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(true, true, true)] + public virtual async Task Can_insert_many_to_many_with_suspected_dangling_join_unidirectional( + bool async, + bool useTrackGraph, + bool useDetectChanges) + { + List keys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7711), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7712), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7713) + }; + var rightEntities = new[] + { + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7721), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7722), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7723) + }; + + leftEntities[0].TwoSkip = CreateCollection(); + leftEntities[1].TwoSkip = CreateCollection(); + leftEntities[2].TwoSkip = CreateCollection(); + + if (!useDetectChanges) + { + leftEntities[0].TwoSkip.Add(rightEntities[0]); // 11 - 21 + leftEntities[1].TwoSkip.Add(rightEntities[0]); // 12 - 21 + leftEntities[2].TwoSkip.Add(rightEntities[0]); // 13 - 21 + } + + var joinEntities = new[] + { + context.Set().CreateInstance( + (e, p) => + { + e.One = leftEntities[0]; + e.Two = rightEntities[0]; + }), + context.Set().CreateInstance( + (e, p) => + { + e.One = leftEntities[0]; + e.Two = rightEntities[1]; + }), + context.Set().CreateInstance( + (e, p) => + { + e.One = leftEntities[0]; + e.Two = rightEntities[2]; + }), + context.Set().CreateInstance( + (e, p) => + { + e.One = leftEntities[1]; + e.Two = rightEntities[0]; + }), + context.Set().CreateInstance( + (e, p) => + { + e.One = leftEntities[2]; + e.Two = rightEntities[0]; + }), + }; + + var extra = context.Set().CreateInstance( + (e, p) => + { + e.JoinEntities = new ObservableCollection + { + joinEntities[0], + joinEntities[1], + joinEntities[2], + joinEntities[3], + joinEntities[4], + }; + }); + + rightEntities[0].Extra = extra; + rightEntities[1].Extra = extra; + rightEntities[2].Extra = extra; + + if (useTrackGraph) + { + foreach (var leftEntity in leftEntities) + { + context.ChangeTracker.TrackGraph(leftEntity, n => n.Entry.State = EntityState.Added); + } + } + else + { + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + } + } + + if (useDetectChanges) + { + leftEntities[0].TwoSkip.Add(rightEntities[0]); // 11 - 21 + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + } + + ValidateFixup(context, leftEntities, rightEntities); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities); + + keys = leftEntities.Select(e => e.Id).ToList(); + }, + async context => + { + var queryable = context.Set() + .Where(e => keys.Contains(e.Id)) + .Include(e => e.TwoSkip) + .ThenInclude(e => e.Extra); + + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(3, results.Count); + + var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities); + }); + + void ValidateFixup(DbContext context, IList leftEntities, IList rightEntities) + { + Assert.Equal(12, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries().Count()); + Assert.Single(context.ChangeTracker.Entries()); + + Assert.Equal(3, leftEntities[0].TwoSkip.Count); + Assert.Single(leftEntities[1].TwoSkip); + Assert.Single(leftEntities[2].TwoSkip); + + var nav = context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne1").CurrentValue; + + Assert.Equal(3, context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne1").CurrentValue!.Cast().Count()); + Assert.Single(context.Entry(rightEntities[1]).Collection("UnidirectionalEntityOne1").CurrentValue!); + Assert.Single(context.Entry(rightEntities[2]).Collection("UnidirectionalEntityOne1").CurrentValue!); + + var extra = context.ChangeTracker.Entries().Select(e => e.Entity).Single(); + Assert.Equal(5, extra.JoinEntities.Count); + + foreach (var joinEntity in extra.JoinEntities) + { + Assert.NotNull(joinEntity.One); + Assert.NotNull(joinEntity.Two); + } + + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + } + } + + [ConditionalTheory] + [InlineData(false, false, false)] + [InlineData(true, false, false)] + [InlineData(false, true, false)] + [InlineData(true, true, false)] + [InlineData(false, false, true)] + [InlineData(true, false, true)] + [InlineData(false, true, true)] + [InlineData(true, true, true)] + public virtual async Task Can_insert_many_to_many_with_dangling_join_unidirectional( + bool async, + bool useTrackGraph, + bool useDetectChanges) + { + List keys = null; + + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7711), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7712), + context.UnidirectionalEntityOnes.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7713) + }; + var rightEntities = new[] + { + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7721), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7722), + context.UnidirectionalEntityTwos.CreateInstance((e, p) => e.Id = Fixture.UseGeneratedKeys ? 0 : 7723) + }; + + leftEntities[0].TwoSkip = CreateCollection(); + leftEntities[1].TwoSkip = CreateCollection(); + leftEntities[2].TwoSkip = CreateCollection(); + + if (!useDetectChanges) + { + leftEntities[0].TwoSkip.Add(rightEntities[0]); // 11 - 21 + leftEntities[0].TwoSkip.Add(rightEntities[1]); // 11 - 22 + leftEntities[0].TwoSkip.Add(rightEntities[2]); // 11 - 23 + } + + leftEntities[1].TwoSkip.Add(rightEntities[0]); // 12 - 21 + leftEntities[2].TwoSkip.Add(rightEntities[0]); // 13 - 21 + + if (useTrackGraph) + { + foreach (var leftEntity in leftEntities) + { + context.ChangeTracker.TrackGraph(leftEntity, n => n.Entry.State = EntityState.Added); + } + } + else + { + if (async) + { + await context.AddRangeAsync(leftEntities[0], leftEntities[1], leftEntities[2]); + } + else + { + context.AddRange(leftEntities[0], leftEntities[1], leftEntities[2]); + } + } + + if (useDetectChanges) + { + leftEntities[0].TwoSkip.Add(rightEntities[0]); // 11 - 21 + leftEntities[0].TwoSkip.Add(rightEntities[1]); // 11 - 22 + leftEntities[0].TwoSkip.Add(rightEntities[2]); // 11 - 23 + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + } + + ValidateFixup(context, leftEntities, rightEntities); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities); + + keys = leftEntities.Select(e => e.Id).ToList(); + }, + async context => + { + var queryable = context.Set() + .Where(e => keys.Contains(e.Id)) + .Include(e => e.TwoSkip) + .ThenInclude(e => e.Extra); + + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(3, results.Count); + + var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities); + }); + + void ValidateFixup(DbContext context, IList leftEntities, IList rightEntities) + { + Assert.Equal(11, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries().Count()); + + Assert.Equal(3, leftEntities[0].TwoSkip.Count); + Assert.Single(leftEntities[1].TwoSkip); + Assert.Single(leftEntities[2].TwoSkip); + + Assert.Equal(3, + ((IEnumerable)context.Entry(rightEntities[0]).Collection("UnidirectionalEntityOne1").CurrentValue!).Count()); + Assert.Single((IEnumerable)context.Entry(rightEntities[1]).Collection("UnidirectionalEntityOne1").CurrentValue!); + Assert.Single((IEnumerable)context.Entry(rightEntities[2]).Collection("UnidirectionalEntityOne1").CurrentValue!); + + var joinEntities = context.ChangeTracker.Entries().Select(e => e.Entity).ToList(); + Assert.Equal(5, joinEntities.Count); + + foreach (var joinEntity in joinEntities) + { + Assert.NotNull(joinEntity.One); + Assert.NotNull(joinEntity.Two); + } + + VerifyRelationshipSnapshots(context, leftEntities); + VerifyRelationshipSnapshots(context, rightEntities); + } + } + + [ConditionalFact] + public virtual void Can_insert_update_delete_proxyable_shared_type_entity_type_unidirectional() + { + var id = 0; + + ExecuteWithStrategyInTransaction( + context => + { + var entity = context.Set("PST").CreateInstance( + (e, p) => + { + e["Id"] = Fixture.UseGeneratedKeys ? null : 1; + e["Payload"] = "NewlyAdded"; + }); + + context.Set("PST").Add(entity); + + context.SaveChanges(); + + id = (int)entity["Id"]; + }, + context => + { + var entity = context.Set("PST").Single(e => (int)e["Id"] == id); + + Assert.Equal("NewlyAdded", (string)entity["Payload"]); + + entity["Payload"] = "AlreadyUpdated"; + + if (RequiresDetectChanges) + { + context.ChangeTracker.DetectChanges(); + } + + context.SaveChanges(); + }, + context => + { + var entity = context.Set("PST").Single(e => (int)e["Id"] == id); + + Assert.Equal("AlreadyUpdated", (string)entity["Payload"]); + + context.Set("PST").Remove(entity); + + context.SaveChanges(); + + Assert.False(context.Set("PST").Any(e => (int)e["Id"] == id)); + }); + } + + [ConditionalTheory] + [InlineData(false)] + [InlineData(true)] + public virtual async Task Can_insert_many_to_many_with_navs_by_join_entity_unidirectional(bool async) + { + await ExecuteWithStrategyInTransactionAsync( + async context => + { + var leftEntities = new[] + { + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7711; + e.Name = "Z7711"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7712; + e.Name = "Z7712"; + }), + context.UnidirectionalEntityTwos.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7713; + e.Name = "Z7713"; + }) + }; + var rightEntities = new[] + { + context.UnidirectionalEntityThrees.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7721; + e.Name = "Z7721"; + }), + context.UnidirectionalEntityThrees.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7722; + e.Name = "Z7722"; + }), + context.UnidirectionalEntityThrees.CreateInstance( + (e, p) => + { + e.Id = Fixture.UseGeneratedKeys ? 0 : 7723; + e.Name = "Z7723"; + }) + }; + + var joinEntities = new[] + { + context.Set().CreateInstance( + (e, p) => + { + e.Two = leftEntities[0]; + e.Three = rightEntities[0]; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Two = leftEntities[0]; + e.Three = rightEntities[1]; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Two = leftEntities[0]; + e.Three = rightEntities[2]; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Two = leftEntities[1]; + e.Three = rightEntities[0]; + }), + context.Set().CreateInstance( + (e, p) => + { + e.Two = leftEntities[2]; + e.Three = rightEntities[0]; + }) + }; + + if (async) + { + await context.AddRangeAsync(joinEntities); + } + else + { + context.AddRange(joinEntities); + } + + ValidateFixup(context, leftEntities, rightEntities); + + if (async) + { + await context.SaveChangesAsync(); + } + else + { + context.SaveChanges(); + } + + ValidateFixup(context, leftEntities, rightEntities); + }, + async context => + { + var queryable = context.Set() + .Where(e => e.Name.StartsWith("Z")) + .OrderBy(e => e.Name) + .Include("UnidirectionalEntityThree"); + + var results = async ? await queryable.ToListAsync() : queryable.ToList(); + Assert.Equal(3, results.Count); + + var leftEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + var rightEntities = context.ChangeTracker.Entries().Select(e => e.Entity).OrderBy(e => e.Name) + .ToList(); + + ValidateFixup(context, leftEntities, rightEntities); + }); + + static void ValidateFixup( + DbContext context, + IList leftEntities, + IList rightEntities) + { + Assert.Equal(11, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(3, context.ChangeTracker.Entries().Count()); + Assert.Equal(5, context.ChangeTracker.Entries().Count()); + + Assert.Equal( + 3, context.Entry(leftEntities[0]).Collection("UnidirectionalEntityThree").CurrentValue!.Count()); + Assert.Single(context.Entry(leftEntities[1]).Collection("UnidirectionalEntityThree").CurrentValue!); + Assert.Single(context.Entry(leftEntities[2]).Collection("UnidirectionalEntityThree").CurrentValue!); + + Assert.Equal(3, rightEntities[0].TwoSkipFull.Count); + Assert.Single(rightEntities[1].TwoSkipFull); + Assert.Single(rightEntities[2].TwoSkipFull); + + foreach (var joinEntity in context.ChangeTracker.Entries().Select(e => e.Entity).ToList()) + { + Assert.Equal(joinEntity.Two.Id, joinEntity.TwoId); + Assert.Equal(joinEntity.Three.Id, joinEntity.ThreeId); + Assert.Contains(joinEntity, joinEntity.Two.JoinThreeFull); + Assert.Contains(joinEntity, joinEntity.Three.JoinTwoFull); + } + } + } +} diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs index d3e32b26210..7324933f48b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs @@ -286,6 +286,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .Entity() .Property(e => e.Payload) .HasDefaultValue("Generated"); + + modelBuilder + .Entity() + .Property(e => e.Payload) + .HasDefaultValueSql("GETUTCDATE()"); + + modelBuilder + .SharedTypeEntity>("UnidirectionalJoinOneToThreePayloadFullShared") + .IndexerProperty("Payload") + .HasDefaultValue("Generated"); + + modelBuilder + .Entity() + .Property(e => e.Payload) + .HasDefaultValue("Generated"); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingGeneratedKeysSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingGeneratedKeysSqlServerTest.cs index be089330539..4e5d992a951 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingGeneratedKeysSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingGeneratedKeysSqlServerTest.cs @@ -32,6 +32,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.SharedTypeEntity("PST").IndexerProperty("Id").ValueGeneratedOnAdd(); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(e => e.Key1).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxyGeneratedKeysSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxyGeneratedKeysSqlServerTest.cs index f61fe2cd400..c7396e13213 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxyGeneratedKeysSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxyGeneratedKeysSqlServerTest.cs @@ -28,6 +28,15 @@ public override void Can_insert_update_delete_shared_type_entity_type() // Mutable properties aren't proxyable on Dictionary } + public override Task Can_insert_many_to_many_shared_with_payload_unidirectional(bool async) + // Mutable properties aren't proxyable on Dictionary + => Task.CompletedTask; + + public override void Can_update_many_to_many_shared_with_payload_unidirectional() + { + // Mutable properties aren't proxyable on Dictionary + } + protected override bool RequiresDetectChanges => false; @@ -52,6 +61,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .SharedTypeEntity>("JoinOneToThreePayloadFullShared") .Ignore("Payload"); // Mutable properties aren't proxyable on Dictionary + modelBuilder + .SharedTypeEntity>("UnidirectionalJoinOneToThreePayloadFullShared") + .Ignore("Payload"); // Mutable properties aren't proxyable on Dictionary + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); @@ -60,6 +73,12 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.SharedTypeEntity("PST").IndexerProperty("Id").ValueGeneratedOnAdd(); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(e => e.Key1).ValueGeneratedOnAdd(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedOnAdd(); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxySqlServerTest.cs index 4393fa74d87..a1e55de13a5 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingProxySqlServerTest.cs @@ -25,6 +25,15 @@ public override void Can_insert_update_delete_shared_type_entity_type() // Mutable properties aren't proxyable on Dictionary } + public override Task Can_insert_many_to_many_shared_with_payload_unidirectional(bool async) + // Mutable properties aren't proxyable on Dictionary + => Task.CompletedTask; + + public override void Can_update_many_to_many_shared_with_payload_unidirectional() + { + // Mutable properties aren't proxyable on Dictionary + } + protected override bool RequiresDetectChanges => false; @@ -45,6 +54,10 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder .SharedTypeEntity>("JoinOneToThreePayloadFullShared") .Ignore("Payload"); // Mutable properties aren't proxyable on Dictionary + + modelBuilder + .SharedTypeEntity>("UnidirectionalJoinOneToThreePayloadFullShared") + .Ignore("Payload"); // Mutable properties aren't proxyable on Dictionary } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingSqlServerTestBase.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingSqlServerTestBase.cs index b792fc81258..5d102801b22 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingSqlServerTestBase.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyTrackingSqlServerTestBase.cs @@ -39,6 +39,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .Entity() .Property(e => e.Payload) .HasDefaultValue("Generated"); + + modelBuilder + .Entity() + .Property(e => e.Payload) + .HasDefaultValueSql("GETUTCDATE()"); + + modelBuilder + .SharedTypeEntity>("UnidirectionalJoinOneToThreePayloadFullShared") + .IndexerProperty("Payload") + .HasDefaultValue("Generated"); + + modelBuilder + .Entity() + .Property(e => e.Payload) + .HasDefaultValue("Generated"); } } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs index 9553fd55f73..d763ba26806 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyNoTrackingQuerySqlServerTest.cs @@ -15,7 +15,7 @@ public TPCManyToManyNoTrackingQuerySqlServerTest(TPCManyToManyQuerySqlServerFixt protected override bool CanExecuteQueryString => true; - [ConditionalFact] + [ConditionalFact(Skip = "TODOU: Needs #27493")] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs index 23cc5f7c8e5..f427f40cb30 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCManyToManyQuerySqlServerTest.cs @@ -15,7 +15,7 @@ public TPCManyToManyQuerySqlServerTest(TPCManyToManyQuerySqlServerFixture fixtur protected override bool CanExecuteQueryString => true; - [ConditionalFact] + [ConditionalFact(Skip = "TODOU: Needs #27493")] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs index f82f2423451..eb47d14ccb1 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyNoTrackingQuerySqlServerTest.cs @@ -16,7 +16,7 @@ public TPTManyToManyNoTrackingQuerySqlServerTest(TPTManyToManyQuerySqlServerFixt protected override bool CanExecuteQueryString => true; - [ConditionalFact] + [ConditionalFact(Skip = "TODOU: Needs #27493")] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs index 4ee21ee5da9..ef2075300fa 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTManyToManyQuerySqlServerTest.cs @@ -15,7 +15,7 @@ public TPTManyToManyQuerySqlServerTest(TPTManyToManyQuerySqlServerFixture fixtur protected override bool CanExecuteQueryString => true; - [ConditionalFact] + [ConditionalFact(Skip = "TODOU: Needs #27493")] public virtual void Check_all_tests_overridden() => TestHelpers.AssertAllMethodsOverridden(GetType()); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerFixture.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerFixture.cs index fc7272c6f8e..c5acf212b5d 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerFixture.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerFixture.cs @@ -24,6 +24,11 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity().ToTable(tb => tb.IsTemporal()); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); @@ -39,6 +44,20 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con modelBuilder.Entity().HasBaseType(); modelBuilder.Entity().HasBaseType(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().HasKey( + e => new + { + e.Key1, + e.Key2, + e.Key3 + }); + modelBuilder.Entity().Property(e => e.Id).ValueGeneratedNever(); + modelBuilder.Entity().HasBaseType(); + modelBuilder.Entity().HasBaseType(); + modelBuilder.Entity() .HasMany(e => e.Collection) .WithOne(e => e.CollectionInverse) @@ -168,6 +187,137 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con })) .ToTable(tb => tb.IsTemporal()); + modelBuilder.Entity() + .HasMany(e => e.Collection) + .WithOne(e => e.CollectionInverse) + .HasForeignKey(e => e.CollectionInverseId); + + modelBuilder.Entity() + .HasOne(e => e.Reference) + .WithOne(e => e.ReferenceInverse) + .HasForeignKey(e => e.ReferenceInverseId); + + modelBuilder.Entity() + .HasMany(e => e.TwoSkipShared) + .WithMany() + .UsingEntity(t => t.ToTable(tb => tb.IsTemporal())); + + // Nav:2 Payload:No Join:Concrete Extra:None + modelBuilder.Entity() + .HasMany(e => e.TwoSkip) + .WithMany() + .UsingEntity() + .ToTable(tb => tb.IsTemporal()); + + // Nav:6 Payload:Yes Join:Concrete Extra:None + modelBuilder.Entity() + .HasMany() + .WithMany() + .UsingEntity( + r => r.HasOne(x => x.Three).WithMany(e => e.JoinOnePayloadFull), + l => l.HasOne(x => x.One).WithMany(e => e.JoinThreePayloadFull)) + .ToTable(tb => tb.IsTemporal()); + + // Nav:4 Payload:Yes Join:Shared Extra:None + modelBuilder.Entity() + .HasMany(e => e.ThreeSkipPayloadFullShared) + .WithMany() + .UsingEntity>( + "UnidirectionalJoinOneToThreePayloadFullShared", + r => r.HasOne().WithMany(e => e.JoinOnePayloadFullShared).HasForeignKey("ThreeId"), + l => l.HasOne().WithMany(e => e.JoinThreePayloadFullShared).HasForeignKey("OneId")) + .ToTable(tb => tb.IsTemporal()) + .IndexerProperty("Payload"); + + // Nav:6 Payload:Yes Join:Concrete Extra:Self-Ref + modelBuilder.Entity() + .HasMany(e => e.SelfSkipPayloadLeft) + .WithMany() + .UsingEntity( + l => l.HasOne(x => x.Left).WithMany(x => x.JoinSelfPayloadLeft), + r => r.HasOne(x => x.Right).WithMany(x => x.JoinSelfPayloadRight)) + .ToTable(tb => tb.IsTemporal()); + + // Nav:2 Payload:No Join:Concrete Extra:Inheritance + modelBuilder.Entity() + .HasMany(e => e.BranchSkip) + .WithMany() + .UsingEntity() + .ToTable(tb => tb.IsTemporal()); + + modelBuilder.Entity() + .HasOne(e => e.Reference) + .WithOne(e => e.ReferenceInverse) + .HasForeignKey(e => e.ReferenceInverseId); + + modelBuilder.Entity() + .HasMany(e => e.Collection) + .WithOne(e => e.CollectionInverse) + .HasForeignKey(e => e.CollectionInverseId); + + // Nav:6 Payload:No Join:Concrete Extra:None + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.TwoSkipFull) + .UsingEntity( + r => r.HasOne(x => x.Three).WithMany(e => e.JoinTwoFull), + l => l.HasOne(x => x.Two).WithMany(e => e.JoinThreeFull)) + .ToTable(tb => tb.IsTemporal()); + + // Nav:2 Payload:No Join:Shared Extra:Self-ref + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.SelfSkipSharedRight) + .UsingEntity(t => t.ToTable(tb => tb.IsTemporal())); + + // Nav:2 Payload:No Join:Shared Extra:CompositeKey + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.TwoSkipShared) + .UsingEntity(t => t.ToTable(tb => tb.IsTemporal())); + + // Nav:6 Payload:No Join:Concrete Extra:CompositeKey + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.ThreeSkipFull) + .UsingEntity( + l => l.HasOne(x => x.Composite).WithMany(x => x.JoinThreeFull).HasForeignKey( + e => new + { + e.CompositeId1, + e.CompositeId2, + e.CompositeId3 + }).IsRequired(), + r => r.HasOne(x => x.Three).WithMany(x => x.JoinCompositeKeyFull).IsRequired()) + .ToTable(tb => tb.IsTemporal()); + + // Nav:2 Payload:No Join:Shared Extra:Inheritance + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.ThreeSkipShared) + .UsingEntity(t => t.ToTable(tb => tb.IsTemporal())); + + // Nav:2 Payload:No Join:Shared Extra:Inheritance,CompositeKey + modelBuilder.Entity() + .HasMany(e => e.RootSkipShared) + .WithMany() + .UsingEntity(t => t.ToTable(tb => tb.IsTemporal())); + + // Nav:6 Payload:No Join:Concrete Extra:Inheritance,CompositeKey + modelBuilder.Entity() + .HasMany() + .WithMany(e => e.CompositeKeySkipFull) + .UsingEntity( + r => r.HasOne(x => x.Leaf).WithMany(x => x.JoinCompositeKeyFull), + l => l.HasOne(x => x.Composite).WithMany(x => x.JoinLeafFull).HasForeignKey( + e => new + { + e.CompositeId1, + e.CompositeId2, + e.CompositeId3 + })) + .ToTable(tb => tb.IsTemporal()); + modelBuilder.SharedTypeEntity( "PST", b => { @@ -187,6 +337,11 @@ protected override void Seed(ManyToManyContext context) context.RemoveRange(context.ChangeTracker.Entries().Where(e => e.Entity is EntityOne).Select(e => e.Entity)); context.RemoveRange(context.ChangeTracker.Entries().Where(e => e.Entity is EntityCompositeKey).Select(e => e.Entity)); context.RemoveRange(context.ChangeTracker.Entries().Where(e => e.Entity is EntityRoot).Select(e => e.Entity)); + context.RemoveRange(context.ChangeTracker.Entries().Where(e => e.Entity is UnidirectionalEntityThree).Select(e => e.Entity)); + context.RemoveRange(context.ChangeTracker.Entries().Where(e => e.Entity is UnidirectionalEntityTwo).Select(e => e.Entity)); + context.RemoveRange(context.ChangeTracker.Entries().Where(e => e.Entity is UnidirectionalEntityOne).Select(e => e.Entity)); + context.RemoveRange(context.ChangeTracker.Entries().Where(e => e.Entity is UnidirectionalEntityCompositeKey).Select(e => e.Entity)); + context.RemoveRange(context.ChangeTracker.Entries().Where(e => e.Entity is UnidirectionalEntityRoot).Select(e => e.Entity)); context.SaveChanges(); var tableNames = new List @@ -209,6 +364,24 @@ protected override void Seed(ManyToManyContext context) "EntityTwoEntityTwo", "EntityCompositeKeyEntityTwo", "JoinTwoToThree", + "UnidirectionalEntityCompositeKeys", + "UnidirectionalEntityOneUnidirectionalEntityTwo", + "UnidirectionalEntityOnes", + "UnidirectionalEntityTwos", + "UnidirectionalEntityThrees", + "UnidirectionalEntityRoots", + "UnidirectionalEntityRootUnidirectionalEntityThree", + "UnidirectionalJoinCompositeKeyToLeaf", + "UnidirectionalEntityCompositeKeyUnidirectionalEntityRoot", + "UnidirectionalJoinOneSelfPayload", + "UnidirectionalJoinOneToBranch", + "UnidirectionalJoinOneToThreePayloadFull", + "UnidirectionalJoinOneToThreePayloadFullShared", + "UnidirectionalJoinOneToTwo", + "UnidirectionalJoinThreeToCompositeKeyFull", + "UnidirectionalEntityTwoUnidirectionalEntityTwo", + "UnidirectionalEntityCompositeKeyUnidirectionalEntityTwo", + "UnidirectionalJoinTwoToThree", }; foreach (var tableName in tableNames) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs index d7efe4be2ba..e3740127789 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalManyToManyQuerySqlServerTest.cs @@ -28,6 +28,13 @@ protected override Expression RewriteServerQueryExpression(Expression serverQuer typeof(EntityRoot), typeof(EntityBranch), typeof(EntityLeaf), + typeof(UnidirectionalEntityOne), + typeof(UnidirectionalEntityTwo), + typeof(UnidirectionalEntityThree), + typeof(UnidirectionalEntityCompositeKey), + typeof(UnidirectionalEntityRoot), + typeof(UnidirectionalEntityBranch), + typeof(UnidirectionalEntityLeaf), }; var rewriter = new TemporalPointInTimeQueryRewriter(Fixture.ChangesDate, temporalEntityTypes); diff --git a/test/EFCore.Sqlite.FunctionalTests/ManyToManyLoadSqliteTestBase.cs b/test/EFCore.Sqlite.FunctionalTests/ManyToManyLoadSqliteTestBase.cs index 98e9384d185..53591c0e81c 100644 --- a/test/EFCore.Sqlite.FunctionalTests/ManyToManyLoadSqliteTestBase.cs +++ b/test/EFCore.Sqlite.FunctionalTests/ManyToManyLoadSqliteTestBase.cs @@ -36,6 +36,21 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .Entity() .Property(e => e.Payload) .HasDefaultValue("Generated"); + + modelBuilder + .Entity() + .Property(e => e.Payload) + .HasDefaultValueSql("GETUTCDATE()"); + + modelBuilder + .SharedTypeEntity>("UnidirectionalJoinOneToThreePayloadFullShared") + .IndexerProperty("Payload") + .HasDefaultValue("Generated"); + + modelBuilder + .Entity() + .Property(e => e.Payload) + .HasDefaultValue("Generated"); } } } diff --git a/test/EFCore.Sqlite.FunctionalTests/ManyToManyTrackingSqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/ManyToManyTrackingSqliteTest.cs index 95c638b3dd9..b3c98e04664 100644 --- a/test/EFCore.Sqlite.FunctionalTests/ManyToManyTrackingSqliteTest.cs +++ b/test/EFCore.Sqlite.FunctionalTests/ManyToManyTrackingSqliteTest.cs @@ -38,6 +38,22 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con .Entity() .Property(e => e.Payload) .HasDefaultValue("Generated"); + + modelBuilder + .Entity() + .Property(e => e.Payload) + .HasDefaultValueSql("CURRENT_TIMESTAMP"); + + modelBuilder + .SharedTypeEntity>("UnidirectionalJoinOneToThreePayloadFullShared") + .IndexerProperty("Payload") + .HasDefaultValue("Generated"); + + modelBuilder + .Entity() + .Property(e => e.Payload) + .HasDefaultValue("Generated"); + } } } diff --git a/test/EFCore.Tests/ChangeTracking/Internal/ShadowFixupTest.cs b/test/EFCore.Tests/ChangeTracking/Internal/ShadowFixupTest.cs index 374373d43fc..c8dab6434a5 100644 --- a/test/EFCore.Tests/ChangeTracking/Internal/ShadowFixupTest.cs +++ b/test/EFCore.Tests/ChangeTracking/Internal/ShadowFixupTest.cs @@ -93,7 +93,7 @@ private void Add_principal_and_dependent_one_to_many( if (setToDependent) { - var collection = new HashSet { dependent }; + var collection = new HashSet { dependent }; principalEntry.Collection("Products").CurrentValue = collection; } diff --git a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs index 2a501d2f768..694508715e1 100644 --- a/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs +++ b/test/EFCore.Tests/ModelBuilding/ManyToManyTestBase.cs @@ -445,44 +445,6 @@ public virtual void Throws_for_conflicting_many_to_one_on_right() .HasMany(o => o.Categories).WithMany(c => c.Products)).Message); } - [ConditionalFact] - public virtual void Throws_for_many_to_many_with_only_one_navigation_configured() - { - var modelBuilder = CreateModelBuilder(); - - Assert.Equal( - CoreStrings.MissingInverseManyToManyNavigation( - nameof(ManyToManyNavPrincipal), - nameof(NavDependent)), - Assert.Throws( - () => modelBuilder.Entity() - .HasMany( /* leaving empty causes the exception */) - .WithMany(d => d.ManyToManyPrincipals)).Message); - } - - [ConditionalFact] - public virtual void Throws_for_many_to_many_with_a_shadow_navigation() - { - var modelBuilder = CreateModelBuilder(); - - modelBuilder.Ignore(); - modelBuilder.Ignore(); - modelBuilder.Entity().Ignore(d => d.ManyToManyPrincipals); - - modelBuilder.Entity() - .HasMany(d => d.Dependents) - .WithMany("Shadow"); - - Assert.Equal( - CoreStrings.ShadowManyToManyNavigation( - nameof(NavDependent), - "Shadow", - nameof(ManyToManyNavPrincipal), - nameof(ManyToManyNavPrincipal.Dependents)), - Assert.Throws( - () => modelBuilder.FinalizeModel()).Message); - } - [ConditionalFact] public virtual void Throws_for_self_ref_with_same_navigation() {