Skip to content

Commit

Permalink
Query: Implement filtered include for AsSplitQuery operator
Browse files Browse the repository at this point in the history
Resolves #21276
  • Loading branch information
smitpatel committed Jun 16, 2020
1 parent 65060fd commit 4306e31
Show file tree
Hide file tree
Showing 9 changed files with 753 additions and 77 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,20 @@ protected override Expression VisitExtension(Expression extensionExpression)

private Expression RewriteSubqueryToSelectMany(Expression subquery)
{
var collectionElementType = subquery.Type.TryGetSequenceType();
if (subquery.Type.GetGenericTypeDefinition() == typeof(IOrderedQueryable<>))
{
subquery = Expression.Call(
QueryableMethods.Skip.MakeGenericMethod(collectionElementType),
subquery,
Expression.Constant(0));
}

var newParameter = Expression.Parameter(_parameterExpression.Type);
subquery = ReplacingExpressionVisitor.Replace(_parameterExpression, newParameter, subquery);

// Collection selector body is IQueryable, we need to adjust the type to IEnumerable, to match the SelectMany signature
// therefore the delegate type is specified explicitly
var collectionElementType = subquery.Type.TryGetSequenceType();
var collectionSelectorLambdaType = typeof(Func<,>).MakeGenericType(
_sourceElementType,
typeof(IEnumerable<>).MakeGenericType(collectionElementType));
Expand Down
78 changes: 75 additions & 3 deletions src/EFCore.Relational/Query/SqlExpressions/SelectExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1124,13 +1124,85 @@ public Expression ApplyCollectionJoin(
{
var parentIdentifier = GetIdentifierAccessor(_identifier).Item1;
innerSelectExpression.ApplyProjection();
var (childIdentifier, childIdentifierValueComparers) = innerSelectExpression
.GetIdentifierAccessor(innerSelectExpression._identifier.Take(_identifier.Count));

for (var i = 0; i < _identifier.Count; i++)
{
AppendOrdering(new OrderingExpression(_identifier[i].Column, ascending: true));
innerSelectExpression.AppendOrdering(new OrderingExpression(innerSelectExpression._identifier[i].Column, ascending: true));
}

// Copy over ordering from previous collections
var innerOrderingExpressions = new List<OrderingExpression>();
foreach (var table in innerSelectExpression.Tables)
{
if (table is InnerJoinExpression collectionJoinExpression
&& collectionJoinExpression.Table is SelectExpression collectionSelectExpression
&& collectionSelectExpression.Predicate != null
&& collectionSelectExpression.Tables.Count == 1
&& collectionSelectExpression.Tables[0] is SelectExpression rowNumberSubquery
&& rowNumberSubquery.Projection.Select(pe => pe.Expression)
.OfType<RowNumberExpression>().SingleOrDefault() is RowNumberExpression rowNumberExpression)
{
foreach (var partition in rowNumberExpression.Partitions)
{
innerOrderingExpressions.Add(new OrderingExpression(
collectionSelectExpression.GenerateOuterColumn(rowNumberSubquery.GenerateOuterColumn(partition)),
ascending: true));
}

foreach (var ordering in rowNumberExpression.Orderings)
{
innerOrderingExpressions.Add(new OrderingExpression(
collectionSelectExpression.GenerateOuterColumn(rowNumberSubquery.GenerateOuterColumn(ordering.Expression)),
ordering.IsAscending));
}
}

if (table is CrossApplyExpression collectionApplyExpression
&& collectionApplyExpression.Table is SelectExpression collectionSelectExpression2
&& collectionSelectExpression2.Orderings.Count > 0)
{
foreach (var ordering in collectionSelectExpression2.Orderings)
{
if (innerSelectExpression._identifier.Any(e => e.Column.Equals(ordering.Expression)))
{
continue;
}

innerOrderingExpressions.Add(new OrderingExpression(
collectionSelectExpression2.GenerateOuterColumn(ordering.Expression),
ordering.IsAscending));
}
}
}

var (childIdentifier, childIdentifierValueComparers) = innerSelectExpression
.GetIdentifierAccessor(innerSelectExpression._identifier.Take(_identifier.Count));

var identifierIndex = 0;
var orderingIndex = 0;
for (var i = 0; i < Orderings.Count; i++)
{
var outerOrdering = Orderings[i];
if (outerOrdering.Expression.Equals(_identifier[identifierIndex].Column))
{
innerSelectExpression.AppendOrdering(new OrderingExpression(innerSelectExpression._identifier[identifierIndex].Column, ascending: true));
identifierIndex++;
}
else
{
if (i < innerSelectExpression.Orderings.Count)
{
continue;
}

innerSelectExpression.AppendOrdering(innerOrderingExpressions[orderingIndex]);
orderingIndex++;
}
}

foreach (var orderingExpression in innerOrderingExpressions.Skip(orderingIndex))
{
innerSelectExpression.AppendOrdering(orderingExpression);
}

return new RelationalSplitCollectionShaperExpression(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public virtual Task Filtered_include_basic_Where_split(bool async)
includeFilter: x => x.Where(l2 => l2.Id > 5))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_OrderBy_split(bool async)
{
Expand All @@ -44,7 +44,7 @@ public virtual Task Filtered_include_OrderBy_split(bool async)
includeFilter: x => x.OrderBy(x => x.Name))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_ThenInclude_OrderBy_split(bool async)
{
Expand All @@ -62,7 +62,7 @@ public virtual Task Filtered_ThenInclude_OrderBy_split(bool async)
includeFilter: x => x.OrderBy(x => x.Name))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_ThenInclude_OrderBy_split(bool async)
{
Expand All @@ -82,7 +82,7 @@ public virtual Task Filtered_include_ThenInclude_OrderBy_split(bool async)
includeFilter: x => x.OrderByDescending(x => x.Name))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_basic_OrderBy_Take_split(bool async)
{
Expand All @@ -95,7 +95,7 @@ public virtual Task Filtered_include_basic_OrderBy_Take_split(bool async)
includeFilter: x => x.OrderBy(x => x.Name).Take(3))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_basic_OrderBy_Skip_split(bool async)
{
Expand All @@ -108,7 +108,7 @@ public virtual Task Filtered_include_basic_OrderBy_Skip_split(bool async)
includeFilter: x => x.OrderBy(x => x.Name).Skip(1))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_basic_OrderBy_Skip_Take_split(bool async)
{
Expand All @@ -121,23 +121,23 @@ public virtual Task Filtered_include_basic_OrderBy_Skip_Take_split(bool async)
includeFilter: x => x.OrderBy(x => x.Name).Skip(1).Take(3))));
}

