Skip to content

Commit

Permalink
Query: Optimize certain types of GroupJoin/SelectMany queries
Browse files Browse the repository at this point in the history
In some circumstances, a GroupJoin/SelectMany pattern (with or
without DefaultIfEmpty) can be optimized by shifting body clauses
from the AdditionalFromClause into the GroupJoinClause. These changes
implement that optimization.
  • Loading branch information
tuespetre committed Mar 15, 2017
1 parent 7a7f138 commit 39513aa
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 15 deletions.
3 changes: 3 additions & 0 deletions src/EFCore/Query/EntityQueryModelVisitor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,9 @@ var entityEqualityRewritingExpressionVisitor

_navigationRewritingExpressionVisitorFactory
.Create(this).Rewrite(queryModel, parentQueryModel: null);

new AdditionalFromClauseOptimizingQueryModelVisitor()
.VisitQueryModel(queryModel);

entityEqualityRewritingExpressionVisitor.Rewrite(queryModel);

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// 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.Linq;
using System.Linq.Expressions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Query.Internal;
using Remotion.Linq;
using Remotion.Linq.Clauses;
using Remotion.Linq.Clauses.Expressions;
using Remotion.Linq.Clauses.ExpressionVisitors;
using Remotion.Linq.Clauses.ResultOperators;

namespace Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public class AdditionalFromClauseOptimizingQueryModelVisitor : QueryModelVisitorBase
{
/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override void VisitQueryModel([NotNull] QueryModel queryModel)
{
queryModel.TransformExpressions(new TransformingQueryModelExpressionVisitor<AdditionalFromClauseOptimizingQueryModelVisitor>(this).Visit);

base.VisitQueryModel(queryModel);
}

/// <summary>
/// This API supports the Entity Framework Core infrastructure and is not intended to be used
/// directly from your code. This API may change or be removed in future releases.
/// </summary>
public override void VisitAdditionalFromClause(
[NotNull] AdditionalFromClause fromClause,
[NotNull] QueryModel queryModel,
int index)
{
if (fromClause.FromExpression is SubQueryExpression subQueryExpression
&& subQueryExpression.QueryModel.MainFromClause.FromExpression is QuerySourceReferenceExpression qsre
&& subQueryExpression.QueryModel.SelectClause.Selector is QuerySourceReferenceExpression
&& qsre.ReferencedQuerySource is GroupJoinClause groupJoinClause
&& queryModel.CountQuerySourceReferences(groupJoinClause) == 1
&& subQueryExpression.QueryModel.BodyClauses.Any()
&& subQueryExpression.QueryModel.BodyClauses.All(c => c is WhereClause))
{
if (groupJoinClause.JoinClause.InnerSequence is SubQueryExpression innerSequenceSubQuery)
{
if (!innerSequenceSubQuery.QueryModel.ResultOperators.Any(r => r is TakeResultOperator || r is SkipResultOperator))
{
ShiftWhereClauses(subQueryExpression.QueryModel, innerSequenceSubQuery.QueryModel);
}
}
else
{
var newMainFromClause = new MainFromClause(
groupJoinClause.JoinClause.ItemName,
groupJoinClause.JoinClause.ItemType,
groupJoinClause.JoinClause.InnerSequence);

var newSelectClause = new SelectClause(
new QuerySourceReferenceExpression(newMainFromClause));

var newSubQueryModel = new QueryModel(newMainFromClause, newSelectClause);

ShiftWhereClauses(subQueryExpression.QueryModel, newSubQueryModel);

var newSubQueryExpression = new SubQueryExpression(newSubQueryModel);

groupJoinClause.JoinClause.InnerSequence = newSubQueryExpression;
}
}
}

private void ShiftWhereClauses(QueryModel oldQueryModel, QueryModel newQueryModel)
{
var querySourceMapping = new QuerySourceMapping();

querySourceMapping.AddMapping(
oldQueryModel.MainFromClause,
new QuerySourceReferenceExpression(newQueryModel.MainFromClause));

foreach (var whereClause in oldQueryModel.BodyClauses.Cast<WhereClause>().ToArray())
{
whereClause.Predicate
= ReferenceReplacingExpressionVisitor.ReplaceClauseReferences(
whereClause.Predicate,
querySourceMapping,
throwOnUnmappedReferences: false);

oldQueryModel.BodyClauses.Remove(whereClause);
newQueryModel.BodyClauses.Add(whereClause);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2223,10 +2223,14 @@ public override void SelectMany_with_navigation_filter_and_explicit_DefaultIfEmp
base.SelectMany_with_navigation_filter_and_explicit_DefaultIfEmpty();

Assert.Equal(
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [l1.OneToMany_Optional].[Id], [l1.OneToMany_Optional].[Date], [l1.OneToMany_Optional].[Level1_Optional_Id], [l1.OneToMany_Optional].[Level1_Required_Id], [l1.OneToMany_Optional].[Name], [l1.OneToMany_Optional].[OneToMany_Optional_InverseId], [l1.OneToMany_Optional].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Optional].[OneToMany_Required_InverseId], [l1.OneToMany_Optional].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Optional].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Optional].[OneToOne_Optional_SelfId]
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId]
FROM [Level1] AS [l1]
LEFT JOIN [Level2] AS [l1.OneToMany_Optional] ON [l1].[Id] = [l1.OneToMany_Optional].[OneToMany_Optional_InverseId]
ORDER BY [l1].[Id]",
LEFT JOIN (
SELECT [l1.OneToMany_Optional].*
FROM [Level2] AS [l1.OneToMany_Optional]
WHERE [l1.OneToMany_Optional].[Id] > 5
) AS [t] ON [l1].[Id] = [t].[OneToMany_Optional_InverseId]
WHERE [t].[Id] IS NOT NULL",
Sql);
}

