Skip to content

Commit

Permalink
Add support for split query for non-composed collections (#21224)
Browse files Browse the repository at this point in the history
- Added tests for filtered include but only Where filter works for now.
- Copy over tags to inner SelectExpression

Part of #20892
  • Loading branch information
smitpatel authored Jun 12, 2020
1 parent fc33923 commit 936d483
Show file tree
Hide file tree
Showing 8 changed files with 1,066 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -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
{
Expand All @@ -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<Customer>().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<Customer>().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<Order>(o => o.OrderDetails))),
entryCount: 227);
}

protected virtual bool CanExecuteQueryString => false;

protected override QueryAsserter CreateQueryAsserter(TFixture fixture)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading

0 comments on commit 936d483

Please sign in to comment.