[ConditionalFact(Skip = "Issue#20892")]
[ConditionalFact]
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")]
[ConditionalFact]
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")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_on_ThenInclude_split(bool async)
{
Expand All @@ -155,7 +155,7 @@ public virtual Task Filtered_include_on_ThenInclude_split(bool async)
x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_after_reference_navigation_split(bool async)
{
Expand All @@ -172,7 +172,7 @@ public virtual Task Filtered_include_after_reference_navigation_split(bool async
x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Name).Skip(1).Take(3))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_after_different_filtered_include_same_level_split(bool async)
{
Expand All @@ -191,7 +191,7 @@ public virtual Task Filtered_include_after_different_filtered_include_same_level
includeFilter: x => x.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_after_different_filtered_include_different_level_split(bool async)
{
Expand All @@ -211,7 +211,7 @@ public virtual Task Filtered_include_after_different_filtered_include_different_
includeFilter: x => x.Where(x => x.Name != "Bar").OrderByDescending(x => x.Name).Skip(1))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual async Task Filtered_include_different_filter_set_on_same_navigation_twice_split(bool async)
{
Expand All @@ -237,7 +237,7 @@ public virtual async Task Filtered_include_different_filter_set_on_same_navigati
.AsSplitQuery()))).Message;
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_same_filter_set_on_same_navigation_twice_split(bool async)
{
Expand All @@ -253,7 +253,7 @@ public virtual Task Filtered_include_same_filter_set_on_same_navigation_twice_sp
includeFilter: x => x.Where(x => x.Name != "Foo").OrderByDescending(x => x.Id).Take(2))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_same_filter_set_on_same_navigation_twice_followed_by_ThenIncludes_split(bool async)
{
Expand All @@ -271,15 +271,17 @@ public virtual Task Filtered_include_same_filter_set_on_same_navigation_twice_fo
new ExpectedInclude<Level2>(e => e.OneToOne_Required_FK2)));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[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<Level1>()
.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)
.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<Level1, Level2>(
Expand All @@ -289,7 +291,7 @@ public virtual Task Filtered_include_multiple_multi_level_includes_with_first_le
new ExpectedInclude<Level2>(e => e.OneToOne_Required_FK2, "OneToMany_Optional1")));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_and_non_filtered_include_on_same_navigation1_split(bool async)
{
Expand All @@ -305,7 +307,7 @@ public virtual Task Filtered_include_and_non_filtered_include_on_same_navigation
includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_and_non_filtered_include_on_same_navigation2_split(bool async)
{
Expand All @@ -321,7 +323,7 @@ public virtual Task Filtered_include_and_non_filtered_include_on_same_navigation
includeFilter: x => x.Where(x => x.Name != "Foo").OrderBy(x => x.Id).Take(3))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_and_non_filtered_include_followed_by_then_include_on_same_navigation_split(bool async)
{
Expand All @@ -342,15 +344,19 @@ public virtual Task Filtered_include_and_non_filtered_include_followed_by_then_i
includeFilter: x => x.Where(x => x.Id > 1))));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_complex_three_level_with_middle_having_filter1_split(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Level1>()
.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)
.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<Level1>(e => e.OneToMany_Optional1),
Expand All @@ -362,15 +368,19 @@ public virtual Task Filtered_include_complex_three_level_with_middle_having_filt
new ExpectedInclude<Level3>(e => e.OneToMany_Required3, "OneToMany_Optional1.OneToMany_Optional2")));
}

