Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

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