diff --git a/src/EFCore.Relational/Query/CollectionResultExpression.cs b/src/EFCore.Relational/Query/CollectionResultExpression.cs
new file mode 100644
index 00000000000..72432446c5f
--- /dev/null
+++ b/src/EFCore.Relational/Query/CollectionResultExpression.cs
@@ -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;
+using System.Linq.Expressions;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Metadata;
+using Microsoft.EntityFrameworkCore.Utilities;
+
+namespace Microsoft.EntityFrameworkCore.Query
+{
+ ///
+ ///
+ /// An expression that represents creation of a collection in for relational providers.
+ ///
+ ///
+ /// This type is typically used by database providers (and other extensions). It is generally
+ /// not used in application code.
+ ///
+ ///
+ public class CollectionResultExpression : Expression, IPrintableExpression
+ {
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// An expression reprensenting how to get the subquery from SelectExpression to get the elements.
+ /// A navigation associated with this collection, if any.
+ /// The clr type of individual elements in the collection.
+ public CollectionResultExpression(
+ ProjectionBindingExpression projectionBindingExpression,
+ INavigationBase? navigation,
+ Type elementType)
+ {
+ ProjectionBindingExpression = projectionBindingExpression;
+ Navigation = navigation;
+ ElementType = elementType;
+ }
+
+ ///
+ /// The expression to get the subquery for this collection.
+ ///
+ public virtual ProjectionBindingExpression ProjectionBindingExpression { get; }
+ ///
+ /// The navigation if associated with the collection.
+ ///
+ public virtual INavigationBase? Navigation { get; }
+ ///
+ /// The clr type of elements of the collection.
+ ///
+ public virtual Type ElementType { get; }
+
+ ///
+ public override Type Type => ProjectionBindingExpression.Type;
+ ///
+ public override ExpressionType NodeType => ExpressionType.Extension;
+
+ ///
+ protected override Expression VisitChildren(ExpressionVisitor visitor)
+ {
+ Check.NotNull(visitor, nameof(visitor));
+
+ var projectionBindingExpression = (ProjectionBindingExpression)visitor.Visit(ProjectionBindingExpression);
+
+ return Update(projectionBindingExpression);
+ }
+
+ ///
+ /// Creates a new expression that is like this one, but using the supplied children. If all of the children are the same, it will
+ /// return this expression.
+ ///
+ /// The property of the result.
+ /// This expression if no children changed, or an expression with the updated children.
+ public virtual CollectionResultExpression Update(ProjectionBindingExpression projectionBindingExpression)
+ {
+ Check.NotNull(projectionBindingExpression, nameof(projectionBindingExpression));
+
+ return projectionBindingExpression != ProjectionBindingExpression
+ ? new CollectionResultExpression(projectionBindingExpression, Navigation, ElementType)
+ : this;
+ }
+
+ ///
+ public virtual void Print(ExpressionPrinter expressionPrinter)
+ {
+ expressionPrinter.AppendLine("CollectionResultExpression:");
+ using (expressionPrinter.Indent())
+ {
+ expressionPrinter.Append("ProjectionBindingExpression:");
+ expressionPrinter.Visit(ProjectionBindingExpression);
+ expressionPrinter.AppendLine();
+ if (Navigation != null)
+ {
+ expressionPrinter.Append("Navigation:").AppendLine(Navigation.ToString()!);
+ }
+ expressionPrinter.Append("ElementType:").AppendLine(ElementType.ShortDisplayName());
+ }
+ }
+ }
+}
diff --git a/src/EFCore.Relational/Query/EntityProjectionExpression.cs b/src/EFCore.Relational/Query/EntityProjectionExpression.cs
index 3f098ced243..3fc7d9c8480 100644
--- a/src/EFCore.Relational/Query/EntityProjectionExpression.cs
+++ b/src/EFCore.Relational/Query/EntityProjectionExpression.cs
@@ -216,5 +216,8 @@ public virtual void AddNavigationBinding(INavigation navigation, EntityShaperExp
? expression
: null;
}
+
+ ///
+ public override string ToString() => $"EntityProjectionExpression: {EntityType.ShortName()}";
}
}
diff --git a/src/EFCore.Relational/Query/Internal/CollectionJoinApplyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/CollectionJoinApplyingExpressionVisitor.cs
deleted file mode 100644
index d9947f5f66b..00000000000
--- a/src/EFCore.Relational/Query/Internal/CollectionJoinApplyingExpressionVisitor.cs
+++ /dev/null
@@ -1,99 +0,0 @@
-// 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;
-using System.Linq.Expressions;
-using Microsoft.EntityFrameworkCore.Diagnostics;
-using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
-using Microsoft.EntityFrameworkCore.Utilities;
-
-namespace Microsoft.EntityFrameworkCore.Query.Internal
-{
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public class CollectionJoinApplyingExpressionVisitor : ExpressionVisitor
- {
- private readonly bool _splitQuery;
- private readonly bool _noConfiguredBehavior;
- private readonly IDiagnosticsLogger _logger;
- private int _collectionId;
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- public CollectionJoinApplyingExpressionVisitor(RelationalQueryCompilationContext queryCompilationContext)
- {
- Check.NotNull(queryCompilationContext, nameof(queryCompilationContext));
-
- _splitQuery = queryCompilationContext.QuerySplittingBehavior == QuerySplittingBehavior.SplitQuery;
- _noConfiguredBehavior = queryCompilationContext.QuerySplittingBehavior == null;
- _logger = queryCompilationContext.Logger;
- }
-
- ///
- /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
- /// the same compatibility standards as public APIs. It may be changed or removed without notice in
- /// any release. You should only use it directly in your code with extreme caution and knowing that
- /// doing so can result in application failures when updating to a new Entity Framework Core release.
- ///
- protected override Expression VisitExtension(Expression extensionExpression)
- {
- Check.NotNull(extensionExpression, nameof(extensionExpression));
-
- if (extensionExpression is CollectionShaperExpression collectionShaperExpression)
- {
- var collectionId = _collectionId++;
-
- if (_noConfiguredBehavior
- && _collectionId == 2)
- {
- _logger.MultipleCollectionIncludeWarning();
- }
-
- var projectionBindingExpression = (ProjectionBindingExpression)collectionShaperExpression.Projection;
- var selectExpression = (SelectExpression)projectionBindingExpression.QueryExpression;
- // Do pushdown beforehand so it updates all pending collections first
- if (selectExpression.IsDistinct
- || selectExpression.Limit != null
- || selectExpression.Offset != null
- || selectExpression.GroupBy.Count > 0)
- {
- selectExpression.PushdownIntoSubquery();
- }
-
- var innerShaper = Visit(collectionShaperExpression.InnerShaper);
-
- var collectionJoin = selectExpression.ApplyCollectionJoin(
- projectionBindingExpression.Index!.Value,
- collectionId,
- innerShaper,
- collectionShaperExpression.Navigation,
- collectionShaperExpression.ElementType,
- _splitQuery);
-
- if (_splitQuery
- && collectionJoin == null)
- {
- throw new InvalidOperationException(
- RelationalStrings.UnableToSplitCollectionProjectionInSplitQuery(
- $"{nameof(QuerySplittingBehavior)}.{QuerySplittingBehavior.SplitQuery}",
- nameof(RelationalQueryableExtensions.AsSplitQuery),
- nameof(RelationalQueryableExtensions.AsSingleQuery)));
- }
-
- return collectionJoin!;
- }
-
- return extensionExpression is ShapedQueryExpression shapedQueryExpression
- ? shapedQueryExpression.UpdateShaperExpression(Visit(shapedQueryExpression.ShaperExpression))
- : base.VisitExtension(extensionExpression);
- }
- }
-}
diff --git a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs
index 0fe1df633c7..0ed2fbfbd31 100644
--- a/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/Internal/RelationalProjectionBindingExpressionVisitor.cs
@@ -10,7 +10,6 @@
using System.Reflection;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.EntityFrameworkCore.Infrastructure;
-using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
using Microsoft.EntityFrameworkCore.Storage;
using Microsoft.EntityFrameworkCore.Utilities;
@@ -33,9 +32,10 @@ private static readonly MethodInfo _getParameterValueMethodInfo
private readonly IncludeFindingExpressionVisitor _includeFindingExpressionVisitor;
private SelectExpression _selectExpression;
- private SqlExpression[] _existingProjections;
- private bool _clientEval;
+
+ private bool _indexBasedBinding;
private Dictionary? _entityProjectionCache;
+ private List? _clientProjections;
private readonly Dictionary _projectionMapping = new();
private readonly Stack _projectionMembers = new();
@@ -54,7 +54,6 @@ public RelationalProjectionBindingExpressionVisitor(
_sqlTranslator = sqlTranslatingExpressionVisitor;
_includeFindingExpressionVisitor = new IncludeFindingExpressionVisitor();
_selectExpression = null!;
- _existingProjections = Array.Empty();
}
///
@@ -66,7 +65,7 @@ public RelationalProjectionBindingExpressionVisitor(
public virtual Expression Translate(SelectExpression selectExpression, Expression expression)
{
_selectExpression = selectExpression;
- _clientEval = false;
+ _indexBasedBinding = false;
_projectionMembers.Push(new ProjectionMember());
@@ -75,21 +74,25 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
if (result == QueryCompilationContext.NotTranslatedExpression)
{
- _clientEval = true;
+ _indexBasedBinding = true;
_entityProjectionCache = new();
+ _projectionMapping.Clear();
+ _clientProjections = new List();
expandedExpression = _queryableMethodTranslatingExpressionVisitor.ExpandWeakEntities(_selectExpression, expression);
- _existingProjections = _selectExpression.Projection.Select(e => e.Expression).ToArray();
- _selectExpression.ClearProjection();
result = Visit(expandedExpression);
+ _selectExpression.ReplaceProjection(_clientProjections);
+ _clientProjections.Clear();
+ }
+ else
+ {
+ _selectExpression.ReplaceProjection(_projectionMapping);
_projectionMapping.Clear();
}
- _selectExpression.ReplaceProjectionMapping(_projectionMapping);
_selectExpression = null!;
_projectionMembers.Clear();
- _projectionMapping.Clear();
result = MatchTypes(result!, expression.Type);
@@ -123,66 +126,60 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
return parameter;
}
- if (_clientEval)
+ if (_indexBasedBinding)
{
switch (expression)
{
case ConstantExpression _:
return expression;
+ case ParameterExpression parameterExpression:
+ return parameterExpression.Name?.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal)
+ == true
+ ? Expression.Call(
+ _getParameterValueMethodInfo.MakeGenericMethod(parameterExpression.Type),
+ QueryCompilationContext.QueryContextParameter,
+ Expression.Constant(parameterExpression.Name))
+ : throw new InvalidOperationException(CoreStrings.TranslationFailed(parameterExpression.Print()));
+
case ProjectionBindingExpression projectionBindingExpression:
- if (projectionBindingExpression.Index is int index)
+ var mappedProjection = _selectExpression.GetProjection(projectionBindingExpression);
+ if (mappedProjection is EntityProjectionExpression entityProjection)
{
- var newIndex = _selectExpression.AddToProjection(_existingProjections[index]);
-
- return new ProjectionBindingExpression(_selectExpression, newIndex, expression.Type);
+ return AddClientProjection(entityProjection, typeof(ValueBuffer));
}
- if (projectionBindingExpression.ProjectionMember != null)
+ if (mappedProjection is SqlExpression mappedSqlExpression)
{
- // This would be SqlExpression. EntityProjectionExpression would be wrapped inside EntityShaperExpression.
- var mappedProjection = (SqlExpression)((SelectExpression)projectionBindingExpression.QueryExpression)
- .GetMappedProjection(projectionBindingExpression.ProjectionMember);
-
- return new ProjectionBindingExpression(
- _selectExpression, _selectExpression.AddToProjection(mappedProjection), expression.Type);
+ return AddClientProjection(mappedSqlExpression, expression.Type.MakeNullable());
}
throw new InvalidOperationException(CoreStrings.TranslationFailed(projectionBindingExpression.Print()));
- case ParameterExpression parameterExpression:
- if (parameterExpression.Name?.StartsWith(QueryCompilationContext.QueryParameterPrefix, StringComparison.Ordinal)
- == true)
- {
- return Expression.Call(
- _getParameterValueMethodInfo.MakeGenericMethod(parameterExpression.Type),
- QueryCompilationContext.QueryContextParameter,
- Expression.Constant(parameterExpression.Name));
- }
-
- throw new InvalidOperationException(CoreStrings.TranslationFailed(parameterExpression.Print()));
-
case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression:
- return _selectExpression.AddCollectionProjection(
- _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(
- materializeCollectionNavigationExpression.Subquery)!,
+ _clientProjections!.Add(_queryableMethodTranslatingExpressionVisitor.TranslateSubquery(
+ materializeCollectionNavigationExpression.Subquery)!);
+ return new CollectionResultExpression(
+ new ProjectionBindingExpression(_selectExpression, _clientProjections.Count - 1, expression.Type),
materializeCollectionNavigationExpression.Navigation,
materializeCollectionNavigationExpression.Navigation.ClrType.GetSequenceType());
case MethodCallExpression methodCallExpression:
- {
if (methodCallExpression.Method.IsGenericMethod
&& methodCallExpression.Method.DeclaringType == typeof(Enumerable)
- && methodCallExpression.Method.Name == nameof(Enumerable.ToList))
+ && methodCallExpression.Method.Name == nameof(Enumerable.ToList)
+ && methodCallExpression.Arguments.Count == 1
+ && methodCallExpression.Arguments[0].Type.TryGetElementType(typeof(IQueryable<>)) != null)
{
- var elementType = methodCallExpression.Method.GetGenericArguments()[0];
-
- var result = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(
+ var subquery = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(
methodCallExpression.Arguments[0]);
-
- if (result != null)
+ if (subquery != null)
{
- return _selectExpression.AddCollectionProjection(result, null, elementType);
+ _clientProjections!.Add(subquery);
+ return new CollectionResultExpression(
+ new ProjectionBindingExpression(_selectExpression, _clientProjections.Count - 1, expression.Type),
+ navigation: null,
+ methodCallExpression.Method.GetGenericArguments()[0]);
}
}
else
@@ -190,49 +187,32 @@ public virtual Expression Translate(SelectExpression selectExpression, Expressio
var subquery = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression);
if (subquery != null)
{
- if (subquery.ResultCardinality == ResultCardinality.Enumerable)
- {
- return _selectExpression.AddCollectionProjection(subquery, null, subquery.ShaperExpression.Type);
- }
-
- static bool IsAggregateResultWithCustomShaper(MethodInfo method)
+ // This simplifies the check when subquery is translated and can be lifted as scalar.
+ var scalarTranslation = _sqlTranslator.Translate(subquery);
+ if (scalarTranslation != null)
{
- if (method.IsGenericMethod)
- {
- method = method.GetGenericMethodDefinition();
- }
-
- return QueryableMethods.IsAverageWithoutSelector(method)
- || QueryableMethods.IsAverageWithSelector(method)
- || method == QueryableMethods.MaxWithoutSelector
- || method == QueryableMethods.MaxWithSelector
- || method == QueryableMethods.MinWithoutSelector
- || method == QueryableMethods.MinWithSelector
- || QueryableMethods.IsSumWithoutSelector(method)
- || QueryableMethods.IsSumWithSelector(method);
+ return AddClientProjection(scalarTranslation, expression.Type.MakeNullable());
}
- if (!(subquery.ShaperExpression is ProjectionBindingExpression
- || (subquery.ShaperExpression is UnaryExpression unaryExpression
- && unaryExpression.NodeType == ExpressionType.Convert
- && unaryExpression.Type.MakeNullable() == unaryExpression.Operand.Type
- && unaryExpression.Operand is ProjectionBindingExpression)
- || IsAggregateResultWithCustomShaper(methodCallExpression.Method)))
- {
- return _selectExpression.AddSingleProjection(subquery);
- }
+ _clientProjections!.Add(subquery);
+ var projectionBindingExpression = new ProjectionBindingExpression(
+ _selectExpression, _clientProjections.Count - 1, expression.Type);
+ return subquery.ResultCardinality == ResultCardinality.Enumerable
+ ? new CollectionResultExpression(
+ projectionBindingExpression, navigation: null, subquery.ShaperExpression.Type)
+ : projectionBindingExpression;
}
}
-
break;
- }
}
var translation = _sqlTranslator.Translate(expression);
- return translation == null
- ? base.Visit(expression)
- : new ProjectionBindingExpression(
- _selectExpression, _selectExpression.AddToProjection(translation), expression.Type.MakeNullable());
+ if (translation != null)
+ {
+ return AddClientProjection(translation, expression.Type.MakeNullable());
+ }
+
+ return base.Visit(expression);
}
else
{
@@ -306,38 +286,27 @@ protected override Expression VisitExtension(Expression extensionExpression)
EntityProjectionExpression entityProjectionExpression;
if (entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression)
{
- // If projectionBinding is not mapped then SelectExpression has client projection
- // Hence force client eval
- if (projectionBindingExpression.ProjectionMember == null)
+ if (projectionBindingExpression.ProjectionMember == null
+ && !_indexBasedBinding)
{
- if (_clientEval)
- {
- var indexMap = new Dictionary();
- foreach (var item in projectionBindingExpression.IndexMap!)
- {
- indexMap[item.Key] = _selectExpression.AddToProjection(_existingProjections[item.Value]);
- }
-
- return entityShaperExpression.Update(new ProjectionBindingExpression(_selectExpression, indexMap));
- }
-
+ // If projectionBinding is not mapped via projection member then it is bound via index
+ // Hence we need to switch to index based binding too.
return QueryCompilationContext.NotTranslatedExpression;
}
entityProjectionExpression = (EntityProjectionExpression)((SelectExpression)projectionBindingExpression.QueryExpression)
- .GetMappedProjection(projectionBindingExpression.ProjectionMember);
+ .GetProjection(projectionBindingExpression);
}
else
{
entityProjectionExpression = (EntityProjectionExpression)entityShaperExpression.ValueBufferExpression;
}
- if (_clientEval)
+ if (_indexBasedBinding)
{
if (!_entityProjectionCache!.TryGetValue(entityProjectionExpression, out var entityProjectionBinding))
{
- entityProjectionBinding = new ProjectionBindingExpression(
- _selectExpression, _selectExpression.AddToProjection(entityProjectionExpression));
+ entityProjectionBinding = AddClientProjection(entityProjectionExpression, typeof(ValueBuffer));
_entityProjectionCache[entityProjectionExpression] = entityProjectionBinding;
}
@@ -351,10 +320,25 @@ protected override Expression VisitExtension(Expression extensionExpression)
}
case IncludeExpression _:
- return _clientEval ? base.VisitExtension(extensionExpression) : QueryCompilationContext.NotTranslatedExpression;
+ return _indexBasedBinding ? base.VisitExtension(extensionExpression) : QueryCompilationContext.NotTranslatedExpression;
- case CollectionShaperExpression _:
- return _clientEval ? extensionExpression : QueryCompilationContext.NotTranslatedExpression;
+ case CollectionResultExpression collectionResultExpression:
+ {
+ // TODO this should not be needed at some point, we shouldn't be revisit same projection.
+ // This happens because we don't process result selector for Join/SelectMany directly.
+ if (_indexBasedBinding)
+ {
+ Check.DebugAssert(ReferenceEquals(_selectExpression, collectionResultExpression.ProjectionBindingExpression.QueryExpression),
+ "The projection should belong to same select expression.");
+ var mappedProjection = _selectExpression.GetProjection(collectionResultExpression.ProjectionBindingExpression);
+ _clientProjections!.Add(mappedProjection);
+
+ return collectionResultExpression.Update(
+ new ProjectionBindingExpression(_selectExpression, _clientProjections.Count - 1, collectionResultExpression.Type));
+ }
+
+ return QueryCompilationContext.NotTranslatedExpression;
+ }
default:
throw new InvalidOperationException(CoreStrings.TranslationFailed(extensionExpression.Print()));
@@ -410,7 +394,7 @@ protected override MemberAssignment VisitMemberAssignment(MemberAssignment membe
{
var expression = memberAssignment.Expression;
Expression? visitedExpression;
- if (_clientEval)
+ if (_indexBasedBinding)
{
visitedExpression = Visit(memberAssignment.Expression);
}
@@ -524,7 +508,7 @@ protected override Expression VisitNew(NewExpression newExpression)
return newExpression;
}
- if (!_clientEval
+ if (!_indexBasedBinding
&& newExpression.Members == null)
{
return QueryCompilationContext.NotTranslatedExpression;
@@ -535,7 +519,7 @@ protected override Expression VisitNew(NewExpression newExpression)
{
var argument = newExpression.Arguments[i];
Expression? visitedArgument;
- if (_clientEval)
+ if (_indexBasedBinding)
{
visitedArgument = Visit(argument);
}
@@ -598,6 +582,18 @@ private static Expression MatchTypes(Expression expression, Type targetType)
return expression;
}
+ private ProjectionBindingExpression AddClientProjection(Expression expression, Type type)
+ {
+ var existingIndex = _clientProjections!.FindIndex(e => e.Equals(expression));
+ if (existingIndex == -1)
+ {
+ _clientProjections.Add(expression);
+ existingIndex = _clientProjections.Count - 1;
+ }
+
+ return new ProjectionBindingExpression(_selectExpression, existingIndex, type);
+ }
+
#pragma warning disable IDE0052 // Remove unread private members
private static T GetParameterValue(QueryContext queryContext, string parameterName)
#pragma warning restore IDE0052 // Remove unread private members
diff --git a/src/EFCore.Relational/Query/Internal/SelectExpressionProjectionApplyingExpressionVisitor.cs b/src/EFCore.Relational/Query/Internal/SelectExpressionProjectionApplyingExpressionVisitor.cs
index e80aa3f2f29..4c3b5f1079f 100644
--- a/src/EFCore.Relational/Query/Internal/SelectExpressionProjectionApplyingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/Internal/SelectExpressionProjectionApplyingExpressionVisitor.cs
@@ -15,6 +15,19 @@ namespace Microsoft.EntityFrameworkCore.Query.Internal
///
public class SelectExpressionProjectionApplyingExpressionVisitor : ExpressionVisitor
{
+ private readonly QuerySplittingBehavior _querySplittingBehavior;
+
+ ///
+ /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
+ /// the same compatibility standards as public APIs. It may be changed or removed without notice in
+ /// any release. You should only use it directly in your code with extreme caution and knowing that
+ /// doing so can result in application failures when updating to a new Entity Framework Core release.
+ ///
+ public SelectExpressionProjectionApplyingExpressionVisitor(QuerySplittingBehavior? querySplittingBehavior)
+ {
+ _querySplittingBehavior = querySplittingBehavior ?? QuerySplittingBehavior.SingleQuery;
+ }
+
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
@@ -25,18 +38,12 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
Check.NotNull(extensionExpression, nameof(extensionExpression));
- if (extensionExpression is ShapedQueryExpression shapedQueryExpression)
- {
- return shapedQueryExpression.Update(Visit(shapedQueryExpression.QueryExpression), shapedQueryExpression.ShaperExpression);
- }
-
- if (extensionExpression is SelectExpression selectExpression)
- {
- selectExpression.ApplyProjection();
- // We visit base to apply projection to inner SelectExpressions
- }
-
- return base.VisitExtension(extensionExpression);
+ return extensionExpression is ShapedQueryExpression shapedQueryExpression
+ && shapedQueryExpression.QueryExpression is SelectExpression selectExpression
+ ? shapedQueryExpression.Update(
+ selectExpression,
+ selectExpression.ApplyProjection(shapedQueryExpression.ShaperExpression, _querySplittingBehavior))
+ : base.VisitExtension(extensionExpression);
}
}
}
diff --git a/src/EFCore.Relational/Query/RelationalCollectionShaperExpression.cs b/src/EFCore.Relational/Query/RelationalCollectionShaperExpression.cs
index a41f0035614..6f77a52913b 100644
--- a/src/EFCore.Relational/Query/RelationalCollectionShaperExpression.cs
+++ b/src/EFCore.Relational/Query/RelationalCollectionShaperExpression.cs
@@ -60,6 +60,7 @@ public RelationalCollectionShaperExpression(
/// An expression used to create individual elements of the collection.
/// A navigation associated with this collection, if any.
/// The clr type of individual elements in the collection.
+ [Obsolete("Use ctor without collectionId")]
public RelationalCollectionShaperExpression(
int collectionId,
Expression parentIdentifier,
@@ -90,10 +91,51 @@ public RelationalCollectionShaperExpression(
ElementType = elementType;
}
+ ///
+ /// Creates a new instance of the class.
+ ///
+ /// An identifier for the parent element.
+ /// An identifier for the outer element.
+ /// An identifier for the element in the collection.
+ /// A list of value comparers to compare parent identifier.
+ /// A list of value comparers to compare outer identifier.
+ /// A list of value comparers to compare self identifier.
+ /// An expression used to create individual elements of the collection.
+ /// A navigation associated with this collection, if any.
+ /// The clr type of individual elements in the collection.
+ public RelationalCollectionShaperExpression(
+ Expression parentIdentifier,
+ Expression outerIdentifier,
+ Expression selfIdentifier,
+ IReadOnlyList? parentIdentifierValueComparers,
+ IReadOnlyList? outerIdentifierValueComparers,
+ IReadOnlyList? selfIdentifierValueComparers,
+ Expression innerShaper,
+ INavigationBase? navigation,
+ Type elementType)
+ {
+ Check.NotNull(parentIdentifier, nameof(parentIdentifier));
+ Check.NotNull(outerIdentifier, nameof(outerIdentifier));
+ Check.NotNull(selfIdentifier, nameof(selfIdentifier));
+ Check.NotNull(innerShaper, nameof(innerShaper));
+ Check.NotNull(elementType, nameof(elementType));
+
+ ParentIdentifier = parentIdentifier;
+ OuterIdentifier = outerIdentifier;
+ SelfIdentifier = selfIdentifier;
+ ParentIdentifierValueComparers = parentIdentifierValueComparers;
+ OuterIdentifierValueComparers = outerIdentifierValueComparers;
+ SelfIdentifierValueComparers = selfIdentifierValueComparers;
+ InnerShaper = innerShaper;
+ Navigation = navigation;
+ ElementType = elementType;
+ }
+
///
/// A unique id for this collection shaper.
///
- public virtual int CollectionId { get; }
+ [Obsolete("CollectionId are not stored in shaper anymore. Shaper compiler assigns it as needed.")]
+ public virtual int? CollectionId { get; }
///
/// The identifier for the parent element.
@@ -186,7 +228,7 @@ public virtual RelationalCollectionShaperExpression Update(
|| selfIdentifier != SelfIdentifier
|| innerShaper != InnerShaper
? new RelationalCollectionShaperExpression(
- CollectionId, parentIdentifier, outerIdentifier, selfIdentifier,
+ parentIdentifier, outerIdentifier, selfIdentifier,
ParentIdentifierValueComparers, OuterIdentifierValueComparers, SelfIdentifierValueComparers,
innerShaper, Navigation, ElementType)
: this;
@@ -200,7 +242,6 @@ void IPrintableExpression.Print(ExpressionPrinter expressionPrinter)
expressionPrinter.AppendLine("RelationalCollectionShaper:");
using (expressionPrinter.Indent())
{
- expressionPrinter.AppendLine($"CollectionId: {CollectionId}");
expressionPrinter.Append("ParentIdentifier:");
expressionPrinter.Visit(ParentIdentifier);
expressionPrinter.AppendLine();
diff --git a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs
index 7dfa5916708..b84cc8429b1 100644
--- a/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryTranslationPostprocessor.cs
@@ -46,8 +46,8 @@ public RelationalQueryTranslationPostprocessor(
public override Expression Process(Expression query)
{
query = base.Process(query);
- query = new SelectExpressionProjectionApplyingExpressionVisitor().Visit(query);
- query = new CollectionJoinApplyingExpressionVisitor((RelationalQueryCompilationContext)QueryCompilationContext).Visit(query);
+ query = new SelectExpressionProjectionApplyingExpressionVisitor(
+ ((RelationalQueryCompilationContext)QueryCompilationContext).QuerySplittingBehavior).Visit(query);
#if DEBUG
// TODO: 24460 blocks from enabling this
//query = new TableAliasVerifyingExpressionVisitor().Visit(query);
diff --git a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
index 698cc218679..79436458c77 100644
--- a/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.cs
@@ -196,7 +196,7 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent
var selectExpression = (SelectExpression)source.QueryExpression;
selectExpression.ApplyPredicate(_sqlExpressionFactory.Not(translation));
- selectExpression.ReplaceProjectionMapping(new Dictionary());
+ selectExpression.ReplaceProjection(new Dictionary());
if (selectExpression.Limit == null
&& selectExpression.Offset == null)
{
@@ -226,7 +226,7 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent
}
var selectExpression = (SelectExpression)source.QueryExpression;
- selectExpression.ReplaceProjectionMapping(new Dictionary());
+ selectExpression.ReplaceProjection(new Dictionary());
if (selectExpression.Limit == null
&& selectExpression.Offset == null)
{
@@ -248,32 +248,7 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent
Check.NotNull(source, nameof(source));
Check.NotNull(resultType, nameof(resultType));
- var selectExpression = (SelectExpression)source.QueryExpression;
- selectExpression.PrepareForAggregate();
- HandleGroupByForAggregate(selectExpression);
-
- var newSelector = selector == null
- || selector.Body == selector.Parameters[0]
- ? selectExpression.Projection.Count == 0
- ? selectExpression.GetMappedProjection(new ProjectionMember())
- : null
- : RemapLambdaBody(source, selector);
-
- if (newSelector == null)
- {
- return null;
- }
-
- var translatedSelector = TranslateExpression(newSelector);
- if (translatedSelector == null)
- {
- return null;
- }
-
- var projection = _sqlTranslator.TranslateAverage(translatedSelector);
- return projection != null
- ? AggregateResultShaper(source, projection, throwWhenEmpty: true, resultType)
- : null;
+ return TranslateAggregateWithSelector(source, selector, e => _sqlTranslator.TranslateAverage(e), throwWhenEmpty: true, resultType);
}
///
@@ -318,14 +293,26 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent
selectExpression.ClearOrdering();
}
- selectExpression.ApplyProjection();
- translation = _sqlExpressionFactory.In(translation, selectExpression, false);
+ // TODO: See issue #24671
+ if (source.ShaperExpression is ProjectionBindingExpression projectionBindingExpression)
+ {
+ var projection = selectExpression.GetProjection(projectionBindingExpression);
+ if (projection is SqlExpression sqlExpression)
+ {
+ selectExpression.ReplaceProjection(new List());
+ selectExpression.AddToProjection(sqlExpression);
- return source.Update(
- _sqlExpressionFactory.Select(translation),
- Expression.Convert(
- new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool?)),
- typeof(bool)));
+ translation = _sqlExpressionFactory.In(translation, selectExpression, false);
+
+ return new ShapedQueryExpression(
+ _sqlExpressionFactory.Select(translation),
+ Expression.Convert(
+ new ProjectionBindingExpression(source.QueryExpression, new ProjectionMember(), typeof(bool?)),
+ typeof(bool)));
+ }
+ }
+
+ return null;
}
///
@@ -357,7 +344,7 @@ private static ShapedQueryExpression CreateShapedQueryExpression(IEntityType ent
var projectionMapping = new Dictionary { { new ProjectionMember(), translation } };
selectExpression.ClearOrdering();
- selectExpression.ReplaceProjectionMapping(projectionMapping);
+ selectExpression.ReplaceProjection(projectionMapping);
return source.UpdateShaperExpression(
Expression.Convert(
@@ -757,7 +744,7 @@ private SqlExpression CreateJoinPredicate(Expression outerKey, Expression innerK
var projectionMapping = new Dictionary { { new ProjectionMember(), translation } };
selectExpression.ClearOrdering();
- selectExpression.ReplaceProjectionMapping(projectionMapping);
+ selectExpression.ReplaceProjection(projectionMapping);
return source.UpdateShaperExpression(
Expression.Convert(
@@ -769,64 +756,18 @@ private SqlExpression CreateJoinPredicate(Expression outerKey, Expression innerK
protected override ShapedQueryExpression? TranslateMax(ShapedQueryExpression source, LambdaExpression? selector, Type resultType)
{
Check.NotNull(source, nameof(source));
+ Check.NotNull(resultType, nameof(resultType));
- var selectExpression = (SelectExpression)source.QueryExpression;
- selectExpression.PrepareForAggregate();
- HandleGroupByForAggregate(selectExpression);
-
- var newSelector = selector == null
- || selector.Body == selector.Parameters[0]
- ? selectExpression.Projection.Count == 0
- ? selectExpression.GetMappedProjection(new ProjectionMember())
- : null
- : RemapLambdaBody(source, selector);
-
- if (newSelector == null)
- {
- return null;
- }
-
- var translatedSelector = TranslateExpression(newSelector);
- if (translatedSelector == null)
- {
- return null;
- }
-
- var projection = _sqlTranslator.TranslateMax(translatedSelector);
-
- return AggregateResultShaper(source, projection, throwWhenEmpty: true, resultType);
+ return TranslateAggregateWithSelector(source, selector, e => _sqlTranslator.TranslateMax(e), throwWhenEmpty: true, resultType);
}
///
protected override ShapedQueryExpression? TranslateMin(ShapedQueryExpression source, LambdaExpression? selector, Type resultType)
{
Check.NotNull(source, nameof(source));
+ Check.NotNull(resultType, nameof(resultType));
- var selectExpression = (SelectExpression)source.QueryExpression;
- selectExpression.PrepareForAggregate();
- HandleGroupByForAggregate(selectExpression);
-
- var newSelector = selector == null
- || selector.Body == selector.Parameters[0]
- ? selectExpression.Projection.Count == 0
- ? selectExpression.GetMappedProjection(new ProjectionMember())
- : null
- : RemapLambdaBody(source, selector);
-
- if (newSelector == null)
- {
- return null;
- }
-
- var translatedSelector = TranslateExpression(newSelector);
- if (translatedSelector == null)
- {
- return null;
- }
-
- var projection = _sqlTranslator.TranslateMin(translatedSelector);
-
- return AggregateResultShaper(source, projection, throwWhenEmpty: true, resultType);
+ return TranslateAggregateWithSelector(source, selector, e => _sqlTranslator.TranslateMin(e), throwWhenEmpty: true, resultType);
}
///
@@ -872,8 +813,8 @@ private SqlExpression CreateJoinPredicate(Expression outerKey, Expression innerK
var projectionMember = projectionBindingExpression.ProjectionMember;
Check.DebugAssert(new ProjectionMember().Equals(projectionMember), "Invalid ProjectionMember when processing OfType");
- var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetMappedProjection(projectionMember);
- selectExpression.ReplaceProjectionMapping(
+ var entityProjectionExpression = (EntityProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
+ selectExpression.ReplaceProjection(
new Dictionary
{
{ projectionMember, entityProjectionExpression.UpdateEntityType(derivedType) }
@@ -1120,32 +1061,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
Check.NotNull(source, nameof(source));
Check.NotNull(resultType, nameof(resultType));
- var selectExpression = (SelectExpression)source.QueryExpression;
- selectExpression.PrepareForAggregate();
- HandleGroupByForAggregate(selectExpression);
-
- var newSelector = selector == null
- || selector.Body == selector.Parameters[0]
- ? selectExpression.Projection.Count == 0
- ? selectExpression.GetMappedProjection(new ProjectionMember())
- : null
- : RemapLambdaBody(source, selector);
-
- if (newSelector == null)
- {
- return null;
- }
-
- var translatedSelector = TranslateExpression(newSelector);
- if (translatedSelector == null)
- {
- return null;
- }
-
- var projection = _sqlTranslator.TranslateSum(translatedSelector);
- return projection != null
- ? AggregateResultShaper(source, projection, throwWhenEmpty: false, resultType)
- : null;
+ return TranslateAggregateWithSelector(source, selector, e => _sqlTranslator.TranslateSum(e), throwWhenEmpty: false, resultType);
}
///
@@ -1412,7 +1328,7 @@ outerKey is NewArrayExpression newArrayExpression
var entityProjectionExpression = (EntityProjectionExpression)
(entityShaperExpression.ValueBufferExpression is ProjectionBindingExpression projectionBindingExpression
- ? _selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember!)
+ ? _selectExpression.GetProjection(projectionBindingExpression)
: entityShaperExpression.ValueBufferExpression);
var innerShaper = entityProjectionExpression.BindNavigation(navigation);
@@ -1529,15 +1445,9 @@ private static void HandleGroupByForAggregate(SelectExpression selectExpression,
{
if (eraseProjection)
{
- selectExpression.ReplaceProjectionMapping(new Dictionary());
- selectExpression.AddToProjection(selectExpression.GroupBy[0]);
- selectExpression.PushdownIntoSubquery();
- selectExpression.ClearProjection();
- }
- else
- {
- selectExpression.PushdownIntoSubquery();
+ selectExpression.ReplaceProjection(new Dictionary());
}
+ selectExpression.PushdownIntoSubquery();
}
}
@@ -1585,19 +1495,57 @@ private Expression MatchShaperNullabilityForSetOperation(Expression shaper1, Exp
}
}
- private ShapedQueryExpression? AggregateResultShaper(
+ private ShapedQueryExpression? TranslateAggregateWithSelector(
ShapedQueryExpression source,
- Expression? projection,
+ LambdaExpression? selector,
+ Func aggregateTranslator,
bool throwWhenEmpty,
Type resultType)
{
+ var selectExpression = (SelectExpression)source.QueryExpression;
+ selectExpression.PrepareForAggregate();
+ HandleGroupByForAggregate(selectExpression);
+
+ SqlExpression translatedSelector;
+ if (selector == null
+ || selector.Body == selector.Parameters[0])
+ {
+ var shaperExpression = source.ShaperExpression;
+ if (shaperExpression is UnaryExpression unaryExpression
+ && unaryExpression.NodeType == ExpressionType.Convert)
+ {
+ shaperExpression = unaryExpression.Operand;
+ }
+
+ if (shaperExpression is ProjectionBindingExpression projectionBindingExpression)
+ {
+ translatedSelector = (SqlExpression)selectExpression.GetProjection(projectionBindingExpression);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ else
+ {
+ var newSelector = RemapLambdaBody(source, selector);
+ if (TranslateExpression(newSelector) is SqlExpression sqlExpression)
+ {
+ translatedSelector = sqlExpression;
+ }
+ else
+ {
+ return null;
+ }
+ }
+
+ var projection = aggregateTranslator(translatedSelector);
if (projection == null)
{
return null;
}
- var selectExpression = (SelectExpression)source.QueryExpression;
- selectExpression.ReplaceProjectionMapping(
+ selectExpression.ReplaceProjection(
new Dictionary { { new ProjectionMember(), projection } });
selectExpression.ClearOrdering();
@@ -1632,7 +1580,7 @@ private Expression MatchShaperNullabilityForSetOperation(Expression shaper1, Exp
returnValueForNull,
resultType != resultVariable.Type
? Expression.Convert(resultVariable, resultType)
- : (Expression)resultVariable));
+ : resultVariable));
}
else
{
diff --git a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
index ccde34ec354..53f409e34ac 100644
--- a/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
+++ b/src/EFCore.Relational/Query/RelationalShapedQueryCompilingExpressionVisitor.ShaperProcessingExpressionVisitor.cs
@@ -121,6 +121,7 @@ private static readonly MethodInfo _collectionAccessorAddMethodInfo
// Since identifiers for collection are not part of larger lambda they don't cannot use caching to materialize only once.
private bool _inline;
+ private int _collectionId;
// States to convert code to data reader read
private readonly IDictionary> _materializationContextBindings
@@ -216,9 +217,11 @@ private ShaperProcessingExpressionVisitor(
public LambdaExpression ProcessShaper(
Expression shaperExpression,
out RelationalCommandCache? relationalCommandCache,
- out LambdaExpression? relatedDataLoaders)
+ out LambdaExpression? relatedDataLoaders,
+ ref int collectionId)
{
relatedDataLoaders = null;
+ _collectionId = collectionId;
if (_indexMapParameter != null)
{
@@ -348,6 +351,8 @@ public LambdaExpression ProcessShaper(
_parentVisitor._useRelationalNulls)
: null;
+ collectionId = _collectionId;
+
return Expression.Lambda(
result,
QueryCompilationContext.QueryContextParameter,
@@ -397,8 +402,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
{
case RelationalEntityShaperExpression entityShaperExpression:
{
- var key = GenerateKey((ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression);
- if (!_variableShaperMapping.TryGetValue(key, out var accessor))
+ if (!_variableShaperMapping.TryGetValue(entityShaperExpression.ValueBufferExpression, out var accessor))
{
var entityParameter = Expression.Parameter(entityShaperExpression.Type);
_variables.Add(entityParameter);
@@ -421,7 +425,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
accessor = entityParameter;
}
- _variableShaperMapping[key] = accessor;
+ _variableShaperMapping[entityShaperExpression.ValueBufferExpression] = accessor;
}
return accessor;
@@ -444,8 +448,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
case ProjectionBindingExpression projectionBindingExpression
when !_inline:
{
- var key = GenerateKey(projectionBindingExpression);
- if (_variableShaperMapping.TryGetValue(key, out var accessor))
+ if (_variableShaperMapping.TryGetValue(projectionBindingExpression, out var accessor))
{
return accessor;
}
@@ -487,7 +490,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
accessor = valueParameter;
}
- _variableShaperMapping[key] = accessor;
+ _variableShaperMapping[projectionBindingExpression] = accessor;
return accessor;
}
@@ -498,11 +501,12 @@ protected override Expression VisitExtension(Expression extensionExpression)
if (includeExpression.NavigationExpression is RelationalCollectionShaperExpression
relationalCollectionShaperExpression)
{
+ var collectionIdConstant = Expression.Constant(_collectionId++);
var innerShaper = new ShaperProcessingExpressionVisitor(
_parentVisitor, _resultCoordinatorParameter, _selectExpression, _dataReaderParameter,
_resultContextParameter,
_readerColumns)
- .ProcessShaper(relationalCollectionShaperExpression.InnerShaper, out _, out _);
+ .ProcessShaper(relationalCollectionShaperExpression.InnerShaper, out _, out _, ref _collectionId);
var entityType = entity.Type;
var navigation = includeExpression.Navigation;
@@ -532,8 +536,6 @@ protected override Expression VisitExtension(Expression extensionExpression)
_inline = false;
- var collectionIdConstant = Expression.Constant(relationalCollectionShaperExpression.CollectionId);
-
_includeExpressions.Add(
Expression.Call(
_initializeIncludeCollectionMethodInfo.MakeGenericMethod(entityType, includingEntityType),
@@ -583,13 +585,15 @@ protected override Expression VisitExtension(Expression extensionExpression)
else if (includeExpression.NavigationExpression is RelationalSplitCollectionShaperExpression
relationalSplitCollectionShaperExpression)
{
+ var collectionIdConstant = Expression.Constant(_collectionId++);
var innerProcessor = new ShaperProcessingExpressionVisitor(
_parentVisitor, _resultCoordinatorParameter,
_executionStrategyParameter!, relationalSplitCollectionShaperExpression.SelectExpression, _tags!);
var innerShaper = innerProcessor.ProcessShaper(
relationalSplitCollectionShaperExpression.InnerShaper,
out var relationalCommandCache,
- out var relatedDataLoaders);
+ out var relatedDataLoaders,
+ ref _collectionId);
var entityType = entity.Type;
var navigation = includeExpression.Navigation;
@@ -618,8 +622,6 @@ protected override Expression VisitExtension(Expression extensionExpression)
innerProcessor._inline = false;
- var collectionIdConstant = Expression.Constant(relationalSplitCollectionShaperExpression.CollectionId);
-
_includeExpressions.Add(
Expression.Call(
_initializeSplitIncludeCollectionMethodInfo.MakeGenericMethod(entityType, includingEntityType),
@@ -699,14 +701,14 @@ protected override Expression VisitExtension(Expression extensionExpression)
case RelationalCollectionShaperExpression relationalCollectionShaperExpression:
{
- var key = GenerateKey(relationalCollectionShaperExpression);
- if (!_variableShaperMapping.TryGetValue(key, out var accessor))
+ if (!_variableShaperMapping.TryGetValue(relationalCollectionShaperExpression, out var accessor))
{
+ var collectionIdConstant = Expression.Constant(_collectionId++);
var innerShaper = new ShaperProcessingExpressionVisitor(
_parentVisitor, _resultCoordinatorParameter, _selectExpression, _dataReaderParameter,
_resultContextParameter,
_readerColumns)
- .ProcessShaper(relationalCollectionShaperExpression.InnerShaper, out _, out _);
+ .ProcessShaper(relationalCollectionShaperExpression.InnerShaper, out _, out _, ref _collectionId);
var navigation = relationalCollectionShaperExpression.Navigation;
var collectionAccessor = navigation?.GetCollectionAccessor();
@@ -733,8 +735,6 @@ protected override Expression VisitExtension(Expression extensionExpression)
_inline = false;
- var collectionIdConstant = Expression.Constant(relationalCollectionShaperExpression.CollectionId);
-
var collectionParameter = Expression.Parameter(relationalCollectionShaperExpression.Type);
_variables.Add(collectionParameter);
_expressions.Add(
@@ -778,7 +778,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
typeof(IReadOnlyList)),
Expression.Constant(innerShaper.Compile())));
- _variableShaperMapping[key] = accessor;
+ _variableShaperMapping[relationalCollectionShaperExpression] = accessor;
}
return accessor;
@@ -786,16 +786,17 @@ protected override Expression VisitExtension(Expression extensionExpression)
case RelationalSplitCollectionShaperExpression relationalSplitCollectionShaperExpression:
{
- var key = GenerateKey(relationalSplitCollectionShaperExpression);
- if (!_variableShaperMapping.TryGetValue(key, out var accessor))
+ if (!_variableShaperMapping.TryGetValue(relationalSplitCollectionShaperExpression, out var accessor))
{
+ var collectionIdConstant = Expression.Constant(_collectionId++);
var innerProcessor = new ShaperProcessingExpressionVisitor(
_parentVisitor, _resultCoordinatorParameter,
_executionStrategyParameter!, relationalSplitCollectionShaperExpression.SelectExpression, _tags!);
var innerShaper = innerProcessor.ProcessShaper(
relationalSplitCollectionShaperExpression.InnerShaper,
out var relationalCommandCache,
- out var relatedDataLoaders);
+ out var relatedDataLoaders,
+ ref _collectionId);
var navigation = relationalSplitCollectionShaperExpression.Navigation;
var collectionAccessor = navigation?.GetCollectionAccessor();
@@ -821,8 +822,6 @@ protected override Expression VisitExtension(Expression extensionExpression)
innerProcessor._inline = false;
- var collectionIdConstant = Expression.Constant(relationalSplitCollectionShaperExpression.CollectionId);
-
var collectionParameter = Expression.Parameter(collectionType);
_variables.Add(collectionParameter);
_expressions.Add(
@@ -865,7 +864,7 @@ protected override Expression VisitExtension(Expression extensionExpression)
? typeof(Func)
: typeof(Action))));
- _variableShaperMapping[key] = accessor;
+ _variableShaperMapping[relationalSplitCollectionShaperExpression] = accessor;
}
return accessor;
@@ -955,21 +954,11 @@ private static Expression AddToCollectionNavigation(
relatedEntity,
Expression.Constant(true));
- private Expression GenerateKey(Expression expression)
- => expression is ProjectionBindingExpression projectionBindingExpression
- && projectionBindingExpression.ProjectionMember != null
- ? _selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember)
- : expression;
-
private object GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)
- => projectionBindingExpression.ProjectionMember != null
- ? _selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember).GetConstantValue