[ConditionalTheory(Skip = "Issue#20892")]
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Filtered_include_complex_three_level_with_middle_having_filter2_split(bool async)
{
return AssertQuery(
async,
ss => ss.Set<Level1>()
.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)
.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<Level1>(e => e.OneToMany_Optional1),
Expand All @@ -382,7 +392,7 @@ public virtual Task Filtered_include_complex_three_level_with_middle_having_filt
new ExpectedInclude<Level3>(e => e.OneToMany_Required3, "OneToMany_Optional1.OneToMany_Optional2")));
}

[ConditionalFact(Skip = "Issue#20892")]
[ConditionalFact]
public virtual void Filtered_include_variable_used_inside_filter_split()
{
using var ctx = CreateContext();
Expand All @@ -392,7 +402,7 @@ public virtual void Filtered_include_variable_used_inside_filter_split()
var result = query.ToList();
}

[ConditionalFact(Skip = "Issue#20892")]
[ConditionalFact]
public virtual void Filtered_include_context_accessed_inside_filter_split()
{
using var ctx = CreateContext();
Expand All @@ -401,7 +411,7 @@ public virtual void Filtered_include_context_accessed_inside_filter_split()
var result = query.ToList();
}

[ConditionalFact(Skip = "Issue#20892")]
[ConditionalFact]
public virtual void Filtered_include_context_accessed_inside_filter_correlated_split()
{
using var ctx = CreateContext();
Expand All @@ -422,7 +432,7 @@ await Assert.ThrowsAsync<InvalidOperationException>(
.Select(l1 => ss.Set<Level2>().Include(l2 => l2.OneToMany_Optional2.Where(x => x.Id != l2.Id))).AsSplitQuery()));
}

[ConditionalFact(Skip = "Issue#20892")]
[ConditionalFact(Skip = "Issue#21234")]
public virtual void Filtered_include_outer_parameter_used_inside_filter_split()
{
// TODO: needs #18191 for result verification
Expand All @@ -436,7 +446,7 @@ public virtual void Filtered_include_outer_parameter_used_inside_filter_split()
var result = query.ToList();
}

[ConditionalFact(Skip = "Issue#20892")]
[ConditionalFact]
public virtual void Filtered_include_is_considered_loaded_split()
{
using var ctx = CreateContext();
Expand Down
Loading

0 comments on commit 4306e31

Please sign in to comment.