Skip to content

Commit

Permalink
Relational: Implement split query for non-include collections
Browse files Browse the repository at this point in the history
Resolves #21234
Resolves #22283
  • Loading branch information
smitpatel committed Apr 21, 2021
1 parent 438b05f commit 32f42cb
Show file tree
Hide file tree
Showing 25 changed files with 2,420 additions and 922 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 0 additions & 3 deletions src/EFCore.Relational/Properties/RelationalStrings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -748,9 +748,6 @@
<data name="UnableToBindMemberToEntityProjection" xml:space="preserve">
<value>Unable to bind '{memberType}.{member}' to an entity projection of '{entityType}'.</value>
</data>
<data name="UnableToSplitCollectionProjectionInSplitQuery" xml:space="preserve">
<value>The query has been configured to use '{splitQueryEnumValue}', but contains a collection in the 'Select' call which could not be split into a separate query. Remove '{splitQueryMethodName}' if applied, or add '{singleQueryMethodName}' to the query.</value>
</data>
<data name="UnhandledExpressionInVisitor" xml:space="preserve">
<value>Unhandled expression '{expression}' of type '{expressionType}' encountered in '{visitor}'.</value>
</data>
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,5 @@ public override Expression NormalizeQueryableMethod(Expression expression)

return expression;
}

/// <inheritdoc />
public override Expression Process(Expression query)
{
query = base.Process(query);

return _relationalQueryCompilationContext.QuerySplittingBehavior == QuerySplittingBehavior.SplitQuery
? new SplitIncludeRewritingExpressionVisitor().Visit(query)
: query;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ namespace Microsoft.EntityFrameworkCore.Query.SqlExpressions
/// not used in application code.
/// </para>
/// </summary>
public class FromSqlExpression : TableExpressionBase
public class FromSqlExpression : TableExpressionBase, ICloneable
{
/// <summary>
/// Creates a new instance of the <see cref="FromSqlExpression" /> class.
Expand Down Expand Up @@ -95,6 +95,9 @@ protected override Expression VisitChildren(ExpressionVisitor visitor)
return this;
}

/// <inheritdoc />
public virtual object Clone() => new FromSqlExpression(Alias, Sql, Arguments);

/// <inheritdoc />
protected override void Print(ExpressionPrinter expressionPrinter)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -712,11 +712,15 @@ public SelectExpressionVerifyingExpressionVisitor(IEnumerable<TableReferenceExpr
Visit(childIdentifier.Column);
}

break;
return selectExpression;

case ConcreteColumnExpression concreteColumnExpression:
concreteColumnExpression.Verify(_tableReferencesInScope);
break;
return concreteColumnExpression;

case ShapedQueryExpression shapedQueryExpression:
Verify(shapedQueryExpression.QueryExpression, _tableReferencesInScope);
return shapedQueryExpression;
}

return base.Visit(expression);
Expand All @@ -727,5 +731,95 @@ public static void Verify(Expression expression, IEnumerable<TableReferenceExpre
=> new SelectExpressionVerifyingExpressionVisitor(tableReferencesInScope)
.Visit(expression);
}

private sealed class CloningExpressionVisitor : ExpressionVisitor
{
[return: NotNullIfNotNull("expression")]
public override Expression? Visit(Expression? expression)
{
if (expression is SelectExpression selectExpression)
{
// We ignore projection binding related elements as we don't want to copy them over for top level
// Nested level will have _projection populated and no binding elements
var newProjections = selectExpression._projection.Select(Visit).ToList<ProjectionExpression>();

var newTables = selectExpression._tables.Select(Visit).ToList<TableExpressionBase>();
// Since we are cloning we need to generate new table references
// In other cases (like VisitChildren), we just reuse the same table references and update the SelectExpression inside it.
// We initially assign old SelectExpression in table references and later update it once we construct clone
var newTableReferences = selectExpression._tableReferences
.Select(e => new TableReferenceExpression(selectExpression, e.Alias)).ToList();
Check.DebugAssert(
newTables.Select(e => GetAliasFromTableExpressionBase(e)).SequenceEqual(newTableReferences.Select(e => e.Alias)),
"Alias of updated tables must match the old tables.");

var predicate = (SqlExpression?)Visit(selectExpression.Predicate);
var newGroupBy = selectExpression._groupBy.Select(Visit)
.Where(e => !(e is SqlConstantExpression || e is SqlParameterExpression))
.ToList<SqlExpression>();
var havingExpression = (SqlExpression?)Visit(selectExpression.Having);
var newOrderings = selectExpression._orderings.Select(Visit).ToList<OrderingExpression>();
var offset = (SqlExpression?)Visit(selectExpression.Offset);
var limit = (SqlExpression?)Visit(selectExpression.Limit);

var newSelectExpression = new SelectExpression(selectExpression.Alias, newProjections, newTables, newTableReferences, newGroupBy, newOrderings)
{
Predicate = predicate,
Having = havingExpression,
Offset = offset,
Limit = limit,
IsDistinct = selectExpression.IsDistinct,
Tags = selectExpression.Tags,
_usedAliases = selectExpression._usedAliases.ToHashSet()
};

newSelectExpression._tptLeftJoinTables.AddRange(selectExpression._tptLeftJoinTables);
// Since identifiers are ColumnExpression, they are not visited since they don't contain SelectExpression inside it.
newSelectExpression._identifier.AddRange(selectExpression._identifier);
newSelectExpression._childIdentifiers.AddRange(selectExpression._childIdentifiers);

// Remap tableReferences in new select expression
foreach (var tableReference in newTableReferences)
{
tableReference.UpdateTableReference(selectExpression, newSelectExpression);
}

// Now that we have SelectExpression, we visit all components and update table references inside columns
newSelectExpression = (SelectExpression)new ColumnExpressionReplacingExpressionVisitor(selectExpression, newSelectExpression)
.Visit(newSelectExpression);

return newSelectExpression;

}

return expression is ICloneable cloneable ? (Expression)cloneable.Clone() : base.Visit(expression);
}
}

private sealed class ColumnExpressionReplacingExpressionVisitor : ExpressionVisitor
{
private readonly SelectExpression _oldSelectExpression;
private readonly Dictionary<string, TableReferenceExpression> _newTableReferences;

public ColumnExpressionReplacingExpressionVisitor(SelectExpression oldSelectExpression, SelectExpression newSelectExpression)
{
_oldSelectExpression = oldSelectExpression;
_newTableReferences = newSelectExpression._tableReferences.ToDictionary(e => e.Alias);
}

[return: NotNullIfNotNull("expression")]
public override Expression? Visit(Expression? expression)
{
return expression is ConcreteColumnExpression concreteColumnExpression
&& _oldSelectExpression.ContainsTableReference(concreteColumnExpression)
? new ConcreteColumnExpression(
concreteColumnExpression.Name,
_newTableReferences[concreteColumnExpression.TableAlias],
concreteColumnExpression.Type,
concreteColumnExpression.TypeMapping!,
concreteColumnExpression.IsNullable)
: base.Visit(expression);
}
}
}
}
Loading

0 comments on commit 32f42cb

Please sign in to comment.