Expand All @@ -2248,11 +2252,15 @@ public override void SelectMany_with_nested_navigation_filter_and_explicit_Defau
base.SelectMany_with_nested_navigation_filter_and_explicit_DefaultIfEmpty();

Assert.Equal(
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [l1.OneToOne_Optional_FK.OneToMany_Optional].[Id], [l1.OneToOne_Optional_FK.OneToMany_Optional].[Level2_Optional_Id], [l1.OneToOne_Optional_FK.OneToMany_Optional].[Level2_Required_Id], [l1.OneToOne_Optional_FK.OneToMany_Optional].[Name], [l1.OneToOne_Optional_FK.OneToMany_Optional].[OneToMany_Optional_InverseId], [l1.OneToOne_Optional_FK.OneToMany_Optional].[OneToMany_Optional_Self_InverseId], [l1.OneToOne_Optional_FK.OneToMany_Optional].[OneToMany_Required_InverseId], [l1.OneToOne_Optional_FK.OneToMany_Optional].[OneToMany_Required_Self_InverseId], [l1.OneToOne_Optional_FK.OneToMany_Optional].[OneToOne_Optional_PK_InverseId], [l1.OneToOne_Optional_FK.OneToMany_Optional].[OneToOne_Optional_SelfId]
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId]
FROM [Level1] AS [l1]
INNER JOIN [Level2] AS [l1.OneToOne_Optional_FK] ON [l1].[Id] = [l1.OneToOne_Optional_FK].[Level1_Optional_Id]
LEFT JOIN [Level3] AS [l1.OneToOne_Optional_FK.OneToMany_Optional] ON [l1.OneToOne_Optional_FK].[Id] = [l1.OneToOne_Optional_FK.OneToMany_Optional].[OneToMany_Optional_InverseId]
ORDER BY [l1.OneToOne_Optional_FK].[Id]",
LEFT JOIN (
SELECT [l1.OneToOne_Optional_FK.OneToMany_Optional].*
FROM [Level3] AS [l1.OneToOne_Optional_FK.OneToMany_Optional]
WHERE [l1.OneToOne_Optional_FK.OneToMany_Optional].[Id] > 5
) AS [t] ON [l1.OneToOne_Optional_FK].[Id] = [t].[OneToMany_Optional_InverseId]
WHERE [t].[Id] IS NOT NULL",
Sql);
}

Expand All @@ -2261,11 +2269,15 @@ public override void Multiple_SelectMany_with_navigation_and_explicit_DefaultIfE
base.Multiple_SelectMany_with_navigation_and_explicit_DefaultIfEmpty();

Assert.Equal(
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [l1.OneToMany_Optional.OneToMany_Optional].[Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level2_Optional_Id], [l1.OneToMany_Optional.OneToMany_Optional].[Level2_Required_Id], [l1.OneToMany_Optional.OneToMany_Optional].[Name], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Required_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Optional.OneToMany_Optional].[OneToOne_Optional_SelfId]
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId]
FROM [Level1] AS [l1]
INNER JOIN [Level2] AS [l1.OneToMany_Optional] ON [l1].[Id] = [l1.OneToMany_Optional].[OneToMany_Optional_InverseId]
LEFT JOIN [Level3] AS [l1.OneToMany_Optional.OneToMany_Optional] ON [l1.OneToMany_Optional].[Id] = [l1.OneToMany_Optional.OneToMany_Optional].[OneToMany_Optional_InverseId]
ORDER BY [l1.OneToMany_Optional].[Id]",
LEFT JOIN (
SELECT [l1.OneToMany_Optional.OneToMany_Optional].*
FROM [Level3] AS [l1.OneToMany_Optional.OneToMany_Optional]
WHERE [l1.OneToMany_Optional.OneToMany_Optional].[Id] > 5
) AS [t] ON [l1.OneToMany_Optional].[Id] = [t].[OneToMany_Optional_InverseId]
WHERE [t].[Id] IS NOT NULL",
Sql);
}

Expand All @@ -2274,9 +2286,13 @@ public override void SelectMany_with_navigation_filter_paging_and_explicit_Defau
base.SelectMany_with_navigation_filter_paging_and_explicit_DefautltIfEmpty();

Assert.Equal(
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [l1.OneToMany_Required].[Id], [l1.OneToMany_Required].[Date], [l1.OneToMany_Required].[Level1_Optional_Id], [l1.OneToMany_Required].[Level1_Required_Id], [l1.OneToMany_Required].[Name], [l1.OneToMany_Required].[OneToMany_Optional_InverseId], [l1.OneToMany_Required].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Required].[OneToMany_Required_InverseId], [l1.OneToMany_Required].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Required].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Required].[OneToOne_Optional_SelfId]
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_InverseId], [t].[OneToMany_Optional_Self_InverseId], [t].[OneToMany_Required_InverseId], [t].[OneToMany_Required_Self_InverseId], [t].[OneToOne_Optional_PK_InverseId], [t].[OneToOne_Optional_SelfId]
FROM [Level1] AS [l1]
LEFT JOIN [Level2] AS [l1.OneToMany_Required] ON [l1].[Id] = [l1.OneToMany_Required].[OneToMany_Required_InverseId]
LEFT JOIN (
SELECT [l1.OneToMany_Required].[Id], [l1.OneToMany_Required].[Date], [l1.OneToMany_Required].[Level1_Optional_Id], [l1.OneToMany_Required].[Level1_Required_Id], [l1.OneToMany_Required].[Name], [l1.OneToMany_Required].[OneToMany_Optional_InverseId], [l1.OneToMany_Required].[OneToMany_Optional_Self_InverseId], [l1.OneToMany_Required].[OneToMany_Required_InverseId], [l1.OneToMany_Required].[OneToMany_Required_Self_InverseId], [l1.OneToMany_Required].[OneToOne_Optional_PK_InverseId], [l1.OneToMany_Required].[OneToOne_Optional_SelfId]
FROM [Level2] AS [l1.OneToMany_Required]
WHERE [l1.OneToMany_Required].[Id] > 5
) AS [t] ON [l1].[Id] = [t].[OneToMany_Required_InverseId]
ORDER BY [l1].[Id]",
Sql);
}
Expand Down Expand Up @@ -2616,9 +2632,13 @@ public override void GroupJoin_with_subquery_on_inner()
base.GroupJoin_with_subquery_on_inner();

Assert.Equal(
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [l2].[Id], [l2].[Date], [l2].[Level1_Optional_Id], [l2].[Level1_Required_Id], [l2].[Name], [l2].[OneToMany_Optional_InverseId], [l2].[OneToMany_Optional_Self_InverseId], [l2].[OneToMany_Required_InverseId], [l2].[OneToMany_Required_Self_InverseId], [l2].[OneToOne_Optional_PK_InverseId], [l2].[OneToOne_Optional_SelfId]
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_InverseId], [t].[OneToMany_Optional_Self_InverseId], [t].[OneToMany_Required_InverseId], [t].[OneToMany_Required_Self_InverseId], [t].[OneToOne_Optional_PK_InverseId], [t].[OneToOne_Optional_SelfId]
FROM [Level1] AS [l1]
LEFT JOIN [Level2] AS [l2] ON [l1].[Id] = [l2].[Level1_Optional_Id]
LEFT JOIN (
SELECT [l2].[Id], [l2].[Date], [l2].[Level1_Optional_Id], [l2].[Level1_Required_Id], [l2].[Name], [l2].[OneToMany_Optional_InverseId], [l2].[OneToMany_Optional_Self_InverseId], [l2].[OneToMany_Required_InverseId], [l2].[OneToMany_Required_Self_InverseId], [l2].[OneToOne_Optional_PK_InverseId], [l2].[OneToOne_Optional_SelfId]
FROM [Level2] AS [l2]
WHERE [l2].[Id] > 0
) AS [t] ON [l1].[Id] = [t].[Level1_Optional_Id]
ORDER BY [l1].[Id]",
Sql);
}
Expand All @@ -2628,9 +2648,13 @@ public override void GroupJoin_with_subquery_on_inner_and_no_DefaultIfEmpty()
base.GroupJoin_with_subquery_on_inner_and_no_DefaultIfEmpty();

Assert.Equal(
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [l2].[Id], [l2].[Date], [l2].[Level1_Optional_Id], [l2].[Level1_Required_Id], [l2].[Name], [l2].[OneToMany_Optional_InverseId], [l2].[OneToMany_Optional_Self_InverseId], [l2].[OneToMany_Required_InverseId], [l2].[OneToMany_Required_Self_InverseId], [l2].[OneToOne_Optional_PK_InverseId], [l2].[OneToOne_Optional_SelfId]
@"SELECT [l1].[Id], [l1].[Date], [l1].[Name], [l1].[OneToMany_Optional_Self_InverseId], [l1].[OneToMany_Required_Self_InverseId], [l1].[OneToOne_Optional_SelfId], [t].[Id], [t].[Date], [t].[Level1_Optional_Id], [t].[Level1_Required_Id], [t].[Name], [t].[OneToMany_Optional_InverseId], [t].[OneToMany_Optional_Self_InverseId], [t].[OneToMany_Required_InverseId], [t].[OneToMany_Required_Self_InverseId], [t].[OneToOne_Optional_PK_InverseId], [t].[OneToOne_Optional_SelfId]
FROM [Level1] AS [l1]
LEFT JOIN [Level2] AS [l2] ON [l1].[Id] = [l2].[Level1_Optional_Id]
LEFT JOIN (
SELECT [l2].[Id], [l2].[Date], [l2].[Level1_Optional_Id], [l2].[Level1_Required_Id], [l2].[Name], [l2].[OneToMany_Optional_InverseId], [l2].[OneToMany_Optional_Self_InverseId], [l2].[OneToMany_Required_InverseId], [l2].[OneToMany_Required_Self_InverseId], [l2].[OneToOne_Optional_PK_InverseId], [l2].[OneToOne_Optional_SelfId]
FROM [Level2] AS [l2]
WHERE [l2].[Id] > 0
) AS [t] ON [l1].[Id] = [t].[Level1_Optional_Id]
ORDER BY [l1].[Id]",
Sql);
}
Expand Down

0 comments on commit 39513aa

Please sign in to comment.