Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
35 changes: 27 additions & 8 deletions src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1288,11 +1288,25 @@ NewExpression EvaluateChildren(NewExpression @new, Expression[]? arguments, Stat
/// </summary>
protected override Expression VisitParameter(ParameterExpression parameterExpression)
{
// ParameterExpressions are lambda parameters, which we cannot evaluate.
// However, _allowedParameters is a mechanism to allow evaluating Select(), see VisitMethodCall.
_state = _evaluatableParameters.Contains(parameterExpression)
? State.CreateEvaluatable(typeof(ParameterExpression), containsCapturedVariable: false)
: State.NoEvaluatability;
// ParameterExpressions are lambda parameters, which are not evaluatable unless they are part of an evaluatable lambda;
// see the Enumerable.Select handling in VisitMethodCall. Even then, a parameter can only be evaluated as part of that
// larger lambda fragment, and never as an evaluatable root - see TryHandleNonEvaluatableAsRoot below.
if (_evaluatableParameters.Contains(parameterExpression))
{
var parameterExpression2 = parameterExpression;
_state = State.CreateEvaluatable(
typeof(ParameterExpression),
containsCapturedVariable: false,
notEvaluatableAsRootHandler: () =>
{
_state = State.NoEvaluatability;
return parameterExpression2;
});
Comment thread
roji marked this conversation as resolved.
}
else
{
_state = State.NoEvaluatability;
}

return parameterExpression;
}
Expand Down Expand Up @@ -1932,9 +1946,11 @@ private static StateType CombineStateTypes(StateType stateType1, StateType state
// different SQLs for each value.
|| !_inLambda);

// We have some cases where a node is evaluatable, but only as part of a larger subtree, and should not be evaluated as a tree root.
// For these cases, the node's state has a notEvaluatableAsRootHandler lambda, which we can invoke to make evaluate the node's
// children (as needed), but not itself.
// Some nodes are evaluatable only as part of a larger subtree, and must not be evaluated as roots by themselves.
// For example, new[] { x, y } should generally be preserved as an inline list, and a ParameterExpression made
// evaluatable for an Enumerable.Select lambda is only bound inside that lambda. For these cases, the node's state
// has a notEvaluatableAsRootHandler lambda, which evaluates children as needed or otherwise preserves the node
// while setting the appropriate state, but does not evaluate the root itself.
if (!forceEvaluation && TryHandleNonEvaluatableAsRoot(evaluatableRoot, state, evaluateAsParameter, out var result))
{
return result;
Expand Down Expand Up @@ -2039,6 +2055,9 @@ bool TryHandleNonEvaluatableAsRoot(Expression root, State state, bool asParamete
// There are some cases of Convert nodes which we shouldn't evaluate when they're at the top of an evaluatable root (but can
// evaluate when they're part of a larger fragment).
case UnaryExpression unary when PreserveConvertNode(unary):
// Parameters made evaluatable for an Enumerable.Select lambda are only evaluatable inside that lambda, and attempting to
// evaluate them as roots produces an unbound parameter.
case ParameterExpression when state.NotEvaluatableAsRootHandler is not null:
result = state.NotEvaluatableAsRootHandler!();
return true;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1056,6 +1056,14 @@ public override async Task SelectMany_correlated_with_outer_1(bool async)
AssertSql();
}

public override async Task SelectMany_over_inline_array_projecting_range_variable_and_outer(bool async)
{
// Cosmos client evaluation. Issue #17246.
await AssertTranslationFailed(() => base.SelectMany_over_inline_array_projecting_range_variable_and_outer(async));

AssertSql();
}

public override async Task SelectMany_correlated_with_outer_2(bool async)
{
// Cosmos client evaluation. Issue #17246.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1203,6 +1203,15 @@ public virtual Task SelectMany_without_result_selector_collection_navigation_com
async,
ss => ss.Set<Customer>().SelectMany(c => c.Orders.Select(o => o.CustomerID)));

[ConditionalTheory, MemberData(nameof(IsAsyncData))]
public virtual Task SelectMany_over_inline_array_projecting_range_variable_and_outer(bool async)
=> AssertQuery(
async,
ss => ss.Set<Customer>()
.Where(c => c.CustomerID == "ALFKI")
.SelectMany(c => new[] { "a", "b" }.Select(k => new { k, c })),
elementSorter: e => e.k);

[ConditionalTheory, MemberData(nameof(IsAsyncData))]
public virtual Task SelectMany_correlated_with_outer_1(bool async)
=> AssertQuery(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1230,6 +1230,19 @@ FROM [Customers] AS [c]
""");
}

public override async Task SelectMany_over_inline_array_projecting_range_variable_and_outer(bool async)
{
await base.SelectMany_over_inline_array_projecting_range_variable_and_outer(async);

AssertSql(
"""
SELECT [v].[Value] AS [k], [c].[CustomerID], [c].[Address], [c].[City], [c].[CompanyName], [c].[ContactName], [c].[ContactTitle], [c].[Country], [c].[Fax], [c].[Phone], [c].[PostalCode], [c].[Region]
FROM [Customers] AS [c]
CROSS APPLY (VALUES (CAST(N'a' AS nvarchar(max))), (N'b')) AS [v]([Value])
WHERE [c].[CustomerID] = N'ALFKI'
""");
}

public override async Task SelectMany_correlated_with_outer_1(bool async)
{
await base.SelectMany_correlated_with_outer_1(async);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,12 @@ public override async Task SelectMany_correlated_with_outer_1(bool async)
SqliteStrings.ApplyNotSupported,
(await Assert.ThrowsAsync<InvalidOperationException>(() => base.SelectMany_correlated_with_outer_1(async))).Message);

public override async Task SelectMany_over_inline_array_projecting_range_variable_and_outer(bool async)
=> Assert.Equal(
SqliteStrings.ApplyNotSupported,
(await Assert.ThrowsAsync<InvalidOperationException>(
() => base.SelectMany_over_inline_array_projecting_range_variable_and_outer(async))).Message);

public override async Task SelectMany_correlated_with_outer_2(bool async)
=> Assert.Equal(
SqliteStrings.ApplyNotSupported,
Expand Down
Loading