From 936d48310ec68be4ea126fa34ebb754b94258409 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Fri, 12 Jun 2020 00:01:49 -0700 Subject: [PATCH] Add support for split query for non-composed collections (#21224) - Added tests for filtered include but only Where filter works for now. - Copy over tags to inner SelectExpression Part of #20892 --- ...CollectionJoinApplyingExpressionVisitor.cs | 37 +- ...sitor.ShaperProcessingExpressionVisitor.cs | 266 +++++++++- ...alShapedQueryCompilingExpressionVisitor.cs | 3 +- ...mplexNavigationsQueryRelationalTestBase.cs | 459 ++++++++++++++++++ ...indMiscellaneousQueryRelationalTestBase.cs | 31 ++ .../ComplexNavigationsQuerySqlServerTest.cs | 202 ++++++++ ...orthwindMiscellaneousQuerySqlServerTest.cs | 41 ++ ...NorthwindQueryTaggingQuerySqlServerTest.cs | 40 ++ 8 files changed, 1066 insertions(+), 13 deletions(-) diff --git a/src/EFCore.Relational/Query/Internal/CollectionJoinApplyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/CollectionJoinApplyingExpressionVisitor.cs index b82f489f6fb..5fd4998b2a1 100644 --- a/src/EFCore.Relational/Query/Internal/CollectionJoinApplyingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Internal/CollectionJoinApplyingExpressionVisitor.cs @@ -53,15 +53,36 @@ protected override Expression VisitExtension(Expression extensionExpression) selectExpression.PushdownIntoSubquery(); } - var innerShaper = Visit(collectionShaperExpression.InnerShaper); + if (_splitQuery) + { + var splitCollectionShaperExpression = (RelationalSplitCollectionShaperExpression)selectExpression.ApplyCollectionJoin( + projectionBindingExpression.Index.Value, + collectionId, + collectionShaperExpression.InnerShaper, + collectionShaperExpression.Navigation, + collectionShaperExpression.ElementType, + _splitQuery); + + var innerShaper = Visit(splitCollectionShaperExpression.InnerShaper); + + return splitCollectionShaperExpression.Update( + splitCollectionShaperExpression.ParentIdentifier, + splitCollectionShaperExpression.ChildIdentifier, + splitCollectionShaperExpression.SelectExpression, + innerShaper); + } + else + { + var innerShaper = Visit(collectionShaperExpression.InnerShaper); - return selectExpression.ApplyCollectionJoin( - projectionBindingExpression.Index.Value, - collectionId, - innerShaper, - collectionShaperExpression.Navigation, - collectionShaperExpression.ElementType, - _splitQuery); + return selectExpression.ApplyCollectionJoin( + projectionBindingExpression.Index.Value, + collectionId, + innerShaper, + collectionShaperExpression.Navigation, + collectionShaperExpression.ElementType, + _splitQuery); + } } return extensionExpression is ShapedQueryExpression shapedQueryExpression diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs index 1f07630acff..2cc13861b9a 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs @@ -60,12 +60,19 @@ private static readonly MethodInfo _initializeCollectionMethodInfo = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(InitializeCollection)); private static readonly MethodInfo _populateCollectionMethodInfo = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(PopulateCollection)); + private static readonly MethodInfo _initializeSplitCollectionMethodInfo + = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(InitializeSplitCollection)); + private static readonly MethodInfo _populateSplitCollectionMethodInfo + = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(PopulateSplitCollection)); + private static readonly MethodInfo _populateSplitCollectionAsyncMethodInfo + = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(PopulateSplitCollectionAsync)); private static readonly MethodInfo _taskAwaiterMethodInfo = typeof(ShaperProcessingExpressionVisitor).GetTypeInfo().GetDeclaredMethod(nameof(TaskAwaiter)); private static readonly MethodInfo _collectionAccessorAddMethodInfo = typeof(IClrCollectionAccessor).GetTypeInfo().GetDeclaredMethod(nameof(IClrCollectionAccessor.Add)); private readonly RelationalShapedQueryCompilingExpressionVisitor _parentVisitor; + private readonly ISet _tags; private readonly bool _isTracking; private readonly bool _isAsync; private readonly bool _splitQuery; @@ -103,6 +110,7 @@ private readonly IDictionary> _ public ShaperProcessingExpressionVisitor( RelationalShapedQueryCompilingExpressionVisitor parentVisitor, SelectExpression selectExpression, + ISet tags, bool splitQuery, bool indexMap) { @@ -112,6 +120,7 @@ public ShaperProcessingExpressionVisitor( _executionStrategyParameter = splitQuery ? Expression.Parameter(typeof(IExecutionStrategy), "executionStrategy") : null; _selectExpression = selectExpression; + _tags = tags; _dataReaderParameter = Expression.Parameter(typeof(DbDataReader), "dataReader"); _resultContextParameter = Expression.Parameter(typeof(ResultContext), "resultContext"); _indexMapParameter = indexMap ? Expression.Parameter(typeof(int[]), "indexMap") : null; @@ -125,6 +134,8 @@ public ShaperProcessingExpressionVisitor( _isTracking = parentVisitor.QueryCompilationContext.IsTracking; _isAsync = parentVisitor.QueryCompilationContext.IsAsync; _splitQuery = splitQuery; + + _selectExpression.ApplyTags(_tags); } // For single query scenario @@ -155,13 +166,15 @@ private ShaperProcessingExpressionVisitor( RelationalShapedQueryCompilingExpressionVisitor parentVisitor, ParameterExpression resultCoordinatorParameter, ParameterExpression executionStrategyParameter, - SelectExpression selectExpression) + SelectExpression selectExpression, + ISet tags) { _parentVisitor = parentVisitor; _resultCoordinatorParameter = resultCoordinatorParameter; _executionStrategyParameter = executionStrategyParameter; _selectExpression = selectExpression; + _tags = tags; _dataReaderParameter = Expression.Parameter(typeof(DbDataReader), "dataReader"); _resultContextParameter = Expression.Parameter(typeof(ResultContext), "resultContext"); if (parentVisitor.QueryCompilationContext.IsBuffering) @@ -173,6 +186,8 @@ private ShaperProcessingExpressionVisitor( _isTracking = parentVisitor.QueryCompilationContext.IsTracking; _isAsync = parentVisitor.QueryCompilationContext.IsAsync; _splitQuery = true; + + _selectExpression.ApplyTags(_tags); } public LambdaExpression ProcessShaper( @@ -528,7 +543,7 @@ protected override Expression VisitExtension(Expression extensionExpression) relationalSplitCollectionShaperExpression) { var innerProcessor = new ShaperProcessingExpressionVisitor(_parentVisitor, _resultCoordinatorParameter, - _executionStrategyParameter, relationalSplitCollectionShaperExpression.SelectExpression); + _executionStrategyParameter, relationalSplitCollectionShaperExpression.SelectExpression, _tags); var innerShaper = innerProcessor.ProcessShaper(relationalSplitCollectionShaperExpression.InnerShaper, out var relationalCommandCache, out var relatedDataLoaders); @@ -708,6 +723,85 @@ protected override Expression VisitExtension(Expression extensionExpression) return accessor; } + case RelationalSplitCollectionShaperExpression relationalSplitCollectionShaperExpression: + { + var key = GenerateKey(relationalSplitCollectionShaperExpression); + if (!_variableShaperMapping.TryGetValue(key, out var accessor)) + { + var innerProcessor = new ShaperProcessingExpressionVisitor(_parentVisitor, _resultCoordinatorParameter, + _executionStrategyParameter, relationalSplitCollectionShaperExpression.SelectExpression, _tags); + var innerShaper = innerProcessor.ProcessShaper(relationalSplitCollectionShaperExpression.InnerShaper, + out var relationalCommandCache, + out var relatedDataLoaders); + + var collectionType = relationalSplitCollectionShaperExpression.Type; + var elementType = collectionType.TryGetSequenceType(); + var relatedElementType = innerShaper.ReturnType; + var navigation = relationalSplitCollectionShaperExpression.Navigation; + + _inline = true; + + var parentIdentifierLambda = Expression.Lambda( + Visit(relationalSplitCollectionShaperExpression.ParentIdentifier), + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter); + + _inline = false; + + innerProcessor._inline = true; + + var childIdentifierLambda = Expression.Lambda( + innerProcessor.Visit(relationalSplitCollectionShaperExpression.ChildIdentifier), + QueryCompilationContext.QueryContextParameter, + innerProcessor._dataReaderParameter); + + innerProcessor._inline = false; + + var collectionIdConstant = Expression.Constant(relationalSplitCollectionShaperExpression.CollectionId); + + var collectionParameter = Expression.Parameter(collectionType); + _variables.Add(collectionParameter); + _expressions.Add( + Expression.Assign( + collectionParameter, + Expression.Call( + _initializeSplitCollectionMethodInfo.MakeGenericMethod(elementType, collectionType), + collectionIdConstant, + QueryCompilationContext.QueryContextParameter, + _dataReaderParameter, + _resultCoordinatorParameter, + Expression.Constant(parentIdentifierLambda.Compile()), + Expression.Constant(navigation?.GetCollectionAccessor(), typeof(IClrCollectionAccessor))))); + + _valuesArrayInitializers.Add(collectionParameter); + accessor = Expression.Convert( + Expression.ArrayIndex( + _valuesArrayExpression, + Expression.Constant(_valuesArrayInitializers.Count - 1)), + relationalSplitCollectionShaperExpression.Type); + + _collectionPopulatingExpressions.Add(Expression.Call( + (_isAsync ? _populateSplitCollectionAsyncMethodInfo : _populateSplitCollectionMethodInfo) + .MakeGenericMethod(collectionType, elementType, relatedElementType), + collectionIdConstant, + Expression.Convert(QueryCompilationContext.QueryContextParameter, typeof(RelationalQueryContext)), + _executionStrategyParameter, + _resultCoordinatorParameter, + Expression.Constant(relationalCommandCache), + Expression.Constant(childIdentifierLambda.Compile()), + Expression.Constant(relationalSplitCollectionShaperExpression.IdentifierValueComparers, typeof(IReadOnlyList)), + Expression.Constant(innerShaper.Compile()), + Expression.Constant(relatedDataLoaders?.Compile(), + _isAsync + ? typeof(Func) + : typeof(Action)))); + + _variableShaperMapping[key] = accessor; + } + + return accessor; + } + case GroupByShaperExpression _: throw new InvalidOperationException(RelationalStrings.ClientGroupByNotSupported); } @@ -1288,7 +1382,6 @@ async Task InitializeReaderAsync(DbContext _, bool result, CancellationTok cancellationToken) .ConfigureAwait(false); - return result; } @@ -1467,6 +1560,173 @@ void GenerateCurrentElementIfPending() } } + private static TCollection InitializeSplitCollection( + int collectionId, + QueryContext queryContext, + DbDataReader parentDataReader, + SplitQueryResultCoordinator resultCoordinator, + Func parentIdentifier, + IClrCollectionAccessor clrCollectionAccessor) + where TCollection : class, IEnumerable + { + var collection = clrCollectionAccessor?.Create() ?? new List(); + var parentKey = parentIdentifier(queryContext, parentDataReader); + var splitQueryCollectionContext = new SplitQueryCollectionContext(null, collection, parentKey); + + resultCoordinator.SetSplitQueryCollectionContext(collectionId, splitQueryCollectionContext); + + return (TCollection)collection; + } + + private static void PopulateSplitCollection( + int collectionId, + RelationalQueryContext queryContext, + IExecutionStrategy executionStrategy, + SplitQueryResultCoordinator resultCoordinator, + RelationalCommandCache relationalCommandCache, + Func childIdentifier, + IReadOnlyList identifierValueComparers, + Func innerShaper, + Action relatedDataLoaders) + where TRelatedEntity : TElement + where TCollection : class, ICollection + { + if (resultCoordinator.DataReaders.Count <= collectionId + || resultCoordinator.DataReaders[collectionId] == null) + { + // Execute and fetch data reader + RelationalDataReader dataReader = null; + executionStrategy.Execute(true, InitializeReader, null); + + bool InitializeReader(DbContext _, bool result) + { + var relationalCommand = relationalCommandCache.GetRelationalCommand(queryContext.ParameterValues); + + dataReader + = relationalCommand.ExecuteReader( + new RelationalCommandParameterObject( + queryContext.Connection, + queryContext.ParameterValues, + relationalCommandCache.ReaderColumns, + queryContext.Context, + queryContext.CommandLogger)); + + + return result; + } + + resultCoordinator.SetDataReader(collectionId, dataReader); + } + + var splitQueryCollectionContext = resultCoordinator.Collections[collectionId]; + var dataReaderContext = resultCoordinator.DataReaders[collectionId]; + var dbDataReader = dataReaderContext.DataReader.DbDataReader; + if (splitQueryCollectionContext.Collection is null) + { + // nothing to materialize since no collection created + return; + } + + while (dataReaderContext.HasNext ?? dbDataReader.Read()) + { + if (!CompareIdentifiers(identifierValueComparers, + splitQueryCollectionContext.ParentIdentifier, childIdentifier(queryContext, dbDataReader))) + { + dataReaderContext.HasNext = true; + + return; + } + + dataReaderContext.HasNext = null; + splitQueryCollectionContext.ResultContext.Values = null; + + innerShaper(queryContext, dbDataReader, splitQueryCollectionContext.ResultContext, resultCoordinator); + relatedDataLoaders?.Invoke(queryContext, executionStrategy, resultCoordinator); + var relatedElement = innerShaper( + queryContext, dbDataReader, splitQueryCollectionContext.ResultContext, resultCoordinator); + ((TCollection)splitQueryCollectionContext.Collection).Add(relatedElement); + } + + dataReaderContext.HasNext = false; + } + + private static async Task PopulateSplitCollectionAsync( + int collectionId, + RelationalQueryContext queryContext, + IExecutionStrategy executionStrategy, + SplitQueryResultCoordinator resultCoordinator, + RelationalCommandCache relationalCommandCache, + Func childIdentifier, + IReadOnlyList identifierValueComparers, + Func innerShaper, + Func relatedDataLoaders) + where TRelatedEntity : TElement + where TCollection : class, ICollection + { + if (resultCoordinator.DataReaders.Count <= collectionId + || resultCoordinator.DataReaders[collectionId] == null) + { + // Execute and fetch data reader + RelationalDataReader dataReader = null; + await executionStrategy.ExecuteAsync( + true, InitializeReaderAsync, null, queryContext.CancellationToken).ConfigureAwait(false); + + async Task InitializeReaderAsync(DbContext _, bool result, CancellationToken cancellationToken) + { + var relationalCommand = relationalCommandCache.GetRelationalCommand(queryContext.ParameterValues); + + dataReader + = await relationalCommand.ExecuteReaderAsync( + new RelationalCommandParameterObject( + queryContext.Connection, + queryContext.ParameterValues, + relationalCommandCache.ReaderColumns, + queryContext.Context, + queryContext.CommandLogger), + cancellationToken) + .ConfigureAwait(false); + + return result; + } + + resultCoordinator.SetDataReader(collectionId, dataReader); + } + + var splitQueryCollectionContext = resultCoordinator.Collections[collectionId]; + var dataReaderContext = resultCoordinator.DataReaders[collectionId]; + var dbDataReader = dataReaderContext.DataReader.DbDataReader; + if (splitQueryCollectionContext.Collection is null) + { + // nothing to materialize since no collection created + return; + } + + while (dataReaderContext.HasNext ?? await dbDataReader.ReadAsync(queryContext.CancellationToken).ConfigureAwait(false)) + { + if (!CompareIdentifiers(identifierValueComparers, + splitQueryCollectionContext.ParentIdentifier, childIdentifier(queryContext, dbDataReader))) + { + dataReaderContext.HasNext = true; + + return; + } + + dataReaderContext.HasNext = null; + splitQueryCollectionContext.ResultContext.Values = null; + + innerShaper(queryContext, dbDataReader, splitQueryCollectionContext.ResultContext, resultCoordinator); + if (relatedDataLoaders != null) + { + await relatedDataLoaders(queryContext, executionStrategy, resultCoordinator).ConfigureAwait(false); + } + var relatedElement = innerShaper( + queryContext, dbDataReader, splitQueryCollectionContext.ResultContext, resultCoordinator); + ((TCollection)splitQueryCollectionContext.Collection).Add(relatedElement); + } + + dataReaderContext.HasNext = false; + } + private static async Task TaskAwaiter(Task[] tasks) { for (var i = 0; i < tasks.Length; i++) diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs index 992ad3493c6..0a1321209f3 100644 --- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -57,12 +57,11 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery Check.NotNull(shapedQueryExpression, nameof(shapedQueryExpression)); var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression; - selectExpression.ApplyTags(_tags); VerifyNoClientConstant(shapedQueryExpression.ShaperExpression); var nonComposedFromSql = selectExpression.IsNonComposedFromSql(); var splitQuery = ((RelationalQueryCompilationContext)QueryCompilationContext).IsSplitQuery; - var shaper = new ShaperProcessingExpressionVisitor(this, selectExpression, splitQuery, nonComposedFromSql).ProcessShaper( + var shaper = new ShaperProcessingExpressionVisitor(this, selectExpression, _tags, splitQuery, nonComposedFromSql).ProcessShaper( shapedQueryExpression.ShaperExpression, out var relationalCommandCache, out var relatedDataLoaders); if (nonComposedFromSql) diff --git a/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsQueryRelationalTestBase.cs index c1dc9988fcf..36fc34bc61c 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/ComplexNavigationsQueryRelationalTestBase.cs @@ -1,7 +1,12 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel; using Microsoft.EntityFrameworkCore.TestUtilities; +using Xunit; namespace Microsoft.EntityFrameworkCore.Query { @@ -13,6 +18,460 @@ protected ComplexNavigationsQueryRelationalTestBase(TFixture fixture) { } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_basic_Where_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.Where(l2 => l2.Id > 5)).AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(l2 => l2.Id > 5)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_OrderBy_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Name)).AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.OrderBy(x => x.Name)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_ThenInclude_OrderBy_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1) + .ThenInclude(l2 => l2.OneToMany_Optional2.OrderBy(x => x.Name)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedInclude(e => e.OneToMany_Optional1), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToMany_Optional1", + includeFilter: x => x.OrderBy(x => x.Name)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_ThenInclude_OrderBy_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Name)) + .ThenInclude(l2 => l2.OneToMany_Optional2.OrderByDescending(x => x.Name)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.OrderBy(x => x.Name)), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToMany_Optional1", + includeFilter: x => x.OrderByDescending(x => x.Name)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_basic_OrderBy_Take_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Name).Take(3)).AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.OrderBy(x => x.Name).Take(3)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_basic_OrderBy_Skip_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Name).Skip(1)).AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.OrderBy(x => x.Name).Skip(1)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_basic_OrderBy_Skip_Take_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Name).Skip(1).Take(3)).AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.OrderBy(x => x.Name).Skip(1).Take(3)))); + } + + [ConditionalFact(Skip = "Issue#20892")] + public virtual void Filtered_include_Skip_without_OrderBy_split() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne.Include(l1 => l1.OneToMany_Optional1.Skip(1)).AsSplitQuery(); + var result = query.ToList(); + } + + [ConditionalFact(Skip = "Issue#20892")] + public virtual void Filtered_include_Take_without_OrderBy_split() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne.Include(l1 => l1.OneToMany_Optional1.Take(1)).AsSplitQuery(); + var result = query.ToList(); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_on_ThenInclude_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToOne_Optional_FK1) + .ThenInclude(l2 => l2.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedInclude(e => e.OneToOne_Optional_FK1), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToOne_Optional_FK1", + x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_after_reference_navigation_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToOne_Optional_FK1.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedInclude(e => e.OneToOne_Optional_FK1), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToOne_Optional_FK1", + x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_after_different_filtered_include_same_level_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Take(3)) + .Include(l1 => l1.OneToMany_Required1.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Take(3)), + new ExpectedFilteredInclude( + e => e.OneToMany_Required1, + includeFilter: x => x.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_after_different_filtered_include_different_level_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Take(3)) + .ThenInclude(l2 => l2.OneToMany_Required2.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Take(3)), + new ExpectedFilteredInclude( + e => e.OneToMany_Required2, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_different_filter_set_on_same_navigation_twice_split(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Take(3)) + .AsSplitQuery()))).Message; + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_different_filter_set_on_same_navigation_twice_multi_level_split(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo")).ThenInclude(l2 => l2.OneToMany_Optional2) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Bar")).ThenInclude(l2 => l2.OneToOne_Required_FK2) + .AsSplitQuery()))).Message; + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_same_filter_set_on_same_navigation_twice_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderByDescending(x => x.Id).Take(2)) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderByDescending(x => x.Id).Take(2)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(x => x.Name != "Foo").OrderByDescending(x => x.Id).Take(2)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)).ThenInclude(l2 => l2.OneToMany_Optional2) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)).ThenInclude(l2 => l2.OneToOne_Required_FK2) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)), + new ExpectedInclude(e => e.OneToMany_Optional2), + new ExpectedInclude(e => e.OneToOne_Required_FK2))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)).ThenInclude(l2 => l2.OneToMany_Optional2) + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToOne_Required_FK2) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(2)), + new ExpectedInclude(e => e.OneToMany_Optional2, "OneToMany_Optional1"), + new ExpectedInclude(e => e.OneToOne_Required_FK2, "OneToMany_Optional1"))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_and_non_filtered_include_on_same_navigation1_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1) + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_and_non_filtered_include_on_same_navigation2_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)) + .Include(l1 => l1.OneToMany_Optional1) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)) + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToOne_Optional_PK2.OneToMany_Optional3.Where(x => x.Id > 1)) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedFilteredInclude( + e => e.OneToMany_Optional1, + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)), + new ExpectedInclude(e => e.OneToOne_Optional_PK2, "OneToMany_Optional1"), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional3, + "OneToMany_Optional1.OneToOne_Optional_PK2", + includeFilter: x => x.Where(x => x.Id > 1)))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_complex_three_level_with_middle_having_filter1_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)).ThenInclude(l3 => l3.OneToMany_Optional3) + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)).ThenInclude(l3 => l3.OneToMany_Required3) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedInclude(e => e.OneToMany_Optional1), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)), + new ExpectedInclude(e => e.OneToMany_Optional3, "OneToMany_Optional1.OneToMany_Optional2"), + new ExpectedInclude(e => e.OneToMany_Required3, "OneToMany_Optional1.OneToMany_Optional2"))); + } + + [ConditionalTheory(Skip = "Issue#20892")] + [MemberData(nameof(IsAsyncData))] + public virtual Task Filtered_include_complex_three_level_with_middle_having_filter2_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToMany_Optional2.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)).ThenInclude(l3 => l3.OneToMany_Optional3) + .Include(l1 => l1.OneToMany_Optional1).ThenInclude(l2 => l2.OneToMany_Optional2).ThenInclude(l3 => l3.OneToMany_Required3) + .AsSplitQuery(), + elementAsserter: (e, a) => AssertInclude(e, a, + new ExpectedInclude(e => e.OneToMany_Optional1), + new ExpectedFilteredInclude( + e => e.OneToMany_Optional2, + "OneToMany_Optional1", + includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(1)), + new ExpectedInclude(e => e.OneToMany_Optional3, "OneToMany_Optional1.OneToMany_Optional2"), + new ExpectedInclude(e => e.OneToMany_Required3, "OneToMany_Optional1.OneToMany_Optional2"))); + } + + [ConditionalFact(Skip = "Issue#20892")] + public virtual void Filtered_include_variable_used_inside_filter_split() + { + using var ctx = CreateContext(); + var prm = "Foo"; + var query = ctx.LevelOne + .Include(l1 => l1.OneToMany_Optional1.Where(x => x.Name != prm).OrderBy(x => x.Id).Take(3)).AsSplitQuery(); + var result = query.ToList(); + } + + [ConditionalFact(Skip = "Issue#20892")] + public virtual void Filtered_include_context_accessed_inside_filter_split() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne + .Include(l1 => l1.OneToMany_Optional1.Where(x => ctx.LevelOne.Count() > 7).OrderBy(x => x.Id).Take(3)).AsSplitQuery(); + var result = query.ToList(); + } + + [ConditionalFact(Skip = "Issue#20892")] + public virtual void Filtered_include_context_accessed_inside_filter_correlated_split() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne + .Include(l1 => l1.OneToMany_Optional1.Where(x => ctx.LevelOne.Count(xx => xx.Id != x.Id) > 1).OrderBy(x => x.Id).Take(3)) + .AsSplitQuery(); + var result = query.ToList(); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_include_parameter_used_inside_filter_throws_split(bool async) + { + await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set() + .Select(l1 => ss.Set().Include(l2 => l2.OneToMany_Optional2.Where(x => x.Id != l2.Id))).AsSplitQuery())); + } + + [ConditionalFact(Skip = "Issue#20892")] + public virtual void Filtered_include_outer_parameter_used_inside_filter_split() + { + // TODO: needs #18191 for result verification + using var ctx = CreateContext(); + var query = ctx.LevelOne.AsSplitQuery().Select(l1 => new + { + l1.Id, + FullInclude = ctx.LevelTwo.Include(l2 => l2.OneToMany_Optional2).ToList(), + FilteredInclude = ctx.LevelTwo.Include(l2 => l2.OneToMany_Optional2.Where(x => x.Id != l1.Id)).ToList() + }); + var result = query.ToList(); + } + + [ConditionalFact(Skip = "Issue#20892")] + public virtual void Filtered_include_is_considered_loaded_split() + { + using var ctx = CreateContext(); + var query = ctx.LevelOne.AsTracking().Include(l1 => l1.OneToMany_Optional1.OrderBy(x => x.Id).Take(1)).AsSplitQuery(); + var result = query.ToList(); + foreach (var resultElement in result) + { + var entry = ctx.Entry(resultElement); + Assert.True(entry.Navigation("OneToMany_Optional1").IsLoaded); + } + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_with_Distinct_throws_split(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set().Include(l1 => l1.OneToMany_Optional1.Distinct()).AsSplitQuery()))).Message; + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual async Task Filtered_include_calling_methods_directly_on_parameter_throws_split(bool async) + { + var message = (await Assert.ThrowsAsync( + () => AssertQuery( + async, + ss => ss.Set() + .Include(l1 => l1.OneToMany_Optional1) + .ThenInclude(l2 => l2.AsQueryable().Where(xx => xx.Id != 42)) + .AsSplitQuery()))).Message; + } + protected virtual bool CanExecuteQueryString => false; protected override QueryAsserter CreateQueryAsserter(TFixture fixture) diff --git a/test/EFCore.Relational.Specification.Tests/Query/NorthwindMiscellaneousQueryRelationalTestBase.cs b/test/EFCore.Relational.Specification.Tests/Query/NorthwindMiscellaneousQueryRelationalTestBase.cs index e75694a87a6..4b727bf4378 100644 --- a/test/EFCore.Relational.Specification.Tests/Query/NorthwindMiscellaneousQueryRelationalTestBase.cs +++ b/test/EFCore.Relational.Specification.Tests/Query/NorthwindMiscellaneousQueryRelationalTestBase.cs @@ -1,7 +1,11 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.EntityFrameworkCore.TestModels.Northwind; +using Xunit; +using System.Linq; namespace Microsoft.EntityFrameworkCore.Query { @@ -13,6 +17,33 @@ protected NorthwindMiscellaneousQueryRelationalTestBase(TFixture fixture) { } + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Projecting_collection_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")).OrderBy(e => e.CustomerID).AsSplitQuery().Select(c => c.Orders), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a), + entryCount: 63); + } + + [ConditionalTheory] + [MemberData(nameof(IsAsyncData))] + public virtual Task Projecting_collection_then_include_split(bool async) + { + return AssertQuery( + async, + ss => ss.Set().Where(c => c.CustomerID.StartsWith("F")) + .Include(c => c.Orders).ThenInclude(o => o.OrderDetails) + .OrderBy(e => e.CustomerID).AsSplitQuery().Select(c => c.Orders), + assertOrder: true, + elementAsserter: (e, a) => AssertCollection(e, a, + elementAsserter: (eo, ao) => AssertInclude(eo, ao, new ExpectedInclude(o => o.OrderDetails))), + entryCount: 227); + } + protected virtual bool CanExecuteQueryString => false; protected override QueryAsserter CreateQueryAsserter(TFixture fixture) diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs index 5a435dd6163..42c318cff2b 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/ComplexNavigationsQuerySqlServerTest.cs @@ -4951,6 +4951,208 @@ WHERE [l3].[Id] <> [l].[Id] ORDER BY [l].[Id], [t].[Id], [t].[Id0], [t1].[Id], [t1].[Id0]"); } + public override async Task Filtered_include_basic_Where_split(bool async) + { + await base.Filtered_include_basic_Where_split(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] +FROM [LevelOne] AS [l] +ORDER BY [l].[Id]", + // + @"SELECT [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_Inverse2Id], [t].[OneToMany_Optional_Self_Inverse2Id], [t].[OneToMany_Required_Inverse2Id], [t].[OneToMany_Required_Self_Inverse2Id], [t].[OneToOne_Optional_PK_Inverse2Id], [t].[OneToOne_Optional_Self2Id], [l].[Id] +FROM [LevelOne] AS [l] +INNER JOIN ( + SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id] + FROM [LevelTwo] AS [l0] + WHERE [l0].[Id] > 5 +) AS [t] ON [l].[Id] = [t].[OneToMany_Optional_Inverse2Id] +ORDER BY [l].[Id]"); + } + + public override async Task Filtered_include_OrderBy_split(bool async) + { + await base.Filtered_include_OrderBy_split(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] +FROM [LevelOne] AS [l] +ORDER BY [l].[Id]", + // + @"SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id], [l].[Id] +FROM [LevelOne] AS [l] +INNER JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] +ORDER BY [l].[Id], [l0].[Name]"); + } + + public override async Task Filtered_ThenInclude_OrderBy_split(bool async) + { + await base.Filtered_ThenInclude_OrderBy_split(async); + + AssertSql( + @"SELECT [l].[Id], [l].[Date], [l].[Name], [l].[OneToMany_Optional_Self_Inverse1Id], [l].[OneToMany_Required_Self_Inverse1Id], [l].[OneToOne_Optional_Self1Id] +FROM [LevelOne] AS [l] +ORDER BY [l].[Id]", + // + @"SELECT [l0].[Id], [l0].[Date], [l0].[Level1_Optional_Id], [l0].[Level1_Required_Id], [l0].[Name], [l0].[OneToMany_Optional_Inverse2Id], [l0].[OneToMany_Optional_Self_Inverse2Id], [l0].[OneToMany_Required_Inverse2Id], [l0].[OneToMany_Required_Self_Inverse2Id], [l0].[OneToOne_Optional_PK_Inverse2Id], [l0].[OneToOne_Optional_Self2Id], [l].[Id] +FROM [LevelOne] AS [l] +INNER JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] +ORDER BY [l].[Id], [l0].[Id]", + // + @"SELECT [l1].[Id], [l1].[Level2_Optional_Id], [l1].[Level2_Required_Id], [l1].[Name], [l1].[OneToMany_Optional_Inverse3Id], [l1].[OneToMany_Optional_Self_Inverse3Id], [l1].[OneToMany_Required_Inverse3Id], [l1].[OneToMany_Required_Self_Inverse3Id], [l1].[OneToOne_Optional_PK_Inverse3Id], [l1].[OneToOne_Optional_Self3Id], [l].[Id], [l0].[Id] +FROM [LevelOne] AS [l] +INNER JOIN [LevelTwo] AS [l0] ON [l].[Id] = [l0].[OneToMany_Optional_Inverse2Id] +INNER JOIN [LevelThree] AS [l1] ON [l0].[Id] = [l1].[OneToMany_Optional_Inverse3Id] +ORDER BY [l].[Id], [l0].[Id], [l1].[Name]"); + } + + public override async Task Filtered_include_ThenInclude_OrderBy_split(bool async) + { + await base.Filtered_include_ThenInclude_OrderBy_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_basic_OrderBy_Take_split(bool async) + { + await base.Filtered_include_basic_OrderBy_Take_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_basic_OrderBy_Skip_split(bool async) + { + await base.Filtered_include_basic_OrderBy_Skip_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_basic_OrderBy_Skip_Take_split(bool async) + { + await base.Filtered_include_basic_OrderBy_Skip_Take_split(async); + + AssertSql(" "); + } + + public override void Filtered_include_Skip_without_OrderBy_split() + { + base.Filtered_include_Skip_without_OrderBy_split(); + + AssertSql(" "); + } + + public override void Filtered_include_Take_without_OrderBy_split() + { + base.Filtered_include_Take_without_OrderBy_split(); + + AssertSql(" "); + } + + public override async Task Filtered_include_on_ThenInclude_split(bool async) + { + await base.Filtered_include_on_ThenInclude_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_after_reference_navigation_split(bool async) + { + await base.Filtered_include_after_reference_navigation_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_after_different_filtered_include_different_level_split(bool async) + { + await base.Filtered_include_after_different_filtered_include_different_level_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_same_filter_set_on_same_navigation_twice_split(bool async) + { + await base.Filtered_include_same_filter_set_on_same_navigation_twice_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes_split(bool async) + { + await base.Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only_split(bool async) + { + await base.Filtered_include_multiple_multi_level_includes_with_first_level_using_filter_include_on_one_of_the_chains_only_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_and_non_filtered_include_on_same_navigation1_split(bool async) + { + await base.Filtered_include_and_non_filtered_include_on_same_navigation1_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_and_non_filtered_include_on_same_navigation2_split(bool async) + { + await base.Filtered_include_and_non_filtered_include_on_same_navigation2_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation_split(bool async) + { + await base.Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_complex_three_level_with_middle_having_filter1_split(bool async) + { + await base.Filtered_include_complex_three_level_with_middle_having_filter1_split(async); + + AssertSql(" "); + } + + public override async Task Filtered_include_complex_three_level_with_middle_having_filter2_split(bool async) + { + await base.Filtered_include_complex_three_level_with_middle_having_filter2_split(async); + + AssertSql(" "); + } + + public override void Filtered_include_variable_used_inside_filter_split() + { + base.Filtered_include_variable_used_inside_filter_split(); + + AssertSql(" "); + } + + public override void Filtered_include_context_accessed_inside_filter_split() + { + base.Filtered_include_context_accessed_inside_filter_split(); + + AssertSql(" "); + } + + public override void Filtered_include_context_accessed_inside_filter_correlated_split() + { + base.Filtered_include_context_accessed_inside_filter_correlated_split(); + + AssertSql(" "); + } + + public override void Filtered_include_outer_parameter_used_inside_filter_split() + { + base.Filtered_include_outer_parameter_used_inside_filter_split(); + + AssertSql(" "); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); } } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs index 312094fdfc8..2cf1dbbc241 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs @@ -5059,6 +5059,47 @@ FROM [Customers] AS [c] WHERE [c].[CustomerID] LIKE N'A%'"); } + public override async Task Projecting_collection_split(bool async) + { + await base.Projecting_collection_split(async); + + AssertSql( + @"SELECT [c].[CustomerID] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID]", + // + @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID] +FROM [Customers] AS [c] +INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID]"); + } + + public override async Task Projecting_collection_then_include_split(bool async) + { + await base.Projecting_collection_then_include_split(async); + + AssertSql( + @"SELECT [c].[CustomerID] +FROM [Customers] AS [c] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID]", + // + @"SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [c].[CustomerID] +FROM [Customers] AS [c] +INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID], [o].[OrderID]", + // + @"SELECT [o0].[OrderID], [o0].[ProductID], [o0].[Discount], [o0].[Quantity], [o0].[UnitPrice], [c].[CustomerID], [o].[OrderID] +FROM [Customers] AS [c] +INNER JOIN [Orders] AS [o] ON [c].[CustomerID] = [o].[CustomerID] +INNER JOIN [Order Details] AS [o0] ON [o].[OrderID] = [o0].[OrderID] +WHERE [c].[CustomerID] LIKE N'F%' +ORDER BY [c].[CustomerID], [o].[OrderID]"); + } + private void AssertSql(params string[] expected) => Fixture.TestSqlLoggerFactory.AssertBaseline(expected); diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryTaggingQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryTaggingQuerySqlServerTest.cs index b6e5eb90b41..ae1045c5f68 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryTaggingQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindQueryTaggingQuerySqlServerTest.cs @@ -2,7 +2,10 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.EntityFrameworkCore.TestUtilities; +using Microsoft.EntityFrameworkCore.TestModels.Northwind; +using Xunit; using Xunit.Abstractions; +using System.Linq; namespace Microsoft.EntityFrameworkCore.Query { @@ -90,6 +93,43 @@ ORDER BY [c].[CustomerID] ORDER BY [t].[CustomerID], [o].[OrderID]"); } + [ConditionalFact] + public virtual void Tag_on_split_include_query() + { + using var context = CreateContext(); + var customer + = context.Set() + .Include(c => c.Orders) + .OrderBy(c => c.CustomerID) + .AsSplitQuery() + .TagWith("Yanni") + .First(); + + Assert.NotNull(customer); + + AssertSql( + @"-- Yanni + +SELECT [t].[CustomerID], [t].[Address], [t].[City], [t].[CompanyName], [t].[ContactName], [t].[ContactTitle], [t].[Country], [t].[Fax], [t].[Phone], [t].[PostalCode], [t].[Region] +FROM ( + SELECT TOP(1) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] + FROM [Customers] AS [c] + ORDER BY [c].[CustomerID] +) AS [t] +ORDER BY [t].[CustomerID]", + // + @"-- Yanni + +SELECT [o].[OrderID], [o].[CustomerID], [o].[EmployeeID], [o].[OrderDate], [t].[CustomerID] +FROM ( + SELECT TOP(1) [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region] + FROM [Customers] AS [c] + ORDER BY [c].[CustomerID] +) AS [t] +INNER JOIN [Orders] AS [o] ON [t].[CustomerID] = [o].[CustomerID] +ORDER BY [t].[CustomerID]"); + } + public override void Tag_on_scalar_query() { base.Tag_on_scalar_query();