diff --git a/EFCore.sln.DotSettings b/EFCore.sln.DotSettings
index 1b4974fd608..d1ddc281f56 100644
--- a/EFCore.sln.DotSettings
+++ b/EFCore.sln.DotSettings
@@ -291,14 +291,21 @@ The .NET Foundation licenses this file to you under the MIT license.
True
True
True
+ True
+ True
+ True
True
True
True
True
True
+ True
True
+ True
+ True
True
True
+ True
True
True
True
@@ -316,6 +323,7 @@ The .NET Foundation licenses this file to you under the MIT license.
True
True
True
+ True
True
True
True
diff --git a/src/EFCore/Properties/CoreStrings.Designer.cs b/src/EFCore/Properties/CoreStrings.Designer.cs
index 1eb205020bb..4678648bc8d 100644
--- a/src/EFCore/Properties/CoreStrings.Designer.cs
+++ b/src/EFCore/Properties/CoreStrings.Designer.cs
@@ -982,8 +982,8 @@ public static string EFConstantInvoked
///
/// The EF.Constant<T> method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
///
- public static string EFConstantWithNonEvaluableArgument
- => GetString("EFConstantWithNonEvaluableArgument");
+ public static string EFConstantWithNonEvaluatableArgument
+ => GetString("EFConstantWithNonEvaluatableArgument");
///
/// The EF.Parameter<T> method may only be used within Entity Framework LINQ queries.
@@ -991,6 +991,12 @@ public static string EFConstantWithNonEvaluableArgument
public static string EFParameterInvoked
=> GetString("EFParameterInvoked");
+ ///
+ /// The EF.Parameter<T> method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
+ ///
+ public static string EFParameterWithNonEvaluatableArgument
+ => GetString("EFParameterWithNonEvaluatableArgument");
+
///
/// Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model.
///
@@ -1766,6 +1772,18 @@ public static string ManyToManyOneNav(object? entityType, object? navigation)
GetString("ManyToManyOneNav", nameof(entityType), nameof(navigation)),
entityType, navigation);
+ ///
+ /// EF Core does not support MemberListBinding: 'new Blog { Posts = { new Post(), new Post() } }'.
+ ///
+ public static string MemberListBindingNotSupported
+ => GetString("MemberListBindingNotSupported");
+
+ ///
+ /// EF Core does not support MemberMemberBinding: 'new Blog { Data = { Name = "hello world" } }'.
+ ///
+ public static string MemberMemberBindingNotSupported
+ => GetString("MemberMemberBindingNotSupported");
+
///
/// The specified field '{field}' could not be found for property '{2_entityType}.{1_property}'.
///
diff --git a/src/EFCore/Properties/CoreStrings.resx b/src/EFCore/Properties/CoreStrings.resx
index 9d7fe91b5f7..7ad9abf40d8 100644
--- a/src/EFCore/Properties/CoreStrings.resx
+++ b/src/EFCore/Properties/CoreStrings.resx
@@ -480,12 +480,15 @@
The EF.Constant<T> method may only be used within Entity Framework LINQ queries.
-
+
The EF.Constant<T> method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
The EF.Parameter<T> method may only be used within Entity Framework LINQ queries.
+
+ The EF.Parameter<T> method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
+
Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model.
@@ -1108,6 +1111,12 @@
The navigation '{entityType}.{navigation}' cannot be used for both sides of a many-to-many relationship. Many-to-many relationships must use two distinct navigation properties.
+
+ EF Core does not support MemberListBinding: 'new Blog { Posts = { new Post(), new Post() } }'.
+
+
+ EF Core does not support MemberMemberBinding: 'new Blog { Data = { Name = "hello world" } }'.
+
The specified field '{field}' could not be found for property '{2_entityType}.{1_property}'.
diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
new file mode 100644
index 00000000000..6e1427b3d54
--- /dev/null
+++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
@@ -0,0 +1,2029 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers;
+using System.Collections.ObjectModel;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using E = System.Linq.Expressions.Expression;
+
+namespace Microsoft.EntityFrameworkCore.Query.Internal;
+
+///
+/// This visitor identifies subtrees in the query which can be evaluated client-side (i.e. no reference to server-side resources),
+/// and evaluates those subtrees, integrating the result either as a constant (if the subtree contained no captured closure variables),
+/// or as parameters.
+///
+///
+/// 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 ExpressionTreeFuncletizer : ExpressionVisitor
+{
+ // The general algorithm here is the following.
+ // 1. First, for each node type, visit that node's children and get their states (evaluatable, contains evaluatable, no evaluatable).
+ // 2. Calculate the parent node's aggregate state from its children; a container node whose children are all evaluatable is itself
+ // evaluatable, etc.
+ // 3. If the parent node is evaluatable (because all its children are), simply bubble that up - nothing more to do
+ // 4. If the parent node isn't evaluatable but contains an evaluatable child, that child is an evaluatable root for its fragment.
+ // Evaluate it, making it either into a parameter (if it contains any captured variables), or into a constant (if not).
+ // 5. If we're in path extraction mode (precompiled queries), build a path back up from the evaluatable roots to the query root; this
+ // is what later gets used to generate code to evaluate and extract those fragments as parameters. If we're in regular parameter
+ // parameter extraction (not precompilation), don't do this (not needed) and just return "not evaluatable".
+
+ ///
+ /// Indicates whether we're calculating the paths to all parameterized evaluatable roots (precompilation mode), or doing regular,
+ /// non-precompiled parameter extraction.
+ ///
+ private bool _calculatingPath;
+
+ ///
+ /// Indicates whether we should parameterize. Is false in in compiled query mode, as well as when we're handling query filters
+ /// from NavigationExpandingExpressionVisitor.
+ ///
+ private bool _parameterize;
+
+ ///
+ /// Indicates whether we're currently within a lambda. When not in a lambda, we evaluate evaluatables as constants even if they
+ /// don't contains a captured variable (Skip/Take case).
+ ///
+ private bool _inLambda;
+
+ ///
+ /// A provider-facing extensibility hook to allow preventing certain expression nodes from being evaluated (typically specific
+ /// methods).
+ ///
+ private readonly IEvaluatableExpressionFilter _evaluatableExpressionFilter;
+
+ ///
+ /// is generally considered as non-evaluatable, since it represents a lambda parameter and we
+ /// don't evaluate lambdas. The one exception is a Select operator over something evaluatable (e.g. a parameterized list) - this
+ /// does need to get evaluated. This list contains instances for that case, to allow
+ /// evaluatability.
+ ///
+ private readonly HashSet _evaluatableParameters = new();
+
+ ///
+ /// A cache of tree fragments that have already been parameterized, along with their parameter. This allows us to reuse the same
+ /// query parameter twice when the same captured variable is referenced in the query.
+ ///
+ private readonly Dictionary _parameterizedValues = new(ExpressionEqualityComparer.Instance);
+
+ ///
+ /// Used only when evaluating arbitrary QueryRootExpressions (specifically SqlQueryRootExpression), to force any evaluatable nested
+ /// expressions to get evaluated as roots, since the query root itself is never evaluatable.
+ ///
+ private bool _evaluateRoot;
+
+ ///
+ /// Enabled only when funcletization is invoked on query filters from within NavigationExpandingExpressionVisitor. Causes special
+ /// handling for DbContext when it's referenced from within the query filter (e.g. for the tenant ID).
+ ///
+ private readonly bool _generateContextAccessors;
+
+ private IQueryProvider? _currentQueryProvider;
+ private State _state;
+ private IParameterValues _parameterValues = null!;
+
+ private readonly IModel _model;
+ private readonly ContextParameterReplacer _contextParameterReplacer;
+ private readonly IDiagnosticsLogger _logger;
+
+ private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection).GetProperties()
+ .Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;
+
+ private static readonly MethodInfo ReadOnlyMemberBindingCollectionIndexerGetter = typeof(ReadOnlyCollection)
+ .GetProperties()
+ .Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;
+
+ private static readonly PropertyInfo MemberAssignmentExpressionProperty =
+ typeof(MemberAssignment).GetProperty(nameof(MemberAssignment.Expression))!;
+
+ private static readonly ArrayPool StateArrayPool = ArrayPool.Shared;
+
+ private const string QueryFilterPrefix = "ef_filter";
+
+ ///
+ /// 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 ExpressionTreeFuncletizer(
+ IModel model,
+ IEvaluatableExpressionFilter evaluatableExpressionFilter,
+ Type contextType,
+ bool generateContextAccessors,
+ IDiagnosticsLogger logger)
+ {
+ _model = model;
+ _evaluatableExpressionFilter = evaluatableExpressionFilter;
+ _generateContextAccessors = generateContextAccessors;
+ _contextParameterReplacer = _generateContextAccessors
+ ? new ContextParameterReplacer(contextType)
+ : null!;
+ _logger = logger;
+ }
+
+ ///
+ /// Processes an expression tree, extracting parameters and evaluating evaluatable fragments as part of the pass.
+ /// Used for regular query execution (neither compiled nor pre-compiled).
+ ///
+ ///
+ /// 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 E ExtractParameters(
+ E expression,
+ IParameterValues parameterValues,
+ bool parameterize,
+ bool clearParameterizedValues)
+ {
+ Reset(clearParameterizedValues);
+ _parameterValues = parameterValues;
+ _parameterize = parameterize;
+ _calculatingPath = false;
+
+ var root = Visit(expression, out var state);
+
+ Check.DebugAssert(!state.ContainsEvaluatable, "In parameter extraction mode, end state should not contain evaluatable");
+
+ // If the top-most node in the tree is evaluatable, evaluate it.
+ if (state.IsEvaluatable)
+ {
+ root = ProcessEvaluatableRoot(root, ref state);
+ }
+
+ return root;
+ }
+
+ ///
+ /// Processes an expression tree, locates references to captured variables and returns information on how to extract them from
+ /// expression trees with the same shape. Used to generate C# code for query precompilation.
+ ///
+ /// A tree representing the path to each evaluatable root node in the tree.
+ ///
+ /// 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 PathNode? CalculatePathsToEvaluatableRoots(E expression)
+ {
+ Reset();
+ _calculatingPath = true;
+
+ // In precompilation mode we don't actually extract parameter values; but we do need to generate the parameter names, using the
+ // same logic (and via the same code) used in parameter extraction, and that logic requires _parameterValues.
+ _parameterValues = new DummyParameterValues();
+
+ _ = Visit(expression, out var state);
+
+ return state.Path;
+ }
+
+ private void Reset(bool clearParameterizedValues = true)
+ {
+ _inLambda = false;
+ _currentQueryProvider = null;
+ _evaluateRoot = false;
+ _evaluatableParameters.Clear();
+
+ if (clearParameterizedValues)
+ {
+ _parameterizedValues.Clear();
+ }
+ }
+
+ [return: NotNullIfNotNull("expression")]
+ private E? Visit(E? expression, out State state)
+ {
+ _state = default;
+ var result = base.Visit(expression);
+ state = _state;
+ return result;
+ }
+
+ ///
+ /// 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.
+ ///
+ [return: NotNullIfNotNull("expression")]
+ public override E? Visit(E? expression)
+ {
+ _state = default;
+
+ if (_evaluateRoot)
+ {
+ // This path is only called from VisitExtension for query roots, as a way of evaluating expressions inside query roots
+ // (i.e. SqlQueryRootExpression.Arguments).
+ _evaluateRoot = false;
+ var result = base.Visit(expression);
+ _evaluateRoot = true;
+
+ if (_state.IsEvaluatable)
+ {
+ result = ProcessEvaluatableRoot(result, ref _state);
+ // TODO: Test this scenario in path calculation mode (probably need to handle children path?)
+ }
+
+ return result;
+ }
+
+ return base.Visit(expression);
+ }
+
+ #region Visitation implementations
+
+ ///
+ /// 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 E VisitBinary(BinaryExpression binary)
+ {
+ var left = Visit(binary.Left, out var leftState);
+
+ // Perform short-circuiting checks to avoid evaluating the right side if not necessary
+ object? leftValue = null;
+ if (leftState.IsEvaluatable)
+ {
+ switch (binary.NodeType)
+ {
+ case ExpressionType.Coalesce:
+ leftValue = Evaluate(left);
+
+ switch (leftValue)
+ {
+ case null:
+ return Visit(binary.Right, out _state);
+ case bool b:
+ _state = leftState with { StateType = StateType.EvaluatableWithoutCapturedVariable };
+ return E.Constant(b);
+ default:
+ return left;
+ }
+
+ case ExpressionType.OrElse or ExpressionType.AndAlso when Evaluate(left) is bool leftBoolValue:
+ {
+ left = E.Constant(leftBoolValue);
+ leftState = leftState with { StateType = StateType.EvaluatableWithoutCapturedVariable };
+
+ if (leftBoolValue && binary.NodeType is ExpressionType.OrElse
+ || !leftBoolValue && binary.NodeType is ExpressionType.AndAlso)
+ {
+ _state = leftState;
+ return left;
+ }
+
+ binary = binary.Update(left, binary.Conversion, binary.Right);
+ break;
+ }
+ }
+ }
+
+ var right = Visit(binary.Right, out var rightState);
+
+ if (binary.NodeType is ExpressionType.AndAlso or ExpressionType.OrElse)
+ {
+ if (leftState.IsEvaluatable && leftValue is bool leftBoolValue)
+ {
+ switch ((leftConstant: leftBoolValue, binary.NodeType))
+ {
+ case (true, ExpressionType.AndAlso) or (false, ExpressionType.OrElse):
+ _state = rightState;
+ return right;
+ case (true, ExpressionType.OrElse) or (false, ExpressionType.AndAlso):
+ throw new UnreachableException(); // Already handled above before visiting the right side
+ }
+ }
+
+ if (rightState.IsEvaluatable && Evaluate(right) is bool rightBoolValue)
+ {
+ switch ((binary.NodeType, rightConstant: rightBoolValue))
+ {
+ case (ExpressionType.AndAlso, true) or (ExpressionType.OrElse, false):
+ _state = leftState;
+ return left;
+ case (ExpressionType.OrElse, true) or (ExpressionType.AndAlso, false):
+ _state = rightState with { StateType = StateType.EvaluatableWithoutCapturedVariable };
+ return E.Constant(rightBoolValue);
+ }
+ }
+ }
+
+ // We're done with simplification/short-circuiting checks specific to BinaryExpression.
+ var state = CombineStateTypes(leftState.StateType, rightState.StateType);
+
+ switch (state)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ when IsGenerallyEvaluatable(binary):
+ _state = State.CreateEvaluatable(typeof(BinaryExpression), state is StateType.EvaluatableWithCapturedVariable);
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ if (leftState.IsEvaluatable)
+ {
+ left = ProcessEvaluatableRoot(left, ref leftState);
+ }
+
+ if (rightState.IsEvaluatable)
+ {
+ right = ProcessEvaluatableRoot(right, ref rightState);
+ }
+
+ List? children = null;
+
+ if (_calculatingPath)
+ {
+ if (leftState.ContainsEvaluatable)
+ {
+ children =
+ [
+ leftState.Path! with { PathFromParent = static e => E.Property(e, nameof(BinaryExpression.Left)) }
+ ];
+ }
+
+ if (rightState.ContainsEvaluatable)
+ {
+ children ??= new();
+ children.Add(rightState.Path! with { PathFromParent = static e => E.Property(e, nameof(BinaryExpression.Right)) });
+ }
+ }
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(ConditionalExpression), children);
+ break;
+ }
+
+ return binary.Update(left, binary.Conversion, right);
+ }
+
+ ///
+ /// 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 E VisitConditional(ConditionalExpression conditional)
+ {
+ var test = Visit(conditional.Test, out var testState);
+
+ // If the test evaluates, simplify the conditional away by bubbling up the leg that remains
+ if (testState.IsEvaluatable && Evaluate(conditional.Test) is bool testBoolValue)
+ {
+ return testBoolValue
+ ? Visit(conditional.IfTrue, out _state)
+ : Visit(conditional.IfFalse, out _state);
+ }
+
+ var ifTrue = Visit(conditional.IfTrue, out var ifTrueState);
+ var ifFalse = Visit(conditional.IfFalse, out var ifFalseState);
+
+ var state = CombineStateTypes(testState.StateType, CombineStateTypes(ifTrueState.StateType, ifFalseState.StateType));
+
+ switch (state)
+ {
+ // If all three children are evaluatable, so is this conditional expression; simply bubble up, we're part of an evaluatable
+ // fragment that will get evaluated somewhere above.
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ when IsGenerallyEvaluatable(conditional):
+ _state = State.CreateEvaluatable(typeof(ConditionalExpression), state is StateType.EvaluatableWithCapturedVariable);
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ if (testState.IsEvaluatable)
+ {
+ // Early optimization - if the test is evaluatable, simply reduce the conditional to the relevant clause
+ if (Evaluate(test) is bool testConstant)
+ {
+ _state = testConstant ? ifTrueState : ifFalseState;
+ return testConstant ? ifTrue : ifFalse;
+ }
+
+ test = ProcessEvaluatableRoot(test, ref testState);
+ }
+
+ if (ifTrueState.IsEvaluatable)
+ {
+ ifTrue = ProcessEvaluatableRoot(ifTrue, ref ifTrueState);
+ }
+
+ if (ifFalseState.IsEvaluatable)
+ {
+ ifFalse = ProcessEvaluatableRoot(ifFalse, ref ifFalseState);
+ }
+
+ List? children = null;
+
+ if (_calculatingPath)
+ {
+ if (testState.ContainsEvaluatable)
+ {
+ children ??= new();
+ children.Add(
+ testState.Path! with { PathFromParent = static e => E.Property(e, nameof(ConditionalExpression.Test)) });
+ }
+
+ if (ifTrueState.ContainsEvaluatable)
+ {
+ children ??= new();
+ children.Add(
+ ifTrueState.Path! with { PathFromParent = static e => E.Property(e, nameof(ConditionalExpression.IfTrue)) });
+ }
+
+ if (ifFalseState.ContainsEvaluatable)
+ {
+ children ??= new();
+ children.Add(
+ ifFalseState.Path! with { PathFromParent = static e => E.Property(e, nameof(ConditionalExpression.IfFalse)) });
+ }
+ }
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(ConditionalExpression), children);
+ break;
+ }
+
+ return conditional.Update(test, ifTrue, ifFalse);
+ }
+
+ ///
+ /// 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 E VisitConstant(ConstantExpression constant)
+ {
+ // Whether this constant represents a captured variable determines whether we'll evaluate it as a parameter (if yes) or as a
+ // constant (if no).
+ var isCapturedVariable =
+ // This identifies compiler-generated closure types which contain captured variables.
+ (constant.Type.Attributes.HasFlag(TypeAttributes.NestedPrivate)
+ && Attribute.IsDefined(constant.Type, typeof(CompilerGeneratedAttribute), inherit: true))
+ // The following is for supporting the Find method (we should look into this and possibly clean it up).
+ || constant.Type == typeof(ValueBuffer);
+
+ _state = constant.Value is IQueryable
+ ? State.NoEvaluatability
+ : State.CreateEvaluatable(typeof(ConstantExpression), isCapturedVariable);
+
+ return constant;
+ }
+
+ ///
+ /// 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 E VisitDefault(DefaultExpression node)
+ {
+ _state = State.CreateEvaluatable(typeof(DefaultExpression), containsCapturedVariable: false);
+ return node;
+ }
+
+ ///
+ /// 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 E VisitExtension(E extension)
+ {
+ if (extension is QueryRootExpression queryRoot)
+ {
+ var queryProvider = queryRoot.QueryProvider;
+ if (_currentQueryProvider == null)
+ {
+ _currentQueryProvider = queryProvider;
+ }
+ else if (!ReferenceEquals(queryProvider, _currentQueryProvider))
+ {
+ throw new InvalidOperationException(CoreStrings.ErrorInvalidQueryable);
+ }
+
+ // Visit after detaching query provider since custom query roots can have additional components
+ extension = queryRoot.DetachQueryProvider();
+
+ // The following is somewhat hacky. We're going to visit the query root's children via VisitChildren - this is primarily for
+ // FromSqlQueryRootExpression. Since the query root itself is never evaluatable, its children should all be handled as
+ // evaluatable roots - we set _evaluateRoot and do that in Visit.
+ // In addition, FromSqlQueryRootExpression's Arguments need to be a parameter rather than constant, so we set _inLambda to
+ // make that happen (quite hacky, but was done this way in the old ParameterExtractingEV as well). Think about a better way.
+ _evaluateRoot = true;
+ var parentInLambda = _inLambda;
+ _inLambda = false;
+ var visitedExtension = base.VisitExtension(extension);
+ _evaluateRoot = false;
+ _inLambda = parentInLambda;
+ _state = State.NoEvaluatability;
+ return visitedExtension;
+ }
+
+ return base.VisitExtension(extension);
+ }
+
+ ///
+ /// 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 E VisitInvocation(InvocationExpression invocation)
+ {
+ var expression = Visit(invocation.Expression, out var expressionState);
+ var state = expressionState.StateType;
+ var arguments = Visit(invocation.Arguments, ref state, out var argumentStates);
+
+ switch (state)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ when IsGenerallyEvaluatable(invocation):
+ _state = State.CreateEvaluatable(typeof(InvocationExpression), state is StateType.EvaluatableWithCapturedVariable);
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ List? children = null;
+
+ if (expressionState.IsEvaluatable)
+ {
+ expression = ProcessEvaluatableRoot(expression, ref expressionState);
+ }
+
+ if (expressionState.ContainsEvaluatable && _calculatingPath)
+ {
+ children =
+ [
+ expressionState.Path! with { PathFromParent = static e => E.Property(e, nameof(InvocationExpression.Expression)) }
+ ];
+ }
+
+ arguments = EvaluateList(
+ ((IReadOnlyList?)arguments) ?? invocation.Arguments,
+ argumentStates,
+ children,
+ static i => e =>
+ E.Call(
+ E.Property(e, nameof(InvocationExpression.Arguments)),
+ ReadOnlyCollectionIndexerGetter,
+ arguments: [E.Constant(i)]));
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(InvocationExpression), children);
+ break;
+ }
+
+ StateArrayPool.Return(argumentStates);
+ return invocation.Update(expression, ((IReadOnlyList?)arguments) ?? invocation.Arguments);
+ }
+
+ ///
+ /// 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 E VisitIndex(IndexExpression index)
+ {
+ var @object = Visit(index.Object, out var objectState);
+ var state = objectState.StateType;
+ var arguments = Visit(index.Arguments, ref state, out var argumentStates);
+
+ switch (state)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable:
+ _state = State.CreateEvaluatable(typeof(IndexExpression), state is StateType.EvaluatableWithCapturedVariable);
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ List? children = null;
+
+ if (objectState.IsEvaluatable)
+ {
+ @object = ProcessEvaluatableRoot(@object, ref objectState);
+ }
+
+ if (objectState.ContainsEvaluatable && _calculatingPath)
+ {
+ children = [objectState.Path! with { PathFromParent = static e => E.Property(e, nameof(IndexExpression.Object)) }];
+ }
+
+ arguments = EvaluateList(
+ ((IReadOnlyList?)arguments) ?? index.Arguments,
+ argumentStates,
+ children,
+ static i => e =>
+ E.Call(
+ E.Property(e, nameof(IndexExpression.Arguments)),
+ ReadOnlyCollectionIndexerGetter,
+ arguments: [E.Constant(i)]));
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(IndexExpression), children);
+ break;
+ }
+
+ StateArrayPool.Return(argumentStates);
+
+ // TODO: https://github.com/dotnet/runtime/issues/96626
+ return index.Update(@object!, ((IReadOnlyList?)arguments) ?? index.Arguments);
+ }
+
+ ///
+ /// 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 E VisitLambda(Expression lambda)
+ {
+ var oldInLambda = _inLambda;
+ _inLambda = true;
+
+ var body = Visit(lambda.Body, out _state);
+ lambda = lambda.Update(body, lambda.Parameters);
+
+ if (_state.StateType is StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable)
+ {
+ // The lambda body is evaluatable. If all lambda parameters are also in the _allowedParameters set (this happens for
+ // Select() over an evaluatable source, see VisitMethodCall()), then the whole lambda is evaluatable. Otherwise, evaluate
+ // the body.
+ if (lambda.Parameters.All(parameter => _evaluatableParameters.Contains(parameter)))
+ {
+ _state = State.CreateEvaluatable(typeof(LambdaExpression), _state.ContainsCapturedVariable);
+ return lambda;
+ }
+
+ lambda = lambda.Update(ProcessEvaluatableRoot(lambda.Body, ref _state), lambda.Parameters);
+ }
+
+ if (_state.ContainsEvaluatable)
+ {
+ _state = State.CreateContainsEvaluatable(
+ typeof(LambdaExpression),
+ [_state.Path! with { PathFromParent = static e => E.Property(e, nameof(Expression.Body)) }]);
+ }
+
+ _inLambda = oldInLambda;
+
+ return lambda;
+ }
+
+ ///
+ /// 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 E VisitMember(MemberExpression member)
+ {
+ // Static member access - notably required for EF.Functions, but also for various translations (DateTime.Now).
+ if (member.Expression is null)
+ {
+ _state = IsGenerallyEvaluatable(member)
+ ? State.CreateEvaluatable(typeof(MemberExpression), containsCapturedVariable: false)
+ : State.NoEvaluatability;
+ return member;
+ }
+
+ var expression = Visit(member.Expression, out _state);
+
+ if (_state.IsEvaluatable)
+ {
+ // If the query contains a captured variable that's a nested IQueryable, inline it into the main query.
+ // Otherwise, evaluation of a terminating operator up the call chain will cause us to execute the query and do another
+ // roundtrip.
+ // Note that we only do this when the MemberExpression is typed as IQueryable/IOrderedQueryable; this notably excludes
+ // DbSet captured variables integrated directly into the query, as that also evaluates e.g. context.Order in
+ // context.Order.FromSqlInterpolated(), which fails.
+ if (member.Type.IsConstructedGenericType
+ && member.Type.GetGenericTypeDefinition() is var genericTypeDefinition
+ && (genericTypeDefinition == typeof(IQueryable<>) || genericTypeDefinition == typeof(IOrderedQueryable<>))
+ && Evaluate(member) is IQueryable queryable)
+ {
+ return Visit(queryable.Expression);
+ }
+
+ if (IsGenerallyEvaluatable(member))
+ {
+ _state = State.CreateEvaluatable(typeof(MemberExpression), _state.ContainsCapturedVariable);
+ return member.Update(expression);
+ }
+
+ expression = ProcessEvaluatableRoot(expression, ref _state);
+ }
+
+ if (_state.ContainsEvaluatable && _calculatingPath)
+ {
+ _state = State.CreateContainsEvaluatable(
+ typeof(MemberExpression),
+ [_state.Path! with { PathFromParent = static e => E.Property(e, nameof(MemberExpression.Expression)) }]);
+ }
+
+ return member.Update(expression);
+ }
+
+ ///
+ /// 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 E VisitMethodCall(MethodCallExpression methodCall)
+ {
+ var method = methodCall.Method;
+
+ // Handle some special, well-known functions
+ // If this is a call to EF.Constant(), or EF.Parameter(), then examine the operand; it it's isn't evaluatable (i.e. contains a
+ // reference to a database table), throw immediately. Otherwise, evaluate the operand (either as a constant or as a parameter) and
+ // return that.
+ if (method.DeclaringType == typeof(EF))
+ {
+ switch (method.Name)
+ {
+ case nameof(EF.Constant):
+ {
+ if (_calculatingPath)
+ {
+ throw new InvalidOperationException("EF.Constant is not supported when using precompiled queries");
+ }
+
+ var argument = Visit(methodCall.Arguments[0], out var argumentState);
+
+ if (!argumentState.IsEvaluatable)
+ {
+ throw new InvalidOperationException(CoreStrings.EFConstantWithNonEvaluatableArgument);
+ }
+
+ argumentState = argumentState with
+ {
+ StateType = StateType.EvaluatableWithoutCapturedVariable, ForceConstantization = true
+ };
+ var evaluatedArgument = ProcessEvaluatableRoot(argument, ref argumentState);
+ _state = argumentState;
+ return evaluatedArgument;
+ }
+
+ case nameof(EF.Parameter):
+ {
+ var argument = Visit(methodCall.Arguments[0], out var argumentState);
+
+ if (!argumentState.IsEvaluatable)
+ {
+ throw new InvalidOperationException(CoreStrings.EFParameterWithNonEvaluatableArgument);
+ }
+
+ argumentState = argumentState with { StateType = StateType.EvaluatableWithCapturedVariable };
+ var evaluatedArgument = ProcessEvaluatableRoot(argument, ref argumentState);
+ _state = argumentState;
+ return evaluatedArgument;
+ }
+ }
+ }
+
+ // Regular/arbitrary method handling from here on
+
+ // First, visit the object and all arguments, saving states as well
+ var @object = Visit(methodCall.Object, out var singleState);
+ var state = singleState.StateType;
+ var arguments = Visit(methodCall.Arguments, ref state, out var argumentStates);
+
+ // The following identifies Select(), and its lambda parameters in a special list which allows us to evaluate them.
+ if (method.DeclaringType == typeof(Enumerable)
+ && method.Name == nameof(Enumerable.Select)
+ && argumentStates[0].IsEvaluatable
+ && methodCall.Arguments[1] is LambdaExpression lambda)
+ {
+ foreach (var parameter in lambda.Parameters)
+ {
+ _evaluatableParameters.Add(parameter);
+ }
+
+ // Revisit with the updated _evaluatableParameters.
+ state = singleState.StateType;
+ arguments = Visit(methodCall.Arguments, ref state, out argumentStates);
+ }
+
+ // We've visited everything and know all the states.
+ switch (state)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ when IsGenerallyEvaluatable(methodCall):
+ _state = State.CreateEvaluatable(typeof(MethodCallExpression), state is StateType.EvaluatableWithCapturedVariable);
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ List? children = null;
+
+ if (singleState.IsEvaluatable)
+ {
+ @object = ProcessEvaluatableRoot(@object, ref singleState);
+ }
+
+ if (singleState.ContainsEvaluatable && _calculatingPath)
+ {
+ children = [singleState.Path! with { PathFromParent = static e => E.Property(e, nameof(MethodCallExpression.Object)) }];
+ }
+
+ // To support [NotParameterized] and indexer method arguments - which force evaluation as constant - go over the parameters
+ // and modify the states as needed
+ ParameterInfo[]? parameterInfos = null;
+ for (var i = 0; i < methodCall.Arguments.Count; i++)
+ {
+ var argumentState = argumentStates[i];
+
+ if (argumentState.IsEvaluatable)
+ {
+ parameterInfos ??= methodCall.Method.GetParameters();
+ if (parameterInfos[i].GetCustomAttribute() is not null
+ || _model.IsIndexerMethod(methodCall.Method))
+ {
+ argumentStates[i] = argumentState with
+ {
+ StateType = StateType.EvaluatableWithoutCapturedVariable, ForceConstantization = true
+ };
+ }
+ }
+ }
+
+ arguments = EvaluateList(
+ ((IReadOnlyList?)arguments) ?? methodCall.Arguments,
+ argumentStates,
+ children,
+ static i => e =>
+ E.Call(
+ E.Property(e, nameof(MethodCallExpression.Arguments)),
+ ReadOnlyCollectionIndexerGetter,
+ arguments: [E.Constant(i)]));
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(MethodCallExpression), children);
+ break;
+ }
+
+ return methodCall.Update(@object, ((IReadOnlyList?)arguments) ?? methodCall.Arguments);
+ }
+
+ ///
+ /// 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 E VisitNewArray(NewArrayExpression newArray)
+ {
+ StateType state = default;
+ var expressions = Visit(newArray.Expressions, ref state, out var expressionStates, poolExpressionStates: false);
+
+ switch (state)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ or StateType.Unknown // Zero expressions
+ when IsGenerallyEvaluatable(newArray):
+ _state = State.CreateEvaluatable(
+ typeof(NewExpression),
+ state is StateType.EvaluatableWithCapturedVariable,
+ // See note below on EvaluateChildren
+ notEvaluatableAsRootHandler: () => EvaluateChildren(newArray, expressions, expressionStates));
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ return EvaluateChildren(newArray, expressions, expressionStates);
+ }
+
+ return newArray.Update(((IReadOnlyList?)expressions) ?? newArray.Expressions);
+
+ // We don't parameterize NewArrayExpression when its an evaluatable root, since we want to allow translating new[] { x, y } to
+ // e.g. IN (x, y) rather than parameterizing the whole thing. But bubble up the evaluatable state so it may get evaluated at a
+ // higher level.
+ // To support that, when the NewArrayExpression is evaluatable, we include a nonEvaluatableAsRootHandler lambda in the returned
+ // state, which gets invoked up the stack, calling this method. This evaluates the NewArrayExpression's children, but not the
+ // NewArrayExpression.
+ NewArrayExpression EvaluateChildren(NewArrayExpression newArray, E[]? expressions, State[] expressionStates)
+ {
+ List? children = null;
+
+ expressions = EvaluateList(
+ ((IReadOnlyList?)expressions) ?? newArray.Expressions,
+ expressionStates,
+ children,
+ i => e => E.Call(
+ E.Property(e, nameof(NewArrayExpression.Expressions)),
+ ReadOnlyCollectionIndexerGetter,
+ arguments: [E.Constant(i)]));
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(NewArrayExpression), children);
+
+ return newArray.Update(((IReadOnlyList?)expressions) ?? newArray.Expressions);
+ }
+ }
+
+ ///
+ /// 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 E VisitNew(NewExpression @new)
+ {
+ StateType state = default;
+ var arguments = Visit(@new.Arguments, ref state, out var argumentStates, poolExpressionStates: false);
+
+ switch (state)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ or StateType.Unknown // Zero expressions
+ when IsGenerallyEvaluatable(@new):
+ _state = State.CreateEvaluatable(
+ typeof(NewExpression),
+ state is StateType.EvaluatableWithCapturedVariable,
+ // See note below on EvaluateChildren
+ notEvaluatableAsRootHandler: () => EvaluateChildren(@new, arguments, argumentStates));
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ return EvaluateChildren(@new, arguments, argumentStates);
+ }
+
+ return @new.Update(((IReadOnlyList?)arguments) ?? @new.Arguments);
+
+ // Although we allow NewExpression to be evaluated within larger tree fragments, we don't constantize them when they're the
+ // evaluatable root, since that would embed arbitrary user type instances in our shaper.
+ // To support that, when the NewExpression is evaluatable, we include a nonEvaluatableAsRootHandler lambda in the returned state,
+ // which gets invoked up the stack, calling this method. This evaluates the NewExpression's children, but not the NewExpression.
+ NewExpression EvaluateChildren(NewExpression @new, E[]? arguments, State[] argumentStates)
+ {
+ List? children = null;
+
+ arguments = EvaluateList(
+ ((IReadOnlyList?)arguments) ?? @new.Arguments,
+ argumentStates,
+ children,
+ i => e => E.Call(
+ E.Property(e, nameof(NewExpression.Arguments)),
+ ReadOnlyCollectionIndexerGetter,
+ arguments: [E.Constant(i)]));
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(NewExpression), children);
+
+ return @new.Update(((IReadOnlyList?)arguments) ?? @new.Arguments);
+ }
+ }
+
+ ///
+ /// 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 E 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;
+
+ return parameterExpression;
+ }
+
+ ///
+ /// 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 E VisitTypeBinary(TypeBinaryExpression typeBinary)
+ {
+ var expression = Visit(typeBinary.Expression, out _state);
+
+ if (_state.IsEvaluatable)
+ {
+ if (IsGenerallyEvaluatable(typeBinary))
+ {
+ _state = State.CreateEvaluatable(typeof(TypeBinaryExpression), _state.ContainsCapturedVariable);
+ return typeBinary.Update(expression);
+ }
+
+ expression = ProcessEvaluatableRoot(expression, ref _state);
+ }
+
+ if (_state.ContainsEvaluatable && _calculatingPath)
+ {
+ _state = State.CreateContainsEvaluatable(
+ typeof(TypeBinaryExpression),
+ [_state.Path! with { PathFromParent = static e => E.Property(e, nameof(TypeBinaryExpression.Expression)) }]);
+ }
+
+ return typeBinary.Update(expression);
+ }
+
+ ///
+ /// 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 E VisitMemberInit(MemberInitExpression memberInit)
+ {
+ var @new = (NewExpression)Visit(memberInit.NewExpression, out var newState);
+ var state = newState.StateType;
+ var bindings = Visit(memberInit.Bindings, VisitMemberBinding, ref state, out var bindingStates, poolExpressionStates: false);
+
+ switch (state)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ when IsGenerallyEvaluatable(memberInit):
+ _state = State.CreateEvaluatable(
+ typeof(InvocationExpression),
+ state is StateType.EvaluatableWithCapturedVariable,
+ notEvaluatableAsRootHandler: () => EvaluateChildren(memberInit, @new, newState, bindings, bindingStates));
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ return EvaluateChildren(memberInit, @new, newState, bindings, bindingStates);
+ }
+
+ return memberInit.Update(@new, ((IReadOnlyList?)bindings) ?? memberInit.Bindings);
+
+ // Although we allow MemberInitExpression to be evaluated within larger tree fragments, we don't constantize them when they're the
+ // evaluatable root, since that would embed arbitrary user type instances in our shaper.
+ // To support that, when the MemberInitExpression is evaluatable, we include a nonEvaluatableAsRootHandler lambda in the returned
+ // state, which gets invoked up the stack, calling this method. This evaluates the MemberInitExpression's children, but not the
+ // MemberInitExpression.
+ MemberInitExpression EvaluateChildren(
+ MemberInitExpression memberInit,
+ NewExpression @new,
+ State newState,
+ MemberBinding[]? bindings,
+ State[] bindingStates)
+ {
+ // If the NewExpression is evaluatable but one of the bindings isn't, we can't evaluate only the NewExpression
+ // (MemberInitExpression requires a NewExpression and doesn't accept ParameterException). However, we may still need to
+ // evaluate constructor arguments in the NewExpression.
+ if (newState.IsEvaluatable)
+ {
+ @new = (NewExpression)newState.NotEvaluatableAsRootHandler!();
+ }
+
+ List? children = null;
+
+ if (newState.ContainsEvaluatable && _calculatingPath)
+ {
+ children =
+ [
+ newState.Path! with { PathFromParent = static e => E.Property(e, nameof(MemberInitExpression.NewExpression)) }
+ ];
+ }
+
+ for (var i = 0; i < memberInit.Bindings.Count; i++)
+ {
+ var bindingState = bindingStates[i];
+
+ if (bindingState.IsEvaluatable)
+ {
+ bindings ??= memberInit.Bindings.ToArray();
+ var binding = (MemberAssignment)bindings[i];
+ bindings[i] = binding.Update(ProcessEvaluatableRoot(binding.Expression, ref bindingState));
+ bindingStates[i] = bindingState;
+ }
+
+ if (bindingState.ContainsEvaluatable && _calculatingPath)
+ {
+ children ??= [];
+ var index = i; // i gets mutated so make a copy for capturing below
+ children.Add(
+ bindingState.Path! with
+ {
+ PathFromParent = e =>
+ E.Property(
+ E.Convert(
+ E.Call(
+ E.Property(e, nameof(MemberInitExpression.Bindings)),
+ ReadOnlyMemberBindingCollectionIndexerGetter,
+ arguments: [E.Constant(index)]), typeof(MemberAssignment)),
+ MemberAssignmentExpressionProperty)
+ });
+ }
+ }
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(MemberInitExpression), children);
+
+ return memberInit.Update(@new, ((IReadOnlyList?)bindings) ?? memberInit.Bindings);
+ }
+ }
+
+ ///
+ /// 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 E VisitListInit(ListInitExpression listInit)
+ {
+ // First, visit the NewExpression and all initializers, saving states as well
+ var @new = (NewExpression)Visit(listInit.NewExpression, out var newState);
+ var state = newState.StateType;
+ var initializers = listInit.Initializers;
+ var initializerArgumentStates = new State[listInit.Initializers.Count][];
+
+ IReadOnlyList[]? visitedInitializersArguments = null;
+
+ for (var i = 0; i < initializers.Count; i++)
+ {
+ var initializer = initializers[i];
+
+ var visitedArguments = Visit(initializer.Arguments, ref state, out var argumentStates);
+ if (visitedArguments is not null)
+ {
+ if (visitedInitializersArguments is null)
+ {
+ visitedInitializersArguments = new IReadOnlyList[initializers.Count];
+ for (var j = 0; j < i; j++)
+ {
+ visitedInitializersArguments[j] = initializers[j].Arguments;
+ }
+ }
+ }
+
+ if (visitedInitializersArguments is not null)
+ {
+ visitedInitializersArguments[i] = (IReadOnlyList?)visitedArguments ?? initializer.Arguments;
+ }
+
+ initializerArgumentStates[i] = argumentStates;
+ }
+
+ // We've visited everything and have both our aggregate state, and the states of all initializer expressions.
+ switch (state)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ when IsGenerallyEvaluatable(listInit):
+ _state = State.CreateEvaluatable(typeof(ListInitExpression), state is StateType.EvaluatableWithCapturedVariable);
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ // If the NewExpression is evaluatable but one of the bindings isn't, we can't evaluate only the NewExpression
+ // (ListInitExpression requires a NewExpression and doesn't accept ParameterException). However, we may still need to
+ // evaluate constructor arguments in the NewExpression.
+ if (newState.IsEvaluatable)
+ {
+ @new = (NewExpression)newState.NotEvaluatableAsRootHandler!();
+ }
+
+ List? children = null;
+
+ if (newState.ContainsEvaluatable)
+ {
+ children =
+ [
+ newState.Path! with { PathFromParent = static e => E.Property(e, nameof(MethodCallExpression.Object)) }
+ ];
+ }
+
+ for (var i = 0; i < initializers.Count; i++)
+ {
+ var initializer = initializers[i];
+
+ var visitedArguments = EvaluateList(
+ visitedInitializersArguments is null
+ ? initializer.Arguments
+ : visitedInitializersArguments[i],
+ initializerArgumentStates[i],
+ children,
+ static i => e =>
+ E.Call(
+ E.Property(e, nameof(MethodCallExpression.Arguments)),
+ ReadOnlyCollectionIndexerGetter,
+ arguments: [E.Constant(i)]));
+
+ if (visitedArguments is not null && visitedInitializersArguments is null)
+ {
+ visitedInitializersArguments = new IReadOnlyList[initializers.Count];
+ for (var j = 0; j < i; j++)
+ {
+ visitedInitializersArguments[j] = initializers[j].Arguments;
+ }
+ }
+
+ if (visitedInitializersArguments is not null)
+ {
+ visitedInitializersArguments[i] = (IReadOnlyList?)visitedArguments ?? initializer.Arguments;
+ }
+ }
+
+ _state = children is null
+ ? State.NoEvaluatability
+ : State.CreateContainsEvaluatable(typeof(ListInitExpression), children);
+ break;
+ }
+
+ foreach (var argumentState in initializerArgumentStates)
+ {
+ StateArrayPool.Return(argumentState);
+ }
+
+ if (visitedInitializersArguments is null)
+ {
+ return listInit.Update(@new, listInit.Initializers);
+ }
+
+ var visitedInitializers = new ElementInit[initializers.Count];
+ for (var i = 0; i < visitedInitializersArguments.Length; i++)
+ {
+ visitedInitializers[i] = initializers[i].Update(visitedInitializersArguments[i]);
+ }
+
+ return listInit.Update(@new, visitedInitializers);
+ }
+
+ ///
+ /// 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 E VisitUnary(UnaryExpression unary)
+ {
+ var operand = Visit(unary.Operand, out var operandState);
+
+ switch (operandState.StateType)
+ {
+ case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable
+ or StateType.Unknown // Null operand
+ when IsGenerallyEvaluatable(unary):
+ _state = State.CreateEvaluatable(
+ typeof(UnaryExpression),
+ _state.ContainsCapturedVariable,
+ // See note below on EvaluateChildren
+ notEvaluatableAsRootHandler: () => EvaluateOperand(unary, operand, operandState));
+ break;
+
+ case StateType.NoEvaluatability:
+ _state = State.NoEvaluatability;
+ break;
+
+ default:
+ return EvaluateOperand(unary, operand, operandState);
+ }
+
+ return unary.Update(operand);
+
+ // 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).
+ // To support that, when the UnaryExpression is evaluatable, we include a nonEvaluatableAsRootHandler lambda in the returned state,
+ // which gets invoked up the stack, calling this method. This evaluates the UnaryExpression's operand, but not the UnaryExpression.
+ UnaryExpression EvaluateOperand(UnaryExpression unary, Expression operand, State operandState)
+ {
+ if (operandState.IsEvaluatable)
+ {
+ operand = ProcessEvaluatableRoot(operand, ref operandState);
+ }
+
+ if (_state.ContainsEvaluatable)
+ {
+ _state = _calculatingPath
+ ? State.CreateContainsEvaluatable(
+ typeof(UnaryExpression),
+ [_state.Path! with { PathFromParent = static e => E.Property(e, nameof(UnaryExpression.Operand)) }])
+ : State.NoEvaluatability;
+ }
+
+ return unary.Update(operand);
+ }
+ }
+
+ ///
+ /// 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 ElementInit VisitElementInit(ElementInit node)
+ => throw new UnreachableException(); // Handled in VisitListInit
+
+ ///
+ /// 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 MemberListBinding VisitMemberListBinding(MemberListBinding node)
+ => throw new InvalidOperationException(CoreStrings.MemberListBindingNotSupported);
+
+ ///
+ /// 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 MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
+ => throw new InvalidOperationException(CoreStrings.MemberMemberBindingNotSupported);
+
+ #endregion Visitation implementations
+
+ #region Unsupported node types
+
+ ///
+ /// 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 E VisitBlock(BlockExpression node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 CatchBlock VisitCatchBlock(CatchBlock node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 E VisitDebugInfo(DebugInfoExpression node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 E VisitDynamic(DynamicExpression node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 E VisitGoto(GotoExpression node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 LabelTarget VisitLabelTarget(LabelTarget? node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 E VisitLabel(LabelExpression node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 E VisitLoop(LoopExpression node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 E VisitRuntimeVariables(RuntimeVariablesExpression node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 E VisitSwitch(SwitchExpression node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 SwitchCase VisitSwitchCase(SwitchCase node)
+ => throw new NotSupportedException();
+
+ ///
+ /// 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 E VisitTry(TryExpression node)
+ => throw new NotSupportedException();
+
+ #endregion Unsupported node types
+
+ private static StateType CombineStateTypes(StateType stateType1, StateType stateType2)
+ => (stateType1, stateType2) switch
+ {
+ (StateType.Unknown, var s) => s,
+ (var s, StateType.Unknown) => s,
+
+ (StateType.NoEvaluatability, StateType.NoEvaluatability) => StateType.NoEvaluatability,
+
+ (StateType.EvaluatableWithoutCapturedVariable, StateType.EvaluatableWithoutCapturedVariable)
+ => StateType.EvaluatableWithoutCapturedVariable,
+
+ (StateType.EvaluatableWithCapturedVariable,
+ StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable)
+ or
+ (StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable,
+ StateType.EvaluatableWithCapturedVariable)
+ => StateType.EvaluatableWithCapturedVariable,
+
+ _ => StateType.ContainsEvaluatable
+ };
+
+ private E[]? Visit(
+ ReadOnlyCollection expressions,
+ ref StateType aggregateStateType,
+ out State[] expressionStates,
+ bool poolExpressionStates = true)
+ => Visit(expressions, Visit, ref aggregateStateType, out expressionStates, poolExpressionStates);
+
+ // This follows the ExpressionVisitor.Visit(ReadOnlyCollection) pattern.
+ private T[]? Visit(
+ ReadOnlyCollection expressions,
+ Func elementVisitor,
+ ref StateType aggregateStateType,
+ out State[] expressionStates,
+ bool poolExpressionStates = true)
+ {
+ if (expressions.Count == 0)
+ {
+ aggregateStateType = CombineStateTypes(aggregateStateType, StateType.EvaluatableWithoutCapturedVariable);
+ expressionStates = [];
+ return null;
+ }
+
+ expressionStates = poolExpressionStates ? StateArrayPool.Rent(expressions.Count) : new State[expressions.Count];
+
+ T[]? newExpressions = null;
+ for (var i = 0; i < expressions.Count; i++)
+ {
+ var oldExpression = expressions[i];
+ var newExpression = elementVisitor(oldExpression);
+ var expressionState = _state;
+
+ if (!ReferenceEquals(newExpression, oldExpression) && newExpressions is null)
+ {
+ newExpressions = new T[expressions.Count];
+ for (var j = 0; j < i; j++)
+ {
+ newExpressions[j] = expressions[j];
+ }
+ }
+
+ if (newExpressions is not null)
+ {
+ newExpressions[i] = newExpression;
+ }
+
+ expressionStates[i] = expressionState;
+
+ aggregateStateType = CombineStateTypes(aggregateStateType, expressionState.StateType);
+ }
+
+ return newExpressions;
+ }
+
+ private E[]? EvaluateList(
+ IReadOnlyList expressions,
+ State[] expressionStates,
+ List? children,
+ Func> pathFromParentGenerator)
+ {
+ // This allows us to make in-place changes in the expression array when the previous visitation pass made modifications (and so
+ // returned a mutable array). This removes an additional copy that would be needed.
+ var visitedExpressions = expressions as E[];
+
+ for (var i = 0; i < expressions.Count; i++)
+ {
+ var argumentState = expressionStates[i];
+ if (argumentState.IsEvaluatable)
+ {
+ if (visitedExpressions is null)
+ {
+ visitedExpressions = new E[expressions.Count];
+ for (var j = 0; j < i; j++)
+ {
+ visitedExpressions[j] = expressions[j];
+ }
+ }
+
+ visitedExpressions[i] = ProcessEvaluatableRoot(expressions[i], ref argumentState);
+ expressionStates[i] = argumentState;
+ }
+ else if (visitedExpressions is not null)
+ {
+ visitedExpressions[i] = expressions[i];
+ }
+
+ if (argumentState.ContainsEvaluatable && _calculatingPath)
+ {
+ children ??= [];
+ children.Add(argumentState.Path! with { PathFromParent = pathFromParentGenerator(i) });
+ }
+ }
+
+ return visitedExpressions;
+ }
+
+ [return: NotNullIfNotNull(nameof(evaluatableRoot))]
+ private E? ProcessEvaluatableRoot(E? evaluatableRoot, ref State state)
+ {
+ if (evaluatableRoot is null)
+ {
+ return null;
+ }
+
+ var evaluateAsParameter =
+ // In some cases, constantization is forced by the context ([NotParameterized], EF.Constant)
+ !state.ForceConstantization
+ && _parameterize
+ && (
+ // If the nodes contains a captured variable somewhere within it, we evaluate as a parameter.
+ state.ContainsCapturedVariable
+ // We don't evaluate as constant if we're not inside a lambda, i.e. in a top-level operator. This is to make sure that
+ // non-lambda arguments to e.g. Skip/Take are parameterized rather than evaluated as constant, since that would produce
+ // different SQLs for each value.
+ || !_inLambda
+ || (evaluatableRoot is MemberExpression member
+ && (member.Expression is not null || member.Member is not FieldInfo { IsInitOnly: true })));
+
+ // 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.
+ if (TryHandleNonEvaluatableAsRoot(evaluatableRoot, state, evaluateAsParameter, out var result))
+ {
+ return result;
+ }
+
+ var value = Evaluate(evaluatableRoot, out var parameterName, out var isContextAccessor);
+
+ switch (value)
+ {
+ // If the query contains a nested IQueryable, e.g. Where(b => context.Blogs.Count()...), the context.Blogs parts gets
+ // evaluated as a parameter; visit its expression tree instead.
+ case IQueryable { Expression: var innerExpression }:
+ return Visit(innerExpression);
+
+ case Expression innerExpression when !isContextAccessor:
+ return Visit(innerExpression);
+ }
+
+ if (isContextAccessor)
+ {
+ // Context accessors (query filters accessing the context) never get constantized
+ evaluateAsParameter = true;
+ }
+
+ if (evaluateAsParameter)
+ {
+ if (_parameterizedValues.TryGetValue(evaluatableRoot, out var cachedParameter))
+ {
+ // We're here when the same captured variable (or other fragment) is referenced more than once in the query; we want to
+ // use the same query parameter rather than sending it twice.
+ // Note that in path calculation (precompiled query), we don't have to do anything, as the path only needs to be returned
+ // once.
+ state = State.NoEvaluatability;
+ return cachedParameter;
+ }
+
+ if (_calculatingPath)
+ {
+ state = new()
+ {
+ StateType = StateType.ContainsEvaluatable,
+ Path = new()
+ {
+ ExpressionType = state.ExpressionType!,
+ ParameterName = parameterName,
+ Children = Array.Empty()
+ }
+ };
+
+ // We still maintain _parameterValues since later parameter names are generated based on already-populated names.
+ _parameterValues.AddParameter(parameterName, null);
+
+ return evaluatableRoot;
+ }
+
+ // Regular parameter extraction mode; client-evaluate the subtree and replace it with a query parameter.
+ state = State.NoEvaluatability;
+
+ _parameterValues.AddParameter(parameterName, value);
+
+ return _parameterizedValues[evaluatableRoot] = E.Parameter(evaluatableRoot.Type, parameterName);
+ }
+
+ // Evaluate as constant
+ state = State.NoEvaluatability;
+
+ // In precompilation mode, we don't care about constant evaluation since the expression tree itself isn't going to get used.
+ // We only care about generating code for extracting captured variables, so ignore.
+ if (_calculatingPath)
+ {
+ // TODO: EF.Constant is probably incompatible with precompilation, may need to throw (but not here, only from EF.Constant)
+ return evaluatableRoot;
+ }
+
+ var returnType = evaluatableRoot.Type;
+ var constantExpression = E.Constant(value, value?.GetType() ?? returnType);
+
+ return constantExpression.Type != returnType
+ ? E.Convert(constantExpression, returnType)
+ : constantExpression;
+
+ bool TryHandleNonEvaluatableAsRoot(E root, State state, bool asParameter, [NotNullWhen(true)] out Expression? result)
+ {
+ switch (root)
+ {
+ // We don't parameterize NewArrayExpression when its an evaluatable root, since we want to allow translating new[] { x, y }
+ // to e.g. IN (x, y) rather than parameterizing the whole thing. But bubble up the evaluatable state so it may get evaluated
+ // at a higher level.
+ case NewArrayExpression when asParameter:
+ // We don't constantize NewExpression/MemberInitExpression since that would embed arbitrary user type instances in our
+ // shaper.
+ case NewExpression or MemberInitExpression when !asParameter:
+ // 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):
+ result = state.NotEvaluatableAsRootHandler!();
+ return true;
+
+ default:
+ result = null;
+ return false;
+ }
+
+ bool PreserveConvertNode(E expression)
+ {
+ if (expression is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unaryExpression)
+ {
+ if (unaryExpression.Type == typeof(object)
+ || unaryExpression.Type == typeof(Enum)
+ || unaryExpression.Operand.Type.UnwrapNullableType().IsEnum)
+ {
+ return true;
+ }
+
+ var innerType = unaryExpression.Operand.Type.UnwrapNullableType();
+ if (unaryExpression.Type.UnwrapNullableType() == typeof(int)
+ && (innerType == typeof(byte)
+ || innerType == typeof(sbyte)
+ || innerType == typeof(char)
+ || innerType == typeof(short)
+ || innerType == typeof(ushort)))
+ {
+ return true;
+ }
+
+ return PreserveConvertNode(unaryExpression.Operand);
+ }
+
+ return false;
+ }
+ }
+ }
+
+ private object? Evaluate(E? expression)
+ => Evaluate(expression, out _, out _);
+
+ private object? Evaluate(E? expression, out string parameterName, out bool isContextAccessor)
+ {
+ var value = EvaluateCore(expression, out var tempParameterName, out isContextAccessor);
+ parameterName = tempParameterName ?? "p";
+
+ var compilerPrefixIndex = parameterName.LastIndexOf('>');
+ if (compilerPrefixIndex != -1)
+ {
+ parameterName = parameterName[(compilerPrefixIndex + 1)..];
+ }
+
+ parameterName = $"{QueryCompilationContext.QueryParameterPrefix}{parameterName}_{_parameterValues.ParameterValues.Count}";
+
+ return value;
+
+ object? EvaluateCore(E? expression, out string? parameterName, out bool isContextAccessor)
+ {
+ parameterName = null;
+ isContextAccessor = false;
+
+ if (expression == null)
+ {
+ return null;
+ }
+
+ if (_generateContextAccessors)
+ {
+ var visited = _contextParameterReplacer.Visit(expression);
+
+ if (visited != expression)
+ {
+ parameterName = QueryFilterPrefix
+ + (RemoveConvert(expression) is MemberExpression { Member.Name: var memberName } ? ("__" + memberName) : "__p");
+ isContextAccessor = true;
+
+ return E.Lambda(visited, _contextParameterReplacer.ContextParameterExpression);
+ }
+
+ static E RemoveConvert(E expression)
+ => expression is UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unaryExpression
+ ? RemoveConvert(unaryExpression.Operand)
+ : expression;
+ }
+
+ switch (expression)
+ {
+ case MemberExpression memberExpression:
+ var instanceValue = EvaluateCore(memberExpression.Expression, out parameterName, out isContextAccessor);
+ try
+ {
+ switch (memberExpression.Member)
+ {
+ case FieldInfo fieldInfo:
+ parameterName = parameterName is null ? fieldInfo.Name : $"{parameterName}_{fieldInfo.Name}";
+ return fieldInfo.GetValue(instanceValue);
+
+ case PropertyInfo propertyInfo:
+ parameterName = parameterName is null ? propertyInfo.Name : $"{parameterName}_{propertyInfo.Name}";
+ return propertyInfo.GetValue(instanceValue);
+ }
+ }
+ catch
+ {
+ // Try again when we compile the delegate
+ }
+
+ break;
+
+ case ConstantExpression constantExpression:
+ return constantExpression.Value;
+
+ case MethodCallExpression methodCallExpression:
+ parameterName = methodCallExpression.Method.Name;
+ break;
+
+ case UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unaryExpression
+ when (unaryExpression.Type.UnwrapNullableType() == unaryExpression.Operand.Type):
+ return EvaluateCore(unaryExpression.Operand, out parameterName, out isContextAccessor);
+ }
+
+ try
+ {
+ return E.Lambda>(
+ E.Convert(expression, typeof(object)))
+ .Compile(preferInterpretation: true)
+ .Invoke();
+ }
+ catch (Exception exception)
+ {
+ throw new InvalidOperationException(
+ _logger.ShouldLogSensitiveData()
+ ? CoreStrings.ExpressionParameterizationExceptionSensitive(expression)
+ : CoreStrings.ExpressionParameterizationException,
+ exception);
+ }
+ }
+ }
+
+ private bool IsGenerallyEvaluatable(E expression)
+ => _evaluatableExpressionFilter.IsEvaluatableExpression(expression, _model)
+ && (_parameterize
+ // Don't evaluate QueryableMethods if in compiled query
+ || !(expression is MethodCallExpression { Method: var method } && method.DeclaringType == typeof(Queryable)));
+
+ private enum StateType
+ {
+ ///
+ /// A temporary initial state, before any children have been examined.
+ ///
+ Unknown,
+
+ ///
+ /// Means that the current node is neither evaluatable, nor does it contains an evaluatable node.
+ ///
+ NoEvaluatability,
+
+ ///
+ /// Whether the current node is evaluatable, i.e. contains no references to server-side resources, and does not contain any
+ /// captured variables. Such nodes can be evaluated and the result integrated as constants in the tree.
+ ///
+ EvaluatableWithoutCapturedVariable,
+
+ ///
+ /// Whether the current node is evaluatable, i.e. contains no references to server-side resources, but contains captured
+ /// variables. Such nodes can be parameterized.
+ ///
+ EvaluatableWithCapturedVariable,
+
+ ///
+ /// Whether the current node contains (parameterizable) evaluatable nodes anywhere within its children.
+ ///
+ ContainsEvaluatable
+ }
+
+ private readonly record struct State
+ {
+ public static State CreateEvaluatable(
+ Type expressionType,
+ bool containsCapturedVariable,
+ Func? notEvaluatableAsRootHandler = null)
+ => new()
+ {
+ StateType = containsCapturedVariable
+ ? StateType.EvaluatableWithCapturedVariable
+ : StateType.EvaluatableWithoutCapturedVariable,
+ ExpressionType = expressionType,
+ NotEvaluatableAsRootHandler = notEvaluatableAsRootHandler
+ };
+
+ public static State CreateContainsEvaluatable(Type expressionType, IReadOnlyList children)
+ => new()
+ {
+ StateType = StateType.ContainsEvaluatable,
+ Path = new() { ExpressionType = expressionType, Children = children }
+ };
+
+ ///
+ /// Means that we're neither within an evaluatable subtree, nor on a node which contains one (and therefore needs to track the
+ /// path to it).
+ ///
+ public static readonly State NoEvaluatability = new() { StateType = StateType.NoEvaluatability };
+
+ public StateType StateType { get; init; }
+
+ public Type? ExpressionType { get; init; }
+
+ ///
+ /// A tree containing information on reaching all evaluatable nodes contained within this node.
+ ///
+ public PathNode? Path { get; init; }
+
+ public bool ForceConstantization { get; init; }
+
+ public Func? NotEvaluatableAsRootHandler { get; init; }
+
+ public bool IsEvaluatable
+ => StateType is StateType.EvaluatableWithoutCapturedVariable or StateType.EvaluatableWithCapturedVariable;
+
+ public bool ContainsCapturedVariable
+ => StateType is StateType.EvaluatableWithCapturedVariable;
+
+ public bool ContainsEvaluatable
+ => StateType is StateType.ContainsEvaluatable;
+
+ public override string ToString()
+ => StateType switch
+ {
+ StateType.NoEvaluatability => "No evaluatability",
+ StateType.EvaluatableWithoutCapturedVariable => "Evaluatable, no captured vars",
+ StateType.EvaluatableWithCapturedVariable => "Evaluatable, captured vars",
+ StateType.ContainsEvaluatable => "Contains evaluatable",
+
+ _ => throw new UnreachableException()
+ };
+ }
+
+ ///
+ /// 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.
+ ///
+ [EntityFrameworkInternal]
+ public record PathNode
+ {
+ ///
+ /// The type of the expression represented by this .
+ ///
+ public required Type ExpressionType { get; init; }
+
+ ///
+ /// Children of this node which contain parameterizable fragments.
+ ///
+ public required IReadOnlyList? Children { get; init; }
+
+ ///
+ /// A function that accepts the parent node, and returns an expression representing the path to this node from that parent
+ /// node. The returned expression can then be used to generate C# code that traverses the expression tree.
+ ///
+ public Func? PathFromParent { get; init; }
+
+ ///
+ /// For nodes representing parameterizable roots, contains the preferred parameter name, generated based on the expression
+ /// node type/contents.
+ ///
+ public string? ParameterName { get; init; }
+ }
+
+ private sealed class ContextParameterReplacer : ExpressionVisitor
+ {
+ private readonly Type _contextType;
+
+ public ContextParameterReplacer(Type contextType)
+ {
+ ContextParameterExpression = Expression.Parameter(contextType, "context");
+ _contextType = contextType;
+ }
+
+ public ParameterExpression ContextParameterExpression { get; }
+
+ [return: NotNullIfNotNull("expression")]
+ public override Expression? Visit(Expression? expression)
+ => expression?.Type != typeof(object)
+ && expression?.Type.IsAssignableFrom(_contextType) == true
+ ? ContextParameterExpression
+ : base.Visit(expression);
+ }
+
+ private class DummyParameterValues : IParameterValues
+ {
+ private readonly Dictionary _parameterValues = new();
+
+ public IReadOnlyDictionary ParameterValues
+ => _parameterValues;
+
+ public void AddParameter(string name, object? value)
+ => _parameterValues.Add(name, value);
+ }
+}
diff --git a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs
index a78e3682f79..07b5982255b 100644
--- a/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs
+++ b/src/EFCore/Query/Internal/NavigationExpandingExpressionVisitor.cs
@@ -49,7 +49,7 @@ private static readonly PropertyInfo QueryContextContextPropertyInfo
private readonly EntityReferenceOptionalMarkingExpressionVisitor _entityReferenceOptionalMarkingExpressionVisitor;
private readonly RemoveRedundantNavigationComparisonExpressionVisitor _removeRedundantNavigationComparisonExpressionVisitor;
private readonly HashSet _parameterNames = [];
- private readonly ParameterExtractingExpressionVisitor _parameterExtractingExpressionVisitor;
+ private readonly ExpressionTreeFuncletizer _funcletizer;
private readonly INavigationExpansionExtensibilityHelper _extensibilityHelper;
private readonly HashSet _nonCyclicAutoIncludeEntityTypes;
@@ -80,14 +80,12 @@ public NavigationExpandingExpressionVisitor(
_entityReferenceOptionalMarkingExpressionVisitor = new EntityReferenceOptionalMarkingExpressionVisitor();
_removeRedundantNavigationComparisonExpressionVisitor = new RemoveRedundantNavigationComparisonExpressionVisitor(
queryCompilationContext.Logger);
- _parameterExtractingExpressionVisitor = new ParameterExtractingExpressionVisitor(
+ _funcletizer = new ExpressionTreeFuncletizer(
+ _queryCompilationContext.Model,
evaluatableExpressionFilter,
- _parameters,
_queryCompilationContext.ContextType,
- _queryCompilationContext.Model,
- _queryCompilationContext.Logger,
- parameterize: false,
- generateContextAccessors: true);
+ generateContextAccessors: true,
+ _queryCompilationContext.Logger);
_nonCyclicAutoIncludeEntityTypes = !_queryCompilationContext.IgnoreAutoIncludes ? [] : null!;
}
@@ -210,8 +208,8 @@ protected override Expression VisitExtension(Expression extensionExpression)
// Apply defining query only when it is not custom query root
&& entityQueryRootExpression.GetType() == typeof(EntityQueryRootExpression))
{
- var processedDefiningQueryBody =
- _parameterExtractingExpressionVisitor.ExtractParameters(definingQuery.Body, clearEvaluatedValues: false);
+ var processedDefiningQueryBody = _funcletizer.ExtractParameters(
+ definingQuery.Body, _parameters, parameterize: false, clearParameterizedValues: false);
processedDefiningQueryBody = _queryTranslationPreprocessor.NormalizeQueryableMethod(processedDefiningQueryBody);
processedDefiningQueryBody = _nullCheckRemovingExpressionVisitor.Visit(processedDefiningQueryBody);
processedDefiningQueryBody =
@@ -1754,8 +1752,8 @@ private Expression ApplyQueryFilter(IEntityType entityType, NavigationExpansionE
if (!_parameterizedQueryFilterPredicateCache.TryGetValue(rootEntityType, out var filterPredicate))
{
filterPredicate = queryFilter;
- filterPredicate = (LambdaExpression)_parameterExtractingExpressionVisitor.ExtractParameters(
- filterPredicate, clearEvaluatedValues: false);
+ filterPredicate = (LambdaExpression)_funcletizer.ExtractParameters(
+ filterPredicate, _parameters, parameterize: false, clearParameterizedValues: false);
filterPredicate = (LambdaExpression)_queryTranslationPreprocessor.NormalizeQueryableMethod(filterPredicate);
// We need to do entity equality, but that requires a full method call on a query root to properly flow the
diff --git a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs b/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs
deleted file mode 100644
index d3057838ead..00000000000
--- a/src/EFCore/Query/Internal/ParameterExtractingExpressionVisitor.cs
+++ /dev/null
@@ -1,728 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-
-using System.Diagnostics.CodeAnalysis;
-using System.Runtime.CompilerServices;
-
-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 ParameterExtractingExpressionVisitor : ExpressionVisitor
-{
- private const string QueryFilterPrefix = "ef_filter";
-
- private readonly IParameterValues _parameterValues;
- private readonly IDiagnosticsLogger _logger;
- private readonly bool _parameterize;
- private readonly bool _generateContextAccessors;
- private readonly EvaluatableExpressionFindingExpressionVisitor _evaluatableExpressionFindingExpressionVisitor;
- private readonly ContextParameterReplacingExpressionVisitor _contextParameterReplacingExpressionVisitor;
-
- private readonly Dictionary _evaluatedValues = new(ExpressionEqualityComparer.Instance);
-
- private IDictionary _evaluatableExpressions;
- private IQueryProvider? _currentQueryProvider;
-
- ///
- /// 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 ParameterExtractingExpressionVisitor(
- IEvaluatableExpressionFilter evaluatableExpressionFilter,
- IParameterValues parameterValues,
- Type contextType,
- IModel model,
- IDiagnosticsLogger logger,
- bool parameterize,
- bool generateContextAccessors)
- {
- _evaluatableExpressionFindingExpressionVisitor
- = new EvaluatableExpressionFindingExpressionVisitor(evaluatableExpressionFilter, model, parameterize);
- _parameterValues = parameterValues;
- _logger = logger;
- _parameterize = parameterize;
- _generateContextAccessors = generateContextAccessors;
- // The entry method will take care of populating this field always. So accesses should be safe.
- _evaluatableExpressions = null!;
- _contextParameterReplacingExpressionVisitor = _generateContextAccessors
- ? new ContextParameterReplacingExpressionVisitor(contextType)
- : null!;
- }
-
- ///
- /// 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 virtual Expression ExtractParameters(Expression expression)
- => ExtractParameters(expression, clearEvaluatedValues: true);
-
- ///
- /// 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 virtual Expression ExtractParameters(Expression expression, bool clearEvaluatedValues)
- {
- var oldEvaluatableExpressions = _evaluatableExpressions;
- _evaluatableExpressions = _evaluatableExpressionFindingExpressionVisitor.Find(expression);
-
- try
- {
- return Visit(expression);
- }
- finally
- {
- _evaluatableExpressions = oldEvaluatableExpressions;
- if (clearEvaluatedValues)
- {
- _evaluatedValues.Clear();
- }
- }
- }
-
- ///
- /// 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.
- ///
- [return: NotNullIfNotNull("expression")]
- public override Expression? Visit(Expression? expression)
- {
- if (expression == null)
- {
- return null;
- }
-
- if (_evaluatableExpressions.TryGetValue(expression, out var generateParameter)
- && !PreserveInitializationConstant(expression, generateParameter)
- && !PreserveConvertNode(expression))
- {
- return Evaluate(expression, _parameterize && generateParameter);
- }
-
- return base.Visit(expression);
- }
-
- private static bool PreserveInitializationConstant(Expression expression, bool generateParameter)
- => !generateParameter && expression is NewExpression or MemberInitExpression;
-
- private bool PreserveConvertNode(Expression expression)
- {
- if (expression is UnaryExpression unaryExpression
- && (unaryExpression.NodeType == ExpressionType.Convert
- || unaryExpression.NodeType == ExpressionType.ConvertChecked))
- {
- if (unaryExpression.Type == typeof(object)
- || unaryExpression.Type == typeof(Enum)
- || unaryExpression.Operand.Type.UnwrapNullableType().IsEnum)
- {
- return true;
- }
-
- var innerType = unaryExpression.Operand.Type.UnwrapNullableType();
- if (unaryExpression.Type.UnwrapNullableType() == typeof(int)
- && (innerType == typeof(byte)
- || innerType == typeof(sbyte)
- || innerType == typeof(char)
- || innerType == typeof(short)
- || innerType == typeof(ushort)))
- {
- return true;
- }
-
- return PreserveConvertNode(unaryExpression.Operand);
- }
-
- return false;
- }
-
- ///
- /// 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 VisitConditional(ConditionalExpression conditionalExpression)
- {
- var newTestExpression = TryGetConstantValue(conditionalExpression.Test) ?? Visit(conditionalExpression.Test);
-
- if (newTestExpression is ConstantExpression { Value: bool constantTestValue })
- {
- return constantTestValue
- ? Visit(conditionalExpression.IfTrue)
- : Visit(conditionalExpression.IfFalse);
- }
-
- return conditionalExpression.Update(
- newTestExpression,
- Visit(conditionalExpression.IfTrue),
- Visit(conditionalExpression.IfFalse));
- }
-
- ///
- /// 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 VisitMethodCall(MethodCallExpression methodCallExpression)
- {
- // If this is a call to EF.Constant(), or EF.Parameter(), then examine the operand; it it's isn't evaluatable (i.e. contains a
- // reference to a database table), throw immediately. Otherwise, evaluate the operand (either as a constant or as a parameter) and
- // return that.
- if (methodCallExpression.Method.DeclaringType == typeof(EF))
- {
- switch (methodCallExpression.Method.Name)
- {
- case nameof(EF.Constant):
- {
- var operand = methodCallExpression.Arguments[0];
- if (!_evaluatableExpressions.TryGetValue(operand, out _))
- {
- throw new InvalidOperationException(CoreStrings.EFConstantWithNonEvaluableArgument);
- }
-
- return Evaluate(operand, generateParameter: false);
- }
-
- case nameof(EF.Parameter):
- {
- var operand = methodCallExpression.Arguments[0];
- if (!_evaluatableExpressions.TryGetValue(operand, out _))
- {
- throw new InvalidOperationException(CoreStrings.EFConstantWithNonEvaluableArgument);
- }
-
- return Evaluate(operand, generateParameter: true);
- }
- }
- }
-
- return base.VisitMethodCall(methodCallExpression);
- }
-
- ///
- /// 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 VisitBinary(BinaryExpression binaryExpression)
- {
- switch (binaryExpression.NodeType)
- {
- case ExpressionType.Coalesce:
- {
- var newLeftExpression = TryGetConstantValue(binaryExpression.Left) ?? Visit(binaryExpression.Left);
- if (newLeftExpression is ConstantExpression constantLeftExpression)
- {
- return constantLeftExpression.Value == null
- ? Visit(binaryExpression.Right)
- : newLeftExpression;
- }
-
- return binaryExpression.Update(
- newLeftExpression,
- binaryExpression.Conversion,
- Visit(binaryExpression.Right));
- }
-
- case ExpressionType.AndAlso:
- case ExpressionType.OrElse:
- {
- var newLeftExpression = TryGetConstantValue(binaryExpression.Left) ?? Visit(binaryExpression.Left);
- if (ShortCircuitLogicalExpression(newLeftExpression, binaryExpression.NodeType))
- {
- return newLeftExpression;
- }
-
- var newRightExpression = TryGetConstantValue(binaryExpression.Right) ?? Visit(binaryExpression.Right);
- return ShortCircuitLogicalExpression(newRightExpression, binaryExpression.NodeType)
- ? newRightExpression
- : binaryExpression.Update(newLeftExpression, binaryExpression.Conversion, newRightExpression);
- }
-
- default:
- return base.VisitBinary(binaryExpression);
- }
- }
-
- private Expression? TryGetConstantValue(Expression expression)
- {
- if (_evaluatableExpressions.ContainsKey(expression))
- {
- var value = GetValue(expression, out _);
-
- if (value is bool)
- {
- return Expression.Constant(value, typeof(bool));
- }
- }
-
- return null;
- }
-
- private static bool ShortCircuitLogicalExpression(Expression expression, ExpressionType nodeType)
- => expression is ConstantExpression { Value: bool constantValue }
- && ((constantValue && nodeType == ExpressionType.OrElse)
- || (!constantValue && nodeType == ExpressionType.AndAlso));
-
- ///
- /// 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)
- {
- if (extensionExpression is QueryRootExpression queryRootExpression)
- {
- var queryProvider = queryRootExpression.QueryProvider;
- if (_currentQueryProvider == null)
- {
- _currentQueryProvider = queryProvider;
- }
- else if (!ReferenceEquals(queryProvider, _currentQueryProvider))
- {
- throw new InvalidOperationException(CoreStrings.ErrorInvalidQueryable);
- }
-
- // Visit after detaching query provider since custom query roots can have additional components
- extensionExpression = queryRootExpression.DetachQueryProvider();
- }
-
- return base.VisitExtension(extensionExpression);
- }
-
- private static Expression GenerateConstantExpression(object? value, Type returnType)
- {
- var constantExpression = Expression.Constant(value, value?.GetType() ?? returnType);
-
- return constantExpression.Type != returnType
- ? Expression.Convert(constantExpression, returnType)
- : constantExpression;
- }
-
- private Expression Evaluate(Expression expression, bool generateParameter)
- {
- object? parameterValue;
- string? parameterName;
- if (_evaluatedValues.TryGetValue(expression, out var cachedValue))
- {
- // The _generateContextAccessors condition allows us to reuse parameter expressions evaluated in query filters.
- // In principle, _generateContextAccessors is orthogonal to query filters, but in practice it is only used in the
- // nav expansion query filters (and defining query). If this changes in future, they would need to be decoupled.
- var existingExpression = generateParameter || _generateContextAccessors
- ? cachedValue.Parameter
- : cachedValue.Constant;
-
- if (existingExpression != null)
- {
- return existingExpression;
- }
-
- parameterValue = cachedValue.Value;
- parameterName = cachedValue.CandidateParameterName;
- }
- else
- {
- parameterValue = GetValue(expression, out parameterName);
- cachedValue = new EvaluatedValues { CandidateParameterName = parameterName, Value = parameterValue };
- _evaluatedValues[expression] = cachedValue;
- }
-
- if (parameterValue is IQueryable innerQueryable)
- {
- return ExtractParameters(innerQueryable.Expression, clearEvaluatedValues: false);
- }
-
- if (parameterName?.StartsWith(QueryFilterPrefix, StringComparison.Ordinal) != true)
- {
- if (parameterValue is Expression innerExpression)
- {
- return ExtractParameters(innerExpression, clearEvaluatedValues: false);
- }
-
- if (!generateParameter)
- {
- var constantValue = GenerateConstantExpression(parameterValue, expression.Type);
-
- cachedValue.Constant = constantValue;
-
- return constantValue;
- }
- }
-
- parameterName ??= "p";
-
- if (string.Equals(QueryFilterPrefix, parameterName, StringComparison.Ordinal))
- {
- parameterName = QueryFilterPrefix + "__p";
- }
-
- var compilerPrefixIndex
- = parameterName.LastIndexOf(">", StringComparison.Ordinal);
-
- if (compilerPrefixIndex != -1)
- {
- parameterName = parameterName[(compilerPrefixIndex + 1)..];
- }
-
- parameterName
- = QueryCompilationContext.QueryParameterPrefix
- + parameterName
- + "_"
- + _parameterValues.ParameterValues.Count;
-
- _parameterValues.AddParameter(parameterName, parameterValue);
-
- var parameter = Expression.Parameter(expression.Type, parameterName);
-
- cachedValue.Parameter = parameter;
-
- return parameter;
- }
-
- private sealed class ContextParameterReplacingExpressionVisitor : ExpressionVisitor
- {
- private readonly Type _contextType;
-
- public ContextParameterReplacingExpressionVisitor(Type contextType)
- {
- ContextParameterExpression = Expression.Parameter(contextType, "context");
- _contextType = contextType;
- }
-
- public ParameterExpression ContextParameterExpression { get; }
-
- [return: NotNullIfNotNull("expression")]
- public override Expression? Visit(Expression? expression)
- => expression?.Type != typeof(object)
- && expression?.Type.IsAssignableFrom(_contextType) == true
- ? ContextParameterExpression
- : base.Visit(expression);
- }
-
- private static Expression RemoveConvert(Expression expression)
- {
- if (expression is UnaryExpression unaryExpression
- && expression.NodeType is ExpressionType.Convert or ExpressionType.ConvertChecked)
- {
- return RemoveConvert(unaryExpression.Operand);
- }
-
- return expression;
- }
-
- private object? GetValue(Expression? expression, out string? parameterName)
- {
- parameterName = null;
-
- if (expression == null)
- {
- return null;
- }
-
- if (_generateContextAccessors)
- {
- var newExpression = _contextParameterReplacingExpressionVisitor.Visit(expression);
-
- if (newExpression != expression)
- {
- if (newExpression.Type is IQueryable)
- {
- return newExpression;
- }
-
- parameterName = QueryFilterPrefix
- + (RemoveConvert(expression) is MemberExpression memberExpression
- ? ("__" + memberExpression.Member.Name)
- : "");
-
- return Expression.Lambda(
- newExpression,
- _contextParameterReplacingExpressionVisitor.ContextParameterExpression);
- }
- }
-
- switch (expression)
- {
- case MemberExpression memberExpression:
- var instanceValue = GetValue(memberExpression.Expression, out parameterName);
- try
- {
- switch (memberExpression.Member)
- {
- case FieldInfo fieldInfo:
- parameterName = (parameterName != null ? parameterName + "_" : "") + fieldInfo.Name;
- return fieldInfo.GetValue(instanceValue);
-
- case PropertyInfo propertyInfo:
- parameterName = (parameterName != null ? parameterName + "_" : "") + propertyInfo.Name;
- return propertyInfo.GetValue(instanceValue);
- }
- }
- catch
- {
- // Try again when we compile the delegate
- }
-
- break;
-
- case ConstantExpression constantExpression:
- return constantExpression.Value;
-
- case MethodCallExpression methodCallExpression:
- parameterName = methodCallExpression.Method.Name;
- break;
-
- case UnaryExpression { NodeType: ExpressionType.Convert or ExpressionType.ConvertChecked } unaryExpression
- when (unaryExpression.Type.UnwrapNullableType() == unaryExpression.Operand.Type):
- return GetValue(unaryExpression.Operand, out parameterName);
- }
-
- try
- {
- return Expression.Lambda>(
- Expression.Convert(expression, typeof(object)))
- .Compile(preferInterpretation: true)
- .Invoke();
- }
- catch (Exception exception)
- {
- throw new InvalidOperationException(
- _logger.ShouldLogSensitiveData()
- ? CoreStrings.ExpressionParameterizationExceptionSensitive(expression)
- : CoreStrings.ExpressionParameterizationException,
- exception);
- }
- }
-
- private sealed class EvaluatableExpressionFindingExpressionVisitor : ExpressionVisitor
- {
- private readonly IEvaluatableExpressionFilter _evaluatableExpressionFilter;
- private readonly ISet _allowedParameters = new HashSet();
- private readonly IModel _model;
- private readonly bool _parameterize;
-
- private bool _evaluatable;
- private bool _containsClosure;
- private bool _inLambda;
- private IDictionary _evaluatableExpressions;
-
- public EvaluatableExpressionFindingExpressionVisitor(
- IEvaluatableExpressionFilter evaluatableExpressionFilter,
- IModel model,
- bool parameterize)
- {
- _evaluatableExpressionFilter = evaluatableExpressionFilter;
- _model = model;
- _parameterize = parameterize;
- // The entry method will take care of populating this field always. So accesses should be safe.
- _evaluatableExpressions = null!;
- }
-
- public IDictionary Find(Expression expression)
- {
- _evaluatable = true;
- _containsClosure = false;
- _inLambda = false;
- _evaluatableExpressions = new Dictionary();
- _allowedParameters.Clear();
-
- Visit(expression);
-
- return _evaluatableExpressions;
- }
-
- [return: NotNullIfNotNull("expression")]
- public override Expression? Visit(Expression? expression)
- {
- if (expression == null)
- {
- return base.Visit(expression);
- }
-
- var parentEvaluatable = _evaluatable;
- var parentContainsClosure = _containsClosure;
-
- _evaluatable = IsEvaluatableNodeType(expression, out var preferNoEvaluation)
- // Extension point to disable funcletization
- && _evaluatableExpressionFilter.IsEvaluatableExpression(expression, _model)
- // Don't evaluate QueryableMethods if in compiled query
- && (_parameterize || !IsQueryableMethod(expression));
- _containsClosure = false;
-
- base.Visit(expression);
-
- if (_evaluatable && !preferNoEvaluation)
- {
- // Force parameterization when not in lambda
- _evaluatableExpressions[expression] = _containsClosure || !_inLambda;
- }
-
- _evaluatable = parentEvaluatable && _evaluatable;
- _containsClosure = parentContainsClosure || _containsClosure;
-
- return expression;
- }
-
- protected override Expression VisitLambda(Expression lambdaExpression)
- {
- var oldInLambda = _inLambda;
- _inLambda = true;
-
- // Note: Don't skip visiting parameter here.
- // SelectMany does not use parameter in lambda but we should still block it from evaluating
- base.VisitLambda(lambdaExpression);
-
- _inLambda = oldInLambda;
- return lambdaExpression;
- }
-
- protected override Expression VisitMemberInit(MemberInitExpression memberInitExpression)
- {
- Visit(memberInitExpression.Bindings, VisitMemberBinding);
-
- // Cannot make parameter for NewExpression if Bindings cannot be evaluated
- // but we still need to visit inside of it.
- var bindingsEvaluatable = _evaluatable;
- Visit(memberInitExpression.NewExpression);
-
- if (!bindingsEvaluatable)
- {
- _evaluatableExpressions.Remove(memberInitExpression.NewExpression);
- }
-
- return memberInitExpression;
- }
-
- protected override Expression VisitListInit(ListInitExpression listInitExpression)
- {
- Visit(listInitExpression.Initializers, VisitElementInit);
-
- // Cannot make parameter for NewExpression if Initializers cannot be evaluated
- // but we still need to visit inside of it.
- var initializersEvaluatable = _evaluatable;
- Visit(listInitExpression.NewExpression);
-
- if (!initializersEvaluatable)
- {
- _evaluatableExpressions.Remove(listInitExpression.NewExpression);
- }
-
- return listInitExpression;
- }
-
- protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression)
- {
- Visit(methodCallExpression.Object);
- var parameterInfos = methodCallExpression.Method.GetParameters();
- for (var i = 0; i < methodCallExpression.Arguments.Count; i++)
- {
- if (i == 1
- && _evaluatableExpressions.ContainsKey(methodCallExpression.Arguments[0])
- && methodCallExpression.Method.DeclaringType == typeof(Enumerable)
- && methodCallExpression.Method.Name == nameof(Enumerable.Select)
- && methodCallExpression.Arguments[1] is LambdaExpression lambdaExpression)
- {
- // Allow evaluation Enumerable.Select operation
- foreach (var parameter in lambdaExpression.Parameters)
- {
- _allowedParameters.Add(parameter);
- }
- }
-
- Visit(methodCallExpression.Arguments[i]);
-
- if (_evaluatableExpressions.ContainsKey(methodCallExpression.Arguments[i])
- && (parameterInfos[i].GetCustomAttribute() != null
- || _model.IsIndexerMethod(methodCallExpression.Method)))
- {
- _evaluatableExpressions[methodCallExpression.Arguments[i]] = false;
- }
- }
-
- return methodCallExpression;
- }
-
- protected override Expression VisitMember(MemberExpression memberExpression)
- {
- _containsClosure = memberExpression.Expression != null
- || !(memberExpression.Member is FieldInfo { IsInitOnly: true });
- return base.VisitMember(memberExpression);
- }
-
- protected override Expression VisitParameter(ParameterExpression parameterExpression)
- {
- _evaluatable = _allowedParameters.Contains(parameterExpression);
-
- return base.VisitParameter(parameterExpression);
- }
-
- protected override Expression VisitConstant(ConstantExpression constantExpression)
- {
- _evaluatable = !(constantExpression.Value is IQueryable);
-
-#pragma warning disable RCS1096 // Use bitwise operation instead of calling 'HasFlag'.
- _containsClosure
- = (constantExpression.Type.Attributes.HasFlag(TypeAttributes.NestedPrivate)
- && Attribute.IsDefined(constantExpression.Type, typeof(CompilerGeneratedAttribute), inherit: true)) // Closure
- || constantExpression.Type == typeof(ValueBuffer); // Find method
-#pragma warning restore RCS1096 // Use bitwise operation instead of calling 'HasFlag'.
-
- return base.VisitConstant(constantExpression);
- }
-
- private static bool IsEvaluatableNodeType(Expression expression, out bool preferNoEvaluation)
- {
- switch (expression.NodeType)
- {
- case ExpressionType.NewArrayInit:
- preferNoEvaluation = true;
- return true;
-
- case ExpressionType.Extension:
- preferNoEvaluation = false;
- return expression.CanReduce && IsEvaluatableNodeType(expression.ReduceAndCheck(), out preferNoEvaluation);
-
- // Identify a call to EF.Constant(), and flag that as non-evaluable.
- // This is important to prevent a larger subtree containing EF.Constant from being evaluated, i.e. to make sure that
- // the EF.Function argument is present in the tree as its own, constant node.
- case ExpressionType.Call
- when expression is MethodCallExpression { Method: var method }
- && method.DeclaringType == typeof(EF)
- && method.Name is nameof(EF.Constant) or nameof(EF.Parameter):
- preferNoEvaluation = true;
- return false;
-
- default:
- preferNoEvaluation = false;
- return true;
- }
- }
-
- private static bool IsQueryableMethod(Expression expression)
- => expression is MethodCallExpression methodCallExpression
- && methodCallExpression.Method.DeclaringType == typeof(Queryable);
- }
-
- private sealed class EvaluatedValues
- {
- public string? CandidateParameterName { get; init; }
- public object? Value { get; init; }
- public Expression? Constant { get; set; }
- public Expression? Parameter { get; set; }
- }
-}
diff --git a/src/EFCore/Query/Internal/QueryCompiler.cs b/src/EFCore/Query/Internal/QueryCompiler.cs
index 441f0fd88fc..a0536aff1bb 100644
--- a/src/EFCore/Query/Internal/QueryCompiler.cs
+++ b/src/EFCore/Query/Internal/QueryCompiler.cs
@@ -1,6 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Runtime.CompilerServices;
+
namespace Microsoft.EntityFrameworkCore.Query.Internal;
///
@@ -54,33 +56,36 @@ public QueryCompiler(
/// doing so can result in application failures when updating to a new Entity Framework Core release.
///
public virtual TResult Execute(Expression query)
+ => ExecuteCore(query, async: false, CancellationToken.None);
+
+ ///
+ /// 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 virtual TResult ExecuteAsync(Expression query, CancellationToken cancellationToken = default)
+ => ExecuteCore(query, async: true, cancellationToken);
+
+ private TResult ExecuteCore(Expression query, bool async, CancellationToken cancellationToken)
{
var queryContext = _queryContextFactory.Create();
- query = ExtractParameters(query, queryContext, _logger);
+ queryContext.CancellationToken = cancellationToken;
+
+ var queryAfterExtraction = ExtractParameters(query, queryContext, _logger);
var compiledQuery
= _compiledQueryCache
.GetOrAddQuery(
- _compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: false),
- () => CompileQueryCore(_database, query, _model, false));
+ _compiledQueryCacheKeyGenerator.GenerateCacheKey(queryAfterExtraction, async),
+ () => RuntimeFeature.IsDynamicCodeSupported
+ ? CompileQueryCore(_database, queryAfterExtraction, _model, async)
+ : throw new InvalidOperationException("Query wasn't precompiled and dynamic code isn't supported (NativeAOT)"));
return compiledQuery(queryContext);
}
- ///
- /// 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 virtual Func CompileQueryCore(
- IDatabase database,
- Expression query,
- IModel model,
- bool async)
- => database.CompileQuery(query, async);
-
///
/// 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
@@ -89,9 +94,9 @@ public virtual Func CompileQueryCore(
///
public virtual Func CreateCompiledQuery(Expression query)
{
- query = ExtractParameters(query, _queryContextFactory.Create(), _logger, parameterize: false);
+ var queryAfterExtraction = ExtractParameters(query, _queryContextFactory.Create(), _logger, compiledQuery: true);
- return CompileQueryCore(_database, query, _model, false);
+ return CompileQueryCore(_database, queryAfterExtraction, _model, false);
}
///
@@ -100,21 +105,11 @@ public virtual Func CreateCompiledQuery(Expressi
/// 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 virtual TResult ExecuteAsync(Expression query, CancellationToken cancellationToken = default)
+ public virtual Func CreateCompiledAsyncQuery(Expression query)
{
- var queryContext = _queryContextFactory.Create();
-
- queryContext.CancellationToken = cancellationToken;
+ var queryAfterExtraction = ExtractParameters(query, _queryContextFactory.Create(), _logger, compiledQuery: true);
- query = ExtractParameters(query, queryContext, _logger);
-
- var compiledQuery
- = _compiledQueryCache
- .GetOrAddQuery(
- _compiledQueryCacheKeyGenerator.GenerateCacheKey(query, async: true),
- () => CompileQueryCore(_database, query, _model, true));
-
- return compiledQuery(queryContext);
+ return CompileQueryCore(_database, queryAfterExtraction, _model, true);
}
///
@@ -123,12 +118,12 @@ var compiledQuery
/// 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 virtual Func CreateCompiledAsyncQuery(Expression query)
- {
- query = ExtractParameters(query, _queryContextFactory.Create(), _logger, parameterize: false);
-
- return CompileQueryCore(_database, query, _model, true);
- }
+ public virtual Func CompileQueryCore(
+ IDatabase database,
+ Expression query,
+ IModel model,
+ bool async)
+ => database.CompileQuery(query, async);
///
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
@@ -140,18 +135,8 @@ public virtual Expression ExtractParameters(
Expression query,
IParameterValues parameterValues,
IDiagnosticsLogger logger,
- bool parameterize = true,
+ bool compiledQuery = false,
bool generateContextAccessors = false)
- {
- var visitor = new ParameterExtractingExpressionVisitor(
- _evaluatableExpressionFilter,
- parameterValues,
- _contextType,
- _model,
- logger,
- parameterize,
- generateContextAccessors);
-
- return visitor.ExtractParameters(query);
- }
+ => new ExpressionTreeFuncletizer(_model, _evaluatableExpressionFilter, _contextType, generateContextAccessors: false, logger)
+ .ExtractParameters(query, parameterValues, parameterize: !compiledQuery, clearParameterizedValues: true);
}
diff --git a/test/EFCore.Specification.Tests/Query/Ef6GroupByTestBase.cs b/test/EFCore.Specification.Tests/Query/Ef6GroupByTestBase.cs
index 74d60093205..304de28685e 100644
--- a/test/EFCore.Specification.Tests/Query/Ef6GroupByTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/Ef6GroupByTestBase.cs
@@ -77,7 +77,7 @@ public virtual Task GroupBy_is_optimized_when_projecting_conditional_expression_
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
- public virtual Task GroupBy_is_optimized_when_filerting_and_projecting_anonymous_type_with_group_key_and_function_aggregate(
+ public virtual Task GroupBy_is_optimized_when_filtering_and_projecting_anonymous_type_with_group_key_and_function_aggregate(
bool async)
=> AssertQuery(
async,
diff --git a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs
index ec3fb71e405..1eb9030b952 100644
--- a/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/NorthwindMiscellaneousQueryTestBase.cs
@@ -2855,6 +2855,16 @@ public virtual void Can_cast_CreateQuery_result_to_IQueryable_T_bug_1730()
products = (IQueryable)products.Provider.CreateQuery(products.Expression);
}
+ [ConditionalFact]
+ public virtual async Task IQueryable_captured_variable()
+ {
+ await using var context = CreateContext();
+
+ IQueryable nestedOrdersQuery = context.Orders;
+
+ _ = await context.Customers.CountAsync(c => nestedOrdersQuery.Count() == 2);
+ }
+
[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Select_Subquery_Single(bool async)
diff --git a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs
index 02583323e23..3364813cc05 100644
--- a/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs
+++ b/test/EFCore.Specification.Tests/Query/NorthwindWhereQueryTestBase.cs
@@ -1211,11 +1211,19 @@ await AssertQuery(
ss => ss.Set().Where(c => c.CustomerID == "ALFKI" && boolean),
assertEmpty: true);
+ await AssertQuery(
+ async,
+ ss => ss.Set().Where(c => c.CustomerID == "ALFKI" || boolean));
+
boolean = true;
await AssertQuery(
async,
ss => ss.Set().Where(c => c.CustomerID == "ALFKI" && boolean));
+
+ await AssertQuery(
+ async,
+ ss => ss.Set().Where(c => c.CustomerID == "ALFKI" || boolean));
}
[ConditionalTheory]
@@ -2391,7 +2399,7 @@ public virtual async Task EF_Constant_with_non_evaluatable_argument_throws(bool
async,
ss => ss.Set().Where(c => c.CustomerID == EF.Constant(c.CustomerID))));
- Assert.Equal(CoreStrings.EFConstantWithNonEvaluableArgument, exception.Message);
+ Assert.Equal(CoreStrings.EFConstantWithNonEvaluatableArgument, exception.Message);
}
[ConditionalTheory]
@@ -2438,7 +2446,7 @@ public virtual async Task EF_Parameter_with_non_evaluatable_argument_throws(bool
async,
ss => ss.Set().Where(c => c.CustomerID == EF.Parameter(c.CustomerID))));
- Assert.Equal(CoreStrings.EFConstantWithNonEvaluableArgument, exception.Message);
+ Assert.Equal(CoreStrings.EFParameterWithNonEvaluatableArgument, exception.Message);
}
private class EntityWithImplicitCast(int value)
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/Ef6GroupBySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/Ef6GroupBySqlServerTest.cs
index 9aa64119a51..6df6a492fc6 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/Ef6GroupBySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/Ef6GroupBySqlServerTest.cs
@@ -159,12 +159,10 @@ public override async Task GroupBy_is_optimized_when_projecting_conditional_expr
AssertSql(
"""
-@__p_0='False'
-
SELECT CASE
WHEN [a].[FirstName] IS NULL THEN N'is null'
ELSE N'not null'
-END AS [keyIsNull], @__p_0 AS [logicExpression]
+END AS [keyIsNull], CAST(0 AS bit) AS [logicExpression]
FROM [ArubaOwner] AS [a]
GROUP BY [a].[FirstName]
""");
@@ -180,10 +178,10 @@ GROUP BY [a].[FirstName]
// ) AS [Distinct1]";
}
- public override async Task GroupBy_is_optimized_when_filerting_and_projecting_anonymous_type_with_group_key_and_function_aggregate(
+ public override async Task GroupBy_is_optimized_when_filtering_and_projecting_anonymous_type_with_group_key_and_function_aggregate(
bool async)
{
- await base.GroupBy_is_optimized_when_filerting_and_projecting_anonymous_type_with_group_key_and_function_aggregate(async);
+ await base.GroupBy_is_optimized_when_filtering_and_projecting_anonymous_type_with_group_key_and_function_aggregate(async);
AssertSql(
"""
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
index 13d6165a6b7..beb1680481e 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/GearsOfWarQuerySqlServerTest.cs
@@ -10374,40 +10374,40 @@ public override async Task Nested_contains_with_enum(bool async)
AssertSql(
"""
-@__ranks_1='[1]' (Size = 4000)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
+@__ranks_0='[1]' (Size = 4000)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank]
FROM [Gears] AS [g]
WHERE CASE
WHEN [g].[Rank] IN (
SELECT [r].[value]
- FROM OPENJSON(@__ranks_1) WITH ([value] int '$') AS [r]
- ) THEN @__key_2
- ELSE @__key_2
+ FROM OPENJSON(@__ranks_0) WITH ([value] int '$') AS [r]
+ ) THEN @__key_1
+ ELSE @__key_1
END IN (
SELECT [k].[value]
- FROM OPENJSON(@__keys_0) WITH ([value] uniqueidentifier '$') AS [k]
+ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k]
)
""",
//
"""
-@__ammoTypes_1='[1]' (Size = 4000)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
+@__ammoTypes_0='[1]' (Size = 4000)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId]
FROM [Weapons] AS [w]
WHERE CASE
WHEN [w].[AmmunitionType] IN (
SELECT [a].[value]
- FROM OPENJSON(@__ammoTypes_1) WITH ([value] int '$') AS [a]
- ) THEN @__key_2
- ELSE @__key_2
+ FROM OPENJSON(@__ammoTypes_0) WITH ([value] int '$') AS [a]
+ ) THEN @__key_1
+ ELSE @__key_1
END IN (
SELECT [k].[value]
- FROM OPENJSON(@__keys_0) WITH ([value] uniqueidentifier '$') AS [k]
+ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k]
)
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs
index 2f44080e1c5..c9561c43dfb 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindDbFunctionsQuerySqlServerTest.cs
@@ -986,17 +986,17 @@ await AssertCount(
AssertSql(
"""
-@__dateTime_0='1919-12-12T10:20:15.0000000' (DbType = DateTime)
-@__dateTime_Month_2='12'
-@__dateTime_Day_3='12'
-@__dateTime_Hour_4='10'
-@__dateTime_Minute_5='20'
-@__dateTime_Second_6='15'
-@__dateTime_Millisecond_7='0'
+@__dateTime_7='1919-12-12T10:20:15.0000000' (DbType = DateTime)
+@__dateTime_Month_1='12'
+@__dateTime_Day_2='12'
+@__dateTime_Hour_3='10'
+@__dateTime_Minute_4='20'
+@__dateTime_Second_5='15'
+@__dateTime_Millisecond_6='0'
SELECT COUNT(*)
FROM [Orders] AS [o]
-WHERE @__dateTime_0 > DATETIMEFROMPARTS(DATEPART(year, GETDATE()), @__dateTime_Month_2, @__dateTime_Day_3, @__dateTime_Hour_4, @__dateTime_Minute_5, @__dateTime_Second_6, @__dateTime_Millisecond_7)
+WHERE @__dateTime_7 > DATETIMEFROMPARTS(DATEPART(year, GETDATE()), @__dateTime_Month_1, @__dateTime_Day_2, @__dateTime_Hour_3, @__dateTime_Minute_4, @__dateTime_Second_5, @__dateTime_Millisecond_6)
""");
}
@@ -1052,13 +1052,13 @@ await AssertCount(
AssertSql(
"""
-@__date_0='1919-12-12T00:00:00.0000000' (DbType = Date)
-@__date_Month_2='12'
-@__date_Day_3='12'
+@__date_3='1919-12-12T00:00:00.0000000' (DbType = Date)
+@__date_Month_1='12'
+@__date_Day_2='12'
SELECT COUNT(*)
FROM [Orders] AS [o]
-WHERE @__date_0 > DATEFROMPARTS(DATEPART(year, GETDATE()), @__date_Month_2, @__date_Day_3)
+WHERE @__date_3 > DATEFROMPARTS(DATEPART(year, GETDATE()), @__date_Month_1, @__date_Day_2)
""");
}
@@ -1120,17 +1120,17 @@ public virtual void DateTime2FromParts_compare_with_local_variable()
AssertSql(
"""
-@__dateTime_0='1919-12-12T10:20:15.0000000'
-@__dateTime_Month_2='12'
-@__dateTime_Day_3='12'
-@__dateTime_Hour_4='10'
-@__dateTime_Minute_5='20'
-@__dateTime_Second_6='15'
-@__fractions_7='9999999'
+@__dateTime_7='1919-12-12T10:20:15.0000000'
+@__dateTime_Month_1='12'
+@__dateTime_Day_2='12'
+@__dateTime_Hour_3='10'
+@__dateTime_Minute_4='20'
+@__dateTime_Second_5='15'
+@__fractions_6='9999999'
SELECT COUNT(*)
FROM [Orders] AS [o]
-WHERE @__dateTime_0 > DATETIME2FROMPARTS(DATEPART(year, GETDATE()), @__dateTime_Month_2, @__dateTime_Day_3, @__dateTime_Hour_4, @__dateTime_Minute_5, @__dateTime_Second_6, @__fractions_7, 7)
+WHERE @__dateTime_7 > DATETIME2FROMPARTS(DATEPART(year, GETDATE()), @__dateTime_Month_1, @__dateTime_Day_2, @__dateTime_Hour_3, @__dateTime_Minute_4, @__dateTime_Second_5, @__fractions_6, 7)
""");
}
}
@@ -1195,19 +1195,19 @@ public virtual void DateTimeOffsetFromParts_compare_with_local_variable()
AssertSql(
"""
-@__dateTimeOffset_0='1919-12-12T10:20:15.0000000+01:30'
-@__dateTimeOffset_Month_2='12'
-@__dateTimeOffset_Day_3='12'
-@__dateTimeOffset_Hour_4='10'
-@__dateTimeOffset_Minute_5='20'
-@__dateTimeOffset_Second_6='15'
-@__fractions_7='5'
-@__hourOffset_8='1'
-@__minuteOffset_9='30'
+@__dateTimeOffset_9='1919-12-12T10:20:15.0000000+01:30'
+@__dateTimeOffset_Month_1='12'
+@__dateTimeOffset_Day_2='12'
+@__dateTimeOffset_Hour_3='10'
+@__dateTimeOffset_Minute_4='20'
+@__dateTimeOffset_Second_5='15'
+@__fractions_6='5'
+@__hourOffset_7='1'
+@__minuteOffset_8='30'
SELECT COUNT(*)
FROM [Orders] AS [o]
-WHERE @__dateTimeOffset_0 > DATETIMEOFFSETFROMPARTS(DATEPART(year, GETDATE()), @__dateTimeOffset_Month_2, @__dateTimeOffset_Day_3, @__dateTimeOffset_Hour_4, @__dateTimeOffset_Minute_5, @__dateTimeOffset_Second_6, @__fractions_7, @__hourOffset_8, @__minuteOffset_9, 7)
+WHERE @__dateTimeOffset_9 > DATETIMEOFFSETFROMPARTS(DATEPART(year, GETDATE()), @__dateTimeOffset_Month_1, @__dateTimeOffset_Day_2, @__dateTimeOffset_Hour_3, @__dateTimeOffset_Minute_4, @__dateTimeOffset_Second_5, @__fractions_6, @__hourOffset_7, @__minuteOffset_8, 7)
""");
}
}
@@ -1265,15 +1265,15 @@ await AssertCount(
AssertSql(
"""
-@__dateTime_0='1919-12-12T23:20:00.0000000' (DbType = DateTime)
-@__dateTime_Month_2='12'
-@__dateTime_Day_3='12'
-@__dateTime_Hour_4='23'
-@__dateTime_Minute_5='20'
+@__dateTime_5='1919-12-12T23:20:00.0000000' (DbType = DateTime)
+@__dateTime_Month_1='12'
+@__dateTime_Day_2='12'
+@__dateTime_Hour_3='23'
+@__dateTime_Minute_4='20'
SELECT COUNT(*)
FROM [Orders] AS [o]
-WHERE @__dateTime_0 > SMALLDATETIMEFROMPARTS(DATEPART(year, GETDATE()), @__dateTime_Month_2, @__dateTime_Day_3, @__dateTime_Hour_4, @__dateTime_Minute_5)
+WHERE @__dateTime_5 > SMALLDATETIMEFROMPARTS(DATEPART(year, GETDATE()), @__dateTime_Month_1, @__dateTime_Day_2, @__dateTime_Hour_3, @__dateTime_Minute_4)
""");
}
@@ -1364,11 +1364,11 @@ public virtual void DataLength_compare_with_local_variable()
AssertSql(
"""
-@__lenght_0='100' (Nullable = true)
+@__lenght_1='100' (Nullable = true)
SELECT COUNT(*)
FROM [Orders] AS [o]
-WHERE @__lenght_0 < DATALENGTH([o].[OrderDate])
+WHERE @__lenght_1 < DATALENGTH([o].[OrderDate])
""");
}
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs
index f8d5bccc73d..b1476c07a6b 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindMiscellaneousQuerySqlServerTest.cs
@@ -1174,11 +1174,7 @@ public override async Task Ternary_should_not_evaluate_both_sides(bool async)
AssertSql(
"""
-@__p_0='none' (Size = 4000)
-@__p_1='none' (Size = 4000)
-@__p_2='none' (Size = 4000)
-
-SELECT [c].[CustomerID], @__p_0 AS [Data1], @__p_1 AS [Data2], @__p_2 AS [Data3]
+SELECT [c].[CustomerID], N'none' AS [Data1]
FROM [Customers] AS [c]
""");
}
@@ -2801,9 +2797,7 @@ public override async Task Null_Coalesce_Short_Circuit(bool async)
AssertSql(
"""
-@__p_0='False'
-
-SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region], @__p_0 AS [Test]
+SELECT [c0].[CustomerID], [c0].[Address], [c0].[City], [c0].[CompanyName], [c0].[ContactName], [c0].[ContactTitle], [c0].[Country], [c0].[Fax], [c0].[Phone], [c0].[PostalCode], [c0].[Region], CAST(0 AS bit) AS [Test]
FROM (
SELECT DISTINCT [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]
@@ -5626,11 +5620,11 @@ public override async Task Entity_equality_with_null_coalesce_client_side(bool a
AssertSql(
"""
-@__entity_equality_p_0_CustomerID='ALFKI' (Size = 5) (DbType = StringFixedLength)
+@__entity_equality_a_0_CustomerID='ALFKI' (Size = 5) (DbType = StringFixedLength)
SELECT [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]
-WHERE [c].[CustomerID] = @__entity_equality_p_0_CustomerID
+WHERE [c].[CustomerID] = @__entity_equality_a_0_CustomerID
""");
}
@@ -7131,6 +7125,20 @@ public override void Can_cast_CreateQuery_result_to_IQueryable_T_bug_1730()
AssertSql();
}
+ public override async Task IQueryable_captured_variable()
+ {
+ await base.IQueryable_captured_variable();
+
+ AssertSql(
+ """
+SELECT COUNT(*)
+FROM [Customers] AS [c]
+WHERE (
+ SELECT COUNT(*)
+ FROM [Orders] AS [o]) = 2
+""");
+ }
+
public override async Task Multiple_context_instances(bool async)
{
await base.Multiple_context_instances(async);
@@ -7407,14 +7415,14 @@ public override async Task Contains_over_concatenated_column_and_parameter(bool
AssertSql(
"""
-@__someVariable_1='SomeVariable' (Size = 4000)
-@__data_0='["ALFKISomeVariable","ANATRSomeVariable","ALFKIX"]' (Size = 4000)
+@__someVariable_0='SomeVariable' (Size = 4000)
+@__data_1='["ALFKISomeVariable","ANATRSomeVariable","ALFKIX"]' (Size = 4000)
SELECT [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]
-WHERE [c].[CustomerID] + @__someVariable_1 IN (
+WHERE [c].[CustomerID] + @__someVariable_0 IN (
SELECT [d].[value]
- FROM OPENJSON(@__data_0) WITH ([value] nvarchar(max) '$') AS [d]
+ FROM OPENJSON(@__data_1) WITH ([value] nvarchar(max) '$') AS [d]
)
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs
index 7efc8aa0923..86cccb978c6 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/NorthwindWhereQuerySqlServerTest.cs
@@ -3129,6 +3129,17 @@ FROM [Customers] AS [c]
SELECT [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]
WHERE [c].[CustomerID] = N'ALFKI'
+""",
+ //
+ """
+SELECT [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]
+WHERE [c].[CustomerID] = N'ALFKI'
+""",
+ //
+ """
+SELECT [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]
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs
index db080f82a63..b9970df7564 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs
@@ -503,27 +503,24 @@ public override async Task Preserve_includes_when_applying_skip_take_after_anony
AssertSql(
"""
-SELECT COUNT(*)
-FROM [OwnedPerson] AS [o]
-""",
- //
- """
-@__p_1='0'
-@__p_2='100'
+@__p_0='0'
+@__p_1='100'
-SELECT [o2].[Id], [o2].[Discriminator], [o2].[Name], [s].[ClientId], [s].[Id], [s].[OrderDate], [s].[OrderClientId], [s].[OrderId], [s].[Id0], [s].[Detail], [o2].[PersonAddress_AddressLine], [o2].[PersonAddress_PlaceType], [o2].[PersonAddress_ZipCode], [o2].[PersonAddress_Country_Name], [o2].[PersonAddress_Country_PlanetId], [o2].[BranchAddress_BranchName], [o2].[BranchAddress_PlaceType], [o2].[BranchAddress_Country_Name], [o2].[BranchAddress_Country_PlanetId], [o2].[LeafBAddress_LeafBType], [o2].[LeafBAddress_PlaceType], [o2].[LeafBAddress_Country_Name], [o2].[LeafBAddress_Country_PlanetId], [o2].[LeafAAddress_LeafType], [o2].[LeafAAddress_PlaceType], [o2].[LeafAAddress_Country_Name], [o2].[LeafAAddress_Country_PlanetId]
+SELECT [o3].[Id], [o3].[Discriminator], [o3].[Name], [s].[ClientId], [s].[Id], [s].[OrderDate], [s].[OrderClientId], [s].[OrderId], [s].[Id0], [s].[Detail], [o3].[PersonAddress_AddressLine], [o3].[PersonAddress_PlaceType], [o3].[PersonAddress_ZipCode], [o3].[PersonAddress_Country_Name], [o3].[PersonAddress_Country_PlanetId], [o3].[BranchAddress_BranchName], [o3].[BranchAddress_PlaceType], [o3].[BranchAddress_Country_Name], [o3].[BranchAddress_Country_PlanetId], [o3].[LeafBAddress_LeafBType], [o3].[LeafBAddress_PlaceType], [o3].[LeafBAddress_Country_Name], [o3].[LeafBAddress_Country_PlanetId], [o3].[LeafAAddress_LeafType], [o3].[LeafAAddress_PlaceType], [o3].[LeafAAddress_Country_Name], [o3].[LeafAAddress_Country_PlanetId], [o3].[c]
FROM (
- SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PersonAddress_AddressLine], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId]
+ SELECT [o].[Id], [o].[Discriminator], [o].[Name], [o].[PersonAddress_AddressLine], [o].[PersonAddress_PlaceType], [o].[PersonAddress_ZipCode], [o].[PersonAddress_Country_Name], [o].[PersonAddress_Country_PlanetId], [o].[BranchAddress_BranchName], [o].[BranchAddress_PlaceType], [o].[BranchAddress_Country_Name], [o].[BranchAddress_Country_PlanetId], [o].[LeafBAddress_LeafBType], [o].[LeafBAddress_PlaceType], [o].[LeafBAddress_Country_Name], [o].[LeafBAddress_Country_PlanetId], [o].[LeafAAddress_LeafType], [o].[LeafAAddress_PlaceType], [o].[LeafAAddress_Country_Name], [o].[LeafAAddress_Country_PlanetId], (
+ SELECT COUNT(*)
+ FROM [OwnedPerson] AS [o2]) AS [c]
FROM [OwnedPerson] AS [o]
ORDER BY [o].[Id]
- OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY
-) AS [o2]
+ OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY
+) AS [o3]
LEFT JOIN (
SELECT [o0].[ClientId], [o0].[Id], [o0].[OrderDate], [o1].[OrderClientId], [o1].[OrderId], [o1].[Id] AS [Id0], [o1].[Detail]
FROM [Order] AS [o0]
LEFT JOIN [OrderDetail] AS [o1] ON [o0].[ClientId] = [o1].[OrderClientId] AND [o0].[Id] = [o1].[OrderId]
-) AS [s] ON [o2].[Id] = [s].[ClientId]
-ORDER BY [o2].[Id], [s].[ClientId], [s].[Id], [s].[OrderClientId], [s].[OrderId]
+) AS [s] ON [o3].[Id] = [s].[ClientId]
+ORDER BY [o3].[Id], [s].[ClientId], [s].[Id], [s].[OrderClientId], [s].[OrderId]
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
index 5e204386f5d..e7fe2079c22 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/PrimitiveCollectionsQuerySqlServerTest.cs
@@ -1478,20 +1478,20 @@ public override async Task Nested_contains_with_Lists_and_no_inferred_type_mappi
AssertSql(
"""
-@__ints_1='[1,2,3]' (Size = 4000)
-@__strings_0='["one","two","three"]' (Size = 4000)
+@__ints_0='[1,2,3]' (Size = 4000)
+@__strings_1='["one","two","three"]' (Size = 4000)
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE CASE
WHEN [p].[Int] IN (
SELECT [i].[value]
- FROM OPENJSON(@__ints_1) WITH ([value] int '$') AS [i]
+ FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i]
) THEN N'one'
ELSE N'two'
END IN (
SELECT [s].[value]
- FROM OPENJSON(@__strings_0) WITH ([value] nvarchar(max) '$') AS [s]
+ FROM OPENJSON(@__strings_1) WITH ([value] nvarchar(max) '$') AS [s]
)
""");
}
@@ -1502,20 +1502,20 @@ public override async Task Nested_contains_with_arrays_and_no_inferred_type_mapp
AssertSql(
"""
-@__ints_1='[1,2,3]' (Size = 4000)
-@__strings_0='["one","two","three"]' (Size = 4000)
+@__ints_0='[1,2,3]' (Size = 4000)
+@__strings_1='["one","two","three"]' (Size = 4000)
SELECT [p].[Id], [p].[Bool], [p].[Bools], [p].[DateTime], [p].[DateTimes], [p].[Enum], [p].[Enums], [p].[Int], [p].[Ints], [p].[NullableInt], [p].[NullableInts], [p].[NullableString], [p].[NullableStrings], [p].[String], [p].[Strings]
FROM [PrimitiveCollectionsEntity] AS [p]
WHERE CASE
WHEN [p].[Int] IN (
SELECT [i].[value]
- FROM OPENJSON(@__ints_1) WITH ([value] int '$') AS [i]
+ FROM OPENJSON(@__ints_0) WITH ([value] int '$') AS [i]
) THEN N'one'
ELSE N'two'
END IN (
SELECT [s].[value]
- FROM OPENJSON(@__strings_0) WITH ([value] nvarchar(max) '$') AS [s]
+ FROM OPENJSON(@__strings_1) WITH ([value] nvarchar(max) '$') AS [s]
)
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs
index f31ad32c55e..eb1b12e52e1 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/QueryFilterFuncletizationSqlServerTest.cs
@@ -225,29 +225,29 @@ public override void DbContext_property_based_filter_does_not_short_circuit()
AssertSql(
"""
-@__ef_filter__p_0='False'
-@__ef_filter__IsModerated_1='True' (Nullable = true)
+@__ef_filter__p_1='False'
+@__ef_filter__IsModerated_0='True' (Nullable = true)
SELECT [s].[Id], [s].[IsDeleted], [s].[IsModerated]
FROM [ShortCircuitFilter] AS [s]
-WHERE [s].[IsDeleted] = CAST(0 AS bit) AND (@__ef_filter__p_0 = CAST(1 AS bit) OR @__ef_filter__IsModerated_1 = [s].[IsModerated])
+WHERE [s].[IsDeleted] = CAST(0 AS bit) AND (@__ef_filter__p_1 = CAST(1 AS bit) OR @__ef_filter__IsModerated_0 = [s].[IsModerated])
""",
//
"""
-@__ef_filter__p_0='False'
-@__ef_filter__IsModerated_1='False' (Nullable = true)
+@__ef_filter__p_1='False'
+@__ef_filter__IsModerated_0='False' (Nullable = true)
SELECT [s].[Id], [s].[IsDeleted], [s].[IsModerated]
FROM [ShortCircuitFilter] AS [s]
-WHERE [s].[IsDeleted] = CAST(0 AS bit) AND (@__ef_filter__p_0 = CAST(1 AS bit) OR @__ef_filter__IsModerated_1 = [s].[IsModerated])
+WHERE [s].[IsDeleted] = CAST(0 AS bit) AND (@__ef_filter__p_1 = CAST(1 AS bit) OR @__ef_filter__IsModerated_0 = [s].[IsModerated])
""",
//
"""
-@__ef_filter__p_0='True'
+@__ef_filter__p_1='True'
SELECT [s].[Id], [s].[IsDeleted], [s].[IsModerated]
FROM [ShortCircuitFilter] AS [s]
-WHERE [s].[IsDeleted] = CAST(0 AS bit) AND @__ef_filter__p_0 = CAST(1 AS bit)
+WHERE [s].[IsDeleted] = CAST(0 AS bit) AND @__ef_filter__p_1 = CAST(1 AS bit)
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
index 143c3932380..0fc343b9afc 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPCGearsOfWarQuerySqlServerTest.cs
@@ -13692,9 +13692,9 @@ public override async Task Nested_contains_with_enum(bool async)
AssertSql(
"""
-@__ranks_1='[1]' (Size = 4000)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
+@__ranks_0='[1]' (Size = 4000)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
SELECT [u].[Nickname], [u].[SquadId], [u].[AssignedCityName], [u].[CityOfBirthName], [u].[FullName], [u].[HasSoulPatch], [u].[LeaderNickname], [u].[LeaderSquadId], [u].[Rank], [u].[Discriminator]
FROM (
@@ -13707,31 +13707,31 @@ FROM [Officers] AS [o]
WHERE CASE
WHEN [u].[Rank] IN (
SELECT [r].[value]
- FROM OPENJSON(@__ranks_1) WITH ([value] int '$') AS [r]
- ) THEN @__key_2
- ELSE @__key_2
+ FROM OPENJSON(@__ranks_0) WITH ([value] int '$') AS [r]
+ ) THEN @__key_1
+ ELSE @__key_1
END IN (
SELECT [k].[value]
- FROM OPENJSON(@__keys_0) WITH ([value] uniqueidentifier '$') AS [k]
+ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k]
)
""",
//
"""
-@__ammoTypes_1='[1]' (Size = 4000)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
+@__ammoTypes_0='[1]' (Size = 4000)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId]
FROM [Weapons] AS [w]
WHERE CASE
WHEN [w].[AmmunitionType] IN (
SELECT [a].[value]
- FROM OPENJSON(@__ammoTypes_1) WITH ([value] int '$') AS [a]
- ) THEN @__key_2
- ELSE @__key_2
+ FROM OPENJSON(@__ammoTypes_0) WITH ([value] int '$') AS [a]
+ ) THEN @__key_1
+ ELSE @__key_1
END IN (
SELECT [k].[value]
- FROM OPENJSON(@__keys_0) WITH ([value] uniqueidentifier '$') AS [k]
+ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k]
)
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
index 348554a72a3..9c15dc1582b 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TPTGearsOfWarQuerySqlServerTest.cs
@@ -11696,9 +11696,9 @@ public override async Task Nested_contains_with_enum(bool async)
AssertSql(
"""
-@__ranks_1='[1]' (Size = 4000)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
+@__ranks_0='[1]' (Size = 4000)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[Rank], CASE
WHEN [o].[Nickname] IS NOT NULL THEN N'Officer'
@@ -11708,31 +11708,31 @@ FROM [Gears] AS [g]
WHERE CASE
WHEN [g].[Rank] IN (
SELECT [r].[value]
- FROM OPENJSON(@__ranks_1) WITH ([value] int '$') AS [r]
- ) THEN @__key_2
- ELSE @__key_2
+ FROM OPENJSON(@__ranks_0) WITH ([value] int '$') AS [r]
+ ) THEN @__key_1
+ ELSE @__key_1
END IN (
SELECT [k].[value]
- FROM OPENJSON(@__keys_0) WITH ([value] uniqueidentifier '$') AS [k]
+ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k]
)
""",
//
"""
-@__ammoTypes_1='[1]' (Size = 4000)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
+@__ammoTypes_0='[1]' (Size = 4000)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[SynergyWithId]
FROM [Weapons] AS [w]
WHERE CASE
WHEN [w].[AmmunitionType] IN (
SELECT [a].[value]
- FROM OPENJSON(@__ammoTypes_1) WITH ([value] int '$') AS [a]
- ) THEN @__key_2
- ELSE @__key_2
+ FROM OPENJSON(@__ammoTypes_0) WITH ([value] int '$') AS [a]
+ ) THEN @__key_1
+ ELSE @__key_1
END IN (
SELECT [k].[value]
- FROM OPENJSON(@__keys_0) WITH ([value] uniqueidentifier '$') AS [k]
+ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k]
)
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
index 00d0d6aeb89..f80589b4848 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/TemporalGearsOfWarQuerySqlServerTest.cs
@@ -10265,40 +10265,40 @@ public override async Task Nested_contains_with_enum(bool async)
AssertSql(
"""
-@__ranks_1='[1]' (Size = 4000)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
+@__ranks_0='[1]' (Size = 4000)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
SELECT [g].[Nickname], [g].[SquadId], [g].[AssignedCityName], [g].[CityOfBirthName], [g].[Discriminator], [g].[FullName], [g].[HasSoulPatch], [g].[LeaderNickname], [g].[LeaderSquadId], [g].[PeriodEnd], [g].[PeriodStart], [g].[Rank]
FROM [Gears] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [g]
WHERE CASE
WHEN [g].[Rank] IN (
SELECT [r].[value]
- FROM OPENJSON(@__ranks_1) WITH ([value] int '$') AS [r]
- ) THEN @__key_2
- ELSE @__key_2
+ FROM OPENJSON(@__ranks_0) WITH ([value] int '$') AS [r]
+ ) THEN @__key_1
+ ELSE @__key_1
END IN (
SELECT [k].[value]
- FROM OPENJSON(@__keys_0) WITH ([value] uniqueidentifier '$') AS [k]
+ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k]
)
""",
//
"""
-@__ammoTypes_1='[1]' (Size = 4000)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
+@__ammoTypes_0='[1]' (Size = 4000)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0a47bcb7-a1cb-4345-8944-c58f82d6aac7","5f221fb9-66f4-442a-92c9-d97ed5989cc7"]' (Size = 4000)
SELECT [w].[Id], [w].[AmmunitionType], [w].[IsAutomatic], [w].[Name], [w].[OwnerFullName], [w].[PeriodEnd], [w].[PeriodStart], [w].[SynergyWithId]
FROM [Weapons] FOR SYSTEM_TIME AS OF '2010-01-01T00:00:00.0000000' AS [w]
WHERE CASE
WHEN [w].[AmmunitionType] IN (
SELECT [a].[value]
- FROM OPENJSON(@__ammoTypes_1) WITH ([value] int '$') AS [a]
- ) THEN @__key_2
- ELSE @__key_2
+ FROM OPENJSON(@__ammoTypes_0) WITH ([value] int '$') AS [a]
+ ) THEN @__key_1
+ ELSE @__key_1
END IN (
SELECT [k].[value]
- FROM OPENJSON(@__keys_0) WITH ([value] uniqueidentifier '$') AS [k]
+ FROM OPENJSON(@__keys_2) WITH ([value] uniqueidentifier '$') AS [k]
)
""");
}
diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs
index 01b6599f783..6086f0132d3 100644
--- a/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs
+++ b/test/EFCore.SqlServer.FunctionalTests/Query/UdfDbFunctionSqlServerTests.cs
@@ -206,12 +206,12 @@ public override void Scalar_Function_Let_Nested_Static()
AssertSql(
"""
-@__starCount_0='3'
-@__customerId_1='1'
+@__starCount_1='3'
+@__customerId_0='1'
-SELECT TOP(2) [c].[LastName], [dbo].[StarValue](@__starCount_0, [dbo].[CustomerOrderCount](@__customerId_1)) AS [OrderCount]
+SELECT TOP(2) [c].[LastName], [dbo].[StarValue](@__starCount_1, [dbo].[CustomerOrderCount](@__customerId_0)) AS [OrderCount]
FROM [Customers] AS [c]
-WHERE [c].[Id] = @__customerId_1
+WHERE [c].[Id] = @__customerId_0
""");
}
@@ -546,12 +546,12 @@ public override void Scalar_Function_Let_Nested_Instance()
AssertSql(
"""
-@__starCount_1='3'
-@__customerId_2='1'
+@__starCount_2='3'
+@__customerId_1='1'
-SELECT TOP(2) [c].[LastName], [dbo].[StarValue](@__starCount_1, [dbo].[CustomerOrderCount](@__customerId_2)) AS [OrderCount]
+SELECT TOP(2) [c].[LastName], [dbo].[StarValue](@__starCount_2, [dbo].[CustomerOrderCount](@__customerId_1)) AS [OrderCount]
FROM [Customers] AS [c]
-WHERE [c].[Id] = @__customerId_2
+WHERE [c].[Id] = @__customerId_1
""");
}