From 45d27f2c3cb617d65164335bcd3f948d8309e7a0 Mon Sep 17 00:00:00 2001 From: Arthur Vickers Date: Sat, 5 Dec 2020 16:32:29 -0800 Subject: [PATCH] Updated to handle case where same navigation is included twice, the first time with setLoaded = false. --- ...ionBindingRemovingExpressionVisitorBase.cs | 2 + ....CustomShaperCompilingExpressionVisitor.cs | 2 + ...sitor.ShaperProcessingExpressionVisitor.cs | 4 + src/EFCore/Query/IncludeExpression.cs | 17 +- ...ingExpressionVisitor.ExpressionVisitors.cs | 4 +- ...nExpandingExpressionVisitor.Expressions.cs | 25 +- .../NavigationExpandingExpressionVisitor.cs | 19 +- .../ManyToManyLoadTestBase.cs | 311 ++++++++++++++++++ .../ManyToManyLoadSqlServerTest.cs | 171 ++++++++++ 9 files changed, 525 insertions(+), 30 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs index 79b86f5eaeb..bb1404170b9 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosShapedQueryCompilingExpressionVisitor.CosmosProjectionBindingRemovingExpressionVisitorBase.cs @@ -393,7 +393,9 @@ private void AddInclude( Expression.Constant(inverseNavigation, typeof(INavigation)), Expression.Constant(fixup), Expression.Constant(initialize, typeof(Action<>).MakeGenericType(includingClrType)), +#pragma warning disable EF1001 // Internal EF Core API usage. Expression.Constant(includeExpression.SetLoaded))); +#pragma warning restore EF1001 // Internal EF Core API usage. } private static readonly MethodInfo _includeReferenceMethodInfo diff --git a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs index dc114f625c3..ec8fb29764a 100644 --- a/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Internal/InMemoryShapedQueryCompilingExpressionVisitor.CustomShaperCompilingExpressionVisitor.cs @@ -183,7 +183,9 @@ protected override Expression VisitExtension(Expression extensionExpression) GenerateFixup( includingClrType, relatedEntityClrType, includeExpression.Navigation, inverseNavigation).Compile()), Expression.Constant(_tracking), +#pragma warning disable EF1001 // Internal EF Core API usage. Expression.Constant(includeExpression.SetLoaded)); +#pragma warning restore EF1001 // Internal EF Core API usage. } return Expression.Call( diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index e27ee90365c..ab21ec8b8ca 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(navigation), Expression.Constant(navigation.GetCollectionAccessor()), Expression.Constant(_isTracking), +#pragma warning disable EF1001 // Internal EF Core API usage. Expression.Constant(includeExpression.SetLoaded))); +#pragma warning restore EF1001 // Internal EF Core API usage. var relatedEntityType = innerShaper.ReturnType; var inverseNavigation = navigation.Inverse; @@ -631,7 +633,9 @@ protected override Expression VisitExtension(Expression extensionExpression) Expression.Constant(navigation), Expression.Constant(navigation.GetCollectionAccessor()), Expression.Constant(_isTracking), +#pragma warning disable EF1001 // Internal EF Core API usage. Expression.Constant(includeExpression.SetLoaded))); +#pragma warning restore EF1001 // Internal EF Core API usage. var relatedEntityType = innerShaper.ReturnType; var inverseNavigation = navigation.Inverse; diff --git a/src/EFCore/Query/IncludeExpression.cs b/src/EFCore/Query/IncludeExpression.cs index 9a94f6cb7f4..049b35bdf78 100644 --- a/src/EFCore/Query/IncludeExpression.cs +++ b/src/EFCore/Query/IncludeExpression.cs @@ -4,6 +4,7 @@ using System; using System.Linq.Expressions; using JetBrains.Annotations; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Utilities; @@ -36,12 +37,12 @@ public IncludeExpression( } /// - /// Creates a new instance of the class. + /// 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. /// - /// An expression to get entity which is performing include. - /// An expression to get included navigation element. - /// The navigation for this include operation. - /// True if the navigation will be marked as loaded. + [EntityFrameworkInternal] public IncludeExpression( [NotNull] Expression entityExpression, [NotNull] Expression navigationExpression, @@ -77,8 +78,12 @@ public IncludeExpression( public virtual INavigationBase Navigation { get; } /// - /// True if the navigation will be marked as loaded. + /// 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. /// + [EntityFrameworkInternal] public virtual bool SetLoaded { get; } /// diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs index 7bad316b5ed..65aefc9b922 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.ExpressionVisitors.cs @@ -825,7 +825,9 @@ private Expression ExpandIncludesHelper(Expression root, EntityReference entityR } } - result = new IncludeExpression(result, included, navigationBase, entityReference.SetLoaded); +#pragma warning disable EF1001 // Internal EF Core API usage. + result = new IncludeExpression(result, included, navigationBase, kvp.Value.SetLoaded); +#pragma warning restore EF1001 // Internal EF Core API usage. } return result; diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs index e7c14c3790d..e23417f7eab 100644 --- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs +++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.Expressions.cs @@ -18,7 +18,7 @@ private sealed class EntityReference : Expression, IPrintableExpression public EntityReference(IEntityType entityType) { EntityType = entityType; - IncludePaths = new IncludeTreeNode(entityType, this); + IncludePaths = new IncludeTreeNode(entityType, this, setLoaded: true); } public IEntityType EntityType { get; } @@ -29,7 +29,6 @@ public EntityReference(IEntityType entityType) public bool IsOptional { get; private set; } public IncludeTreeNode IncludePaths { get; private set; } public IncludeTreeNode LastIncludeTreeNode { get; private set; } - public bool SetLoaded { get; private set; } = true; public override ExpressionType NodeType => ExpressionType.Extension; @@ -58,9 +57,6 @@ public void SetLastInclude(IncludeTreeNode lastIncludeTree) public void MarkAsOptional() => IsOptional = true; - public void SuppressSettingLoaded() - => SetLoaded = false; - void IPrintableExpression.Print(ExpressionPrinter expressionPrinter) { Check.NotNull(expressionPrinter, nameof(expressionPrinter)); @@ -90,23 +86,30 @@ private sealed class IncludeTreeNode : Dictionary GetOutgoingEagerLoadedNavigations(IE .Concat(entityType.GetDerivedSkipNavigations()) .Where(n => n.IsEagerLoaded); - private IncludeTreeNode PopulateIncludeTree(IncludeTreeNode includeTreeNode, Expression expression) + private IncludeTreeNode PopulateIncludeTree(IncludeTreeNode includeTreeNode, Expression expression, bool setLoaded) { switch (expression) { @@ -1803,7 +1798,7 @@ private IncludeTreeNode PopulateIncludeTree(IncludeTreeNode includeTreeNode, Exp case MemberExpression memberExpression: var innerExpression = memberExpression.Expression.UnwrapTypeConversion(out var convertedType); - var innerIncludeTreeNode = PopulateIncludeTree(includeTreeNode, innerExpression); + var innerIncludeTreeNode = PopulateIncludeTree(includeTreeNode, innerExpression, setLoaded); var entityType = innerIncludeTreeNode.EntityType; if (convertedType != null) { @@ -1819,7 +1814,7 @@ private IncludeTreeNode PopulateIncludeTree(IncludeTreeNode includeTreeNode, Exp var navigation = entityType.FindNavigation(memberExpression.Member); if (navigation != null) { - var addedNode = innerIncludeTreeNode.AddNavigation(navigation); + var addedNode = innerIncludeTreeNode.AddNavigation(navigation, setLoaded); // This is to add eager Loaded navigations when owner type is included. PopulateEagerLoadedNavigations(addedNode); @@ -1830,7 +1825,7 @@ private IncludeTreeNode PopulateIncludeTree(IncludeTreeNode includeTreeNode, Exp var skipNavigation = entityType.FindSkipNavigation(memberExpression.Member); if (skipNavigation != null) { - var addedNode = innerIncludeTreeNode.AddNavigation(skipNavigation); + var addedNode = innerIncludeTreeNode.AddNavigation(skipNavigation, setLoaded); // This is to add eager Loaded navigations when owner type is included. PopulateEagerLoadedNavigations(addedNode); diff --git a/test/EFCore.Specification.Tests/ManyToManyLoadTestBase.cs b/test/EFCore.Specification.Tests/ManyToManyLoadTestBase.cs index f195d3086eb..2202e16d810 100644 --- a/test/EFCore.Specification.Tests/ManyToManyLoadTestBase.cs +++ b/test/EFCore.Specification.Tests/ManyToManyLoadTestBase.cs @@ -770,6 +770,317 @@ public virtual void Query_collection_for_detached_throws(QueryTrackingBehavior q Assert.Throws(() => collectionEntry.Query()).Message); } + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Load_collection_using_Query_with_Include(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(e => e.ThreeSkipFull).ToListAsync() + : collectionEntry.Query().Include(e => e.ThreeSkipFull).ToList(); + + Assert.False(collectionEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkipShared) + { + Assert.False(context.Entry(entityTwo).Collection(e => e.OneSkipShared).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, right.OneSkipShared); + foreach (var three in right.ThreeSkipFull) + { + 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(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(e => e.OneSkipShared); + var children = async + ? await queryable.ToListAsync() + : queryable.ToList(); + + Assert.False(collectionEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkipShared) + { + Assert.True(context.Entry(entityTwo).Collection(e => e.OneSkipShared).IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(3, left.TwoSkipShared.Count); + foreach (var right in left.TwoSkipShared) + { + Assert.Contains(left, right.OneSkipShared); + } + + Assert.Equal(children, left.TwoSkipShared.ToList()); + Assert.Equal(7, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Load_collection_using_Query_with_Include_for_same_collection(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(e => e.OneSkipShared).ThenInclude(e => e.TwoSkipShared); + var children = async + ? await queryable.ToListAsync() + : queryable.ToList(); + + Assert.True(collectionEntry.IsLoaded); + foreach (var entityTwo in left.TwoSkipShared) + { + Assert.True(context.Entry(entityTwo).Collection(e => e.OneSkipShared).IsLoaded); + } + + RecordLog(); + context.ChangeTracker.LazyLoadingEnabled = false; + + Assert.Equal(3, left.TwoSkipShared.Count); + foreach (var right in left.TwoSkipShared) + { + Assert.Contains(left, right.OneSkipShared); + } + + Assert.Equal(children, left.TwoSkipShared.ToList()); + Assert.Equal(7, context.ChangeTracker.Entries().Count()); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Load_collection_using_Query_with_filtered_Include(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(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(e => e.OneSkipShared).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, right.OneSkipShared); + 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] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Load_collection_using_Query_with_filtered_Include_and_projection(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(e => e.ThreeSkipFull.Where(e => e.Id == 13 || e.Id == 11)) + .Select(e => new { e.Id, e.Name, Count1 = e.OneSkipShared.Count, 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.Equal(1, context.ChangeTracker.Entries().Count()); + + Assert.Equal(3, projected.Count); + + Assert.Equal(10, projected[0].Id); + Assert.Equal("EntityTwo 10", projected[0].Name); + Assert.Equal(3, projected[0].Count1); + Assert.Equal(1, projected[0].Count3); + + Assert.Equal(11, projected[1].Id); + Assert.Equal("EntityTwo 11", projected[1].Name); + Assert.Equal(2, projected[1].Count1); + Assert.Equal(4, projected[1].Count3); + + Assert.Equal(16, projected[2].Id); + Assert.Equal("EntityTwo 16", projected[2].Name); + Assert.Equal(3, projected[2].Count1); + Assert.Equal(2, projected[2].Count3); + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Load_collection_using_Query_with_join(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(bool async) + { + using var context = Fixture.CreateContext(); + + var queryable = context.EntityOnes.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).Collection(e => e.OneSkip).IsLoaded); + Assert.Same(left, right.OneSkip.Single()); + } + } + + [ConditionalTheory] + [InlineData(true)] + [InlineData(false)] + public virtual async Task Query_with_filtered_Include_marks_only_left_as_loaded(bool async) + { + using var context = Fixture.CreateContext(); + + var queryable = context.EntityOnes.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(e => e.OneSkip).IsLoaded); + Assert.Same(left, right.OneSkip.Single()); + } + } + protected virtual void ClearLog() { } diff --git a/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs index 68a402bfc7e..53b31ab5258 100644 --- a/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/ManyToManyLoadSqlServerTest.cs @@ -42,6 +42,177 @@ FROM [JoinOneToTwo] AS [j0] ORDER BY [e].[Id], [t].[OneId], [t].[TwoId], [t].[Id], [t0].[OneId], [t0].[TwoId], [t0].[Id]"); } + + public override async Task Load_collection_using_Query_with_Include_for_inverse(bool async) + { + await base.Load_collection_using_Query_with_Include_for_inverse(async); + + AssertSql( + @"@__p_0='3' + +SELECT [t].[Id], [t].[CollectionInverseId], [t].[Name], [t].[ReferenceInverseId], [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t0].[EntityOneId], [t0].[EntityTwoId], [t0].[Id], [t0].[Name] +FROM [EntityOnes] AS [e] +INNER JOIN ( + SELECT [e1].[Id], [e1].[CollectionInverseId], [e1].[Name], [e1].[ReferenceInverseId], [e0].[EntityOneId], [e0].[EntityTwoId] + FROM [EntityOneEntityTwo] AS [e0] + INNER JOIN [EntityTwos] AS [e1] ON [e0].[EntityTwoId] = [e1].[Id] +) AS [t] ON [e].[Id] = [t].[EntityOneId] +LEFT JOIN ( + SELECT [e2].[EntityOneId], [e2].[EntityTwoId], [e3].[Id], [e3].[Name] + FROM [EntityOneEntityTwo] AS [e2] + INNER JOIN [EntityOnes] AS [e3] ON [e2].[EntityOneId] = [e3].[Id] + WHERE [e3].[Id] = @__p_0 +) AS [t0] ON [t].[Id] = [t0].[EntityTwoId] +WHERE [e].[Id] = @__p_0 +ORDER BY [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t].[Id], [t0].[EntityOneId], [t0].[EntityTwoId], [t0].[Id]"); + } + + public override async Task Load_collection_using_Query_with_Include_for_same_collection(bool async) + { + await base.Load_collection_using_Query_with_Include_for_same_collection(async); + + AssertSql( + @"@__p_0='3' + +SELECT [t].[Id], [t].[CollectionInverseId], [t].[Name], [t].[ReferenceInverseId], [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t1].[EntityOneId], [t1].[EntityTwoId], [t1].[Id], [t1].[Name], [t1].[EntityOneId0], [t1].[EntityTwoId0], [t1].[Id0], [t1].[CollectionInverseId], [t1].[Name0], [t1].[ReferenceInverseId] +FROM [EntityOnes] AS [e] +INNER JOIN ( + SELECT [e1].[Id], [e1].[CollectionInverseId], [e1].[Name], [e1].[ReferenceInverseId], [e0].[EntityOneId], [e0].[EntityTwoId] + FROM [EntityOneEntityTwo] AS [e0] + INNER JOIN [EntityTwos] AS [e1] ON [e0].[EntityTwoId] = [e1].[Id] +) AS [t] ON [e].[Id] = [t].[EntityOneId] +LEFT JOIN ( + SELECT [e2].[EntityOneId], [e2].[EntityTwoId], [e3].[Id], [e3].[Name], [t0].[EntityOneId] AS [EntityOneId0], [t0].[EntityTwoId] AS [EntityTwoId0], [t0].[Id] AS [Id0], [t0].[CollectionInverseId], [t0].[Name] AS [Name0], [t0].[ReferenceInverseId] + FROM [EntityOneEntityTwo] AS [e2] + INNER JOIN [EntityOnes] AS [e3] ON [e2].[EntityOneId] = [e3].[Id] + LEFT JOIN ( + SELECT [e4].[EntityOneId], [e4].[EntityTwoId], [e5].[Id], [e5].[CollectionInverseId], [e5].[Name], [e5].[ReferenceInverseId] + FROM [EntityOneEntityTwo] AS [e4] + INNER JOIN [EntityTwos] AS [e5] ON [e4].[EntityTwoId] = [e5].[Id] + ) AS [t0] ON [e3].[Id] = [t0].[EntityOneId] + WHERE [e3].[Id] = @__p_0 +) AS [t1] ON [t].[Id] = [t1].[EntityTwoId] +WHERE [e].[Id] = @__p_0 +ORDER BY [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t].[Id], [t1].[EntityOneId], [t1].[EntityTwoId], [t1].[Id], [t1].[EntityOneId0], [t1].[EntityTwoId0], [t1].[Id0]"); + } + + public override async Task Load_collection_using_Query_with_Include(bool async) + { + await base.Load_collection_using_Query_with_Include(async); + + AssertSql( + @"@__p_0='3' + +SELECT [t].[Id], [t].[CollectionInverseId], [t].[Name], [t].[ReferenceInverseId], [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t0].[EntityOneId], [t0].[EntityTwoId], [t0].[Id], [t0].[Name], [t1].[ThreeId], [t1].[TwoId], [t1].[Id], [t1].[CollectionInverseId], [t1].[Name], [t1].[ReferenceInverseId] +FROM [EntityOnes] AS [e] +INNER JOIN ( + SELECT [e1].[Id], [e1].[CollectionInverseId], [e1].[Name], [e1].[ReferenceInverseId], [e0].[EntityOneId], [e0].[EntityTwoId] + FROM [EntityOneEntityTwo] AS [e0] + INNER JOIN [EntityTwos] AS [e1] ON [e0].[EntityTwoId] = [e1].[Id] +) AS [t] ON [e].[Id] = [t].[EntityOneId] +LEFT JOIN ( + SELECT [e2].[EntityOneId], [e2].[EntityTwoId], [e3].[Id], [e3].[Name] + FROM [EntityOneEntityTwo] AS [e2] + INNER JOIN [EntityOnes] AS [e3] ON [e2].[EntityOneId] = [e3].[Id] + WHERE [e3].[Id] = @__p_0 +) AS [t0] ON [t].[Id] = [t0].[EntityTwoId] +LEFT JOIN ( + SELECT [j].[ThreeId], [j].[TwoId], [e4].[Id], [e4].[CollectionInverseId], [e4].[Name], [e4].[ReferenceInverseId] + FROM [JoinTwoToThree] AS [j] + INNER JOIN [EntityThrees] AS [e4] ON [j].[ThreeId] = [e4].[Id] +) AS [t1] ON [t].[Id] = [t1].[TwoId] +WHERE [e].[Id] = @__p_0 +ORDER BY [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t].[Id], [t0].[EntityOneId], [t0].[EntityTwoId], [t0].[Id], [t1].[ThreeId], [t1].[TwoId], [t1].[Id]"); + } + + public override async Task Load_collection_using_Query_with_filtered_Include(bool async) + { + await base.Load_collection_using_Query_with_filtered_Include(async); + + AssertSql( + @"@__p_0='3' + +SELECT [t].[Id], [t].[CollectionInverseId], [t].[Name], [t].[ReferenceInverseId], [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t0].[EntityOneId], [t0].[EntityTwoId], [t0].[Id], [t0].[Name], [t1].[ThreeId], [t1].[TwoId], [t1].[Id], [t1].[CollectionInverseId], [t1].[Name], [t1].[ReferenceInverseId] +FROM [EntityOnes] AS [e] +INNER JOIN ( + SELECT [e1].[Id], [e1].[CollectionInverseId], [e1].[Name], [e1].[ReferenceInverseId], [e0].[EntityOneId], [e0].[EntityTwoId] + FROM [EntityOneEntityTwo] AS [e0] + INNER JOIN [EntityTwos] AS [e1] ON [e0].[EntityTwoId] = [e1].[Id] +) AS [t] ON [e].[Id] = [t].[EntityOneId] +LEFT JOIN ( + SELECT [e2].[EntityOneId], [e2].[EntityTwoId], [e3].[Id], [e3].[Name] + FROM [EntityOneEntityTwo] AS [e2] + INNER JOIN [EntityOnes] AS [e3] ON [e2].[EntityOneId] = [e3].[Id] + WHERE [e3].[Id] = @__p_0 +) AS [t0] ON [t].[Id] = [t0].[EntityTwoId] +LEFT JOIN ( + SELECT [j].[ThreeId], [j].[TwoId], [e4].[Id], [e4].[CollectionInverseId], [e4].[Name], [e4].[ReferenceInverseId] + FROM [JoinTwoToThree] AS [j] + INNER JOIN [EntityThrees] AS [e4] ON [j].[ThreeId] = [e4].[Id] + WHERE [e4].[Id] IN (13, 11) +) AS [t1] ON [t].[Id] = [t1].[TwoId] +WHERE [e].[Id] = @__p_0 +ORDER BY [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t].[Id], [t0].[EntityOneId], [t0].[EntityTwoId], [t0].[Id], [t1].[ThreeId], [t1].[TwoId], [t1].[Id]"); + } + + public override async Task Load_collection_using_Query_with_filtered_Include_and_projection(bool async) + { + await base.Load_collection_using_Query_with_filtered_Include_and_projection(async); + + AssertSql( + @"@__p_0='3' + +SELECT [t].[Id], [t].[Name], ( + SELECT COUNT(*) + FROM [EntityOneEntityTwo] AS [e] + INNER JOIN [EntityOnes] AS [e0] ON [e].[EntityOneId] = [e0].[Id] + WHERE [t].[Id] = [e].[EntityTwoId]) AS [Count1], ( + SELECT COUNT(*) + FROM [JoinTwoToThree] AS [j] + INNER JOIN [EntityThrees] AS [e1] ON [j].[ThreeId] = [e1].[Id] + WHERE [t].[Id] = [j].[TwoId]) AS [Count3] +FROM [EntityOnes] AS [e2] +INNER JOIN ( + SELECT [e4].[Id], [e4].[Name], [e3].[EntityOneId] + FROM [EntityOneEntityTwo] AS [e3] + INNER JOIN [EntityTwos] AS [e4] ON [e3].[EntityTwoId] = [e4].[Id] +) AS [t] ON [e2].[Id] = [t].[EntityOneId] +WHERE [e2].[Id] = @__p_0"); + } + + public override async Task Load_collection_using_Query_with_join(bool async) + { + await base.Load_collection_using_Query_with_join(async); + + AssertSql( + @"@__p_0='3' + +SELECT [t].[Id], [t].[CollectionInverseId], [t].[Name], [t].[ReferenceInverseId], [t1].[Id0], [t1].[CollectionInverseId], [t1].[Name0], [t1].[ReferenceInverseId], [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t1].[Id], [t1].[EntityOneId], [t1].[EntityTwoId], [t2].[EntityOneId], [t2].[EntityTwoId], [t2].[Id], [t2].[Name] +FROM [EntityOnes] AS [e] +INNER JOIN ( + SELECT [e1].[Id], [e1].[CollectionInverseId], [e1].[Name], [e1].[ReferenceInverseId], [e0].[EntityOneId], [e0].[EntityTwoId] + FROM [EntityOneEntityTwo] AS [e0] + INNER JOIN [EntityTwos] AS [e1] ON [e0].[EntityTwoId] = [e1].[Id] +) AS [t] ON [e].[Id] = [t].[EntityOneId] +INNER JOIN ( + SELECT [e2].[Id], [t0].[Id] AS [Id0], [t0].[CollectionInverseId], [t0].[Name] AS [Name0], [t0].[ReferenceInverseId], [t0].[EntityOneId], [t0].[EntityTwoId] + FROM [EntityOnes] AS [e2] + INNER JOIN ( + SELECT [e4].[Id], [e4].[CollectionInverseId], [e4].[Name], [e4].[ReferenceInverseId], [e3].[EntityOneId], [e3].[EntityTwoId] + FROM [EntityOneEntityTwo] AS [e3] + INNER JOIN [EntityTwos] AS [e4] ON [e3].[EntityTwoId] = [e4].[Id] + ) AS [t0] ON [e2].[Id] = [t0].[EntityOneId] +) AS [t1] ON [t].[Id] = [t1].[Id0] +LEFT JOIN ( + SELECT [e5].[EntityOneId], [e5].[EntityTwoId], [e6].[Id], [e6].[Name] + FROM [EntityOneEntityTwo] AS [e5] + INNER JOIN [EntityOnes] AS [e6] ON [e5].[EntityOneId] = [e6].[Id] + WHERE [e6].[Id] = @__p_0 +) AS [t2] ON [t].[Id] = [t2].[EntityTwoId] +WHERE [e].[Id] = @__p_0 +ORDER BY [e].[Id], [t].[EntityOneId], [t].[EntityTwoId], [t].[Id], [t1].[Id], [t1].[EntityOneId], [t1].[EntityTwoId], [t1].[Id0], [t2].[EntityOneId], [t2].[EntityTwoId], [t2].[Id]"); + } + protected override void ClearLog() => Fixture.TestSqlLoggerFactory.Clear();