Repository: dotnet/efcore

Issue:
`Select` projection with an (uncorrelated) subquery produces two separate SQL queries
A `Select` projection with an (uncorrelated) subquery seems to produce two separate SQL queries, which is not what I expect as a developer. For example:

```csharp
IQueryable<Employee> allEmployees = context
	.Employees
	.Where(x => x.Department == "Engineering");

Employee[] result = allEmployees
	.Where(x => x.Salary > 1000)
	.Select(e => new {
		Employee = e,
		TotalCount = allEmployees.Count()
	})
	.Skip(2).Take(10)
	.ToArray();
```

produces:

```sql
info: Executed DbCommand (16ms) [...]
      SELECT COUNT(*)
      FROM [Employees] AS [e]
      WHERE [e].[Department] = N'Engineering'

info: Executed DbCommand (26ms) [...]
      SELECT [e].[Id], [e].[Department], [e].[Manager], [e].[Name], [e].[Salary], @__Count_0 AS [TotalCount]
      FROM [Employees] AS [e]
      WHERE [e].[Department] = N'Engineering' AND [e].[Salary] > 1000.0
      ORDER BY (SELECT 1)
      OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY
```

Can I somehow force EF to combine these two queries into one (which is what I expect as a developer when I write the above query):

```sql
      SELECT [e].[Id], [e].[Department], [e].[Manager], [e].[Name], [e].[Salary], (
             SELECT COUNT(*)
             FROM [Employees] AS [e]
             WHERE [e].[Department] = N'Engineering') AS [TotalCount]
      FROM [Employees] AS [e]
      WHERE [e].[Department] = N'Engineering' AND [e].[Salary] > 1000.0
      ORDER BY (SELECT 1)
      OFFSET @__p_1 ROWS FETCH NEXT @__p_2 ROWS ONLY
```

In my real application the query is quite complex (with filters and DISTINCTs) and it is measurable faster to combine the two (I guess because SQL Server can reuse quite some intermediate query results). But there might also be other reasons, why developers want to combine the two queries, for example if there are hard consistency requirements.

Here is a [.NET Fiddle](https://dotnetfiddle.net/rVnLzy) with full repro.


Hints:

Patch:
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.&#xD;
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Comparers/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=composability/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=composable/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=constantization/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=constantize/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=constantized/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Constructible/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=DATEADD/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=datetimeoffset/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=doesnt/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=efcore/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=evaluatability/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=evaluatable/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Evaluatables/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=evaluatables/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=fallbacks/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=funcletization/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=Funcletizer/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Includable/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=initializers/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=keyless/@EntryIndexedValue">True</s:Boolean>
@@ -316,6 +323,7 @@ The .NET Foundation licenses this file to you under the MIT license.&#xD;
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=pluralizer/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Poolable/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=Postgre/@EntryIndexedValue">True</s:Boolean>
+	<s:Boolean x:Key="/Default/UserDictionary/Words/=precompilation/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=prunable/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=pushdown/@EntryIndexedValue">True</s:Boolean>
 	<s:Boolean x:Key="/Default/UserDictionary/Words/=queryables/@EntryIndexedValue">True</s:Boolean>
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
         /// <summary>
         ///     The EF.Constant&lt;T&gt; method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
         /// </summary>
-        public static string EFConstantWithNonEvaluableArgument
-            => GetString("EFConstantWithNonEvaluableArgument");
+        public static string EFConstantWithNonEvaluatableArgument
+            => GetString("EFConstantWithNonEvaluatableArgument");
 
         /// <summary>
         ///     The EF.Parameter&lt;T&gt; method may only be used within Entity Framework LINQ queries.
@@ -991,6 +991,12 @@ public static string EFConstantWithNonEvaluableArgument
         public static string EFParameterInvoked
             => GetString("EFParameterInvoked");
 
+        /// <summary>
+        ///     The EF.Parameter&lt;T&gt; method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.
+        /// </summary>
+        public static string EFParameterWithNonEvaluatableArgument
+            => GetString("EFParameterWithNonEvaluatableArgument");
+
         /// <summary>
         ///     Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model.
         /// </summary>
@@ -1766,6 +1772,18 @@ public static string ManyToManyOneNav(object? entityType, object? navigation)
                 GetString("ManyToManyOneNav", nameof(entityType), nameof(navigation)),
                 entityType, navigation);
 
+        /// <summary>
+        ///     EF Core does not support MemberListBinding: 'new Blog { Posts = { new Post(), new Post() } }'.
+        /// </summary>
+        public static string MemberListBindingNotSupported
+            => GetString("MemberListBindingNotSupported");
+
+        /// <summary>
+        ///     EF Core does not support MemberMemberBinding: 'new Blog { Data = { Name = "hello world" } }'.
+        /// </summary>
+        public static string MemberMemberBindingNotSupported
+            => GetString("MemberMemberBindingNotSupported");
+
         /// <summary>
         ///     The specified field '{field}' could not be found for property '{2_entityType}.{1_property}'.
         /// </summary>
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 @@
   <data name="EFConstantInvoked" xml:space="preserve">
     <value>The EF.Constant&lt;T&gt; method may only be used within Entity Framework LINQ queries.</value>
   </data>
-  <data name="EFConstantWithNonEvaluableArgument" xml:space="preserve">
+  <data name="EFConstantWithNonEvaluatableArgument" xml:space="preserve">
     <value>The EF.Constant&lt;T&gt; method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.</value>
   </data>
   <data name="EFParameterInvoked" xml:space="preserve">
     <value>The EF.Parameter&lt;T&gt; method may only be used within Entity Framework LINQ queries.</value>
   </data>
+  <data name="EFParameterWithNonEvaluatableArgument" xml:space="preserve">
+    <value>The EF.Parameter&lt;T&gt; method may only be used with an argument that can be evaluated client-side and does not contain any reference to database-side entities.</value>
+  </data>
   <data name="EmptyComplexType" xml:space="preserve">
     <value>Complex type '{complexType}' has no properties defines. Configure at least one property or don't include this type in the model.</value>
   </data>
@@ -1108,6 +1111,12 @@
   <data name="ManyToManyOneNav" xml:space="preserve">
     <value>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.</value>
   </data>
+  <data name="MemberListBindingNotSupported" xml:space="preserve">
+    <value>EF Core does not support MemberListBinding: 'new Blog { Posts = { new Post(), new Post() } }'.</value>
+  </data>
+  <data name="MemberMemberBindingNotSupported" xml:space="preserve">
+    <value>EF Core does not support MemberMemberBinding: 'new Blog { Data = { Name = "hello world" } }'.</value>
+  </data>
   <data name="MissingBackingField" xml:space="preserve">
     <value>The specified field '{field}' could not be found for property '{2_entityType}.{1_property}'.</value>
   </data>
diff --git a/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
new file mode 100644
index 00000000000..8469fccf688
--- /dev/null
+++ b/src/EFCore/Query/Internal/ExpressionTreeFuncletizer.cs
@@ -0,0 +1,2114 @@
+// 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 static System.Linq.Expressions.Expression;
+
+namespace Microsoft.EntityFrameworkCore.Query.Internal;
+
+/// <summary>
+///     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.
+/// </summary>
+/// <remarks>
+///     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.
+/// </remarks>
+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".
+
+    /// <summary>
+    ///     Indicates whether we're calculating the paths to all parameterized evaluatable roots (precompilation mode), or doing regular,
+    ///     non-precompiled parameter extraction.
+    /// </summary>
+    private bool _calculatingPath;
+
+    /// <summary>
+    ///     Indicates whether we should parameterize. Is false in in compiled query mode, as well as when we're handling query filters
+    ///     from NavigationExpandingExpressionVisitor.
+    /// </summary>
+    private bool _parameterize;
+
+    /// <summary>
+    ///     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).
+    /// </summary>
+    private bool _inLambda;
+
+    /// <summary>
+    ///     A provider-facing extensibility hook to allow preventing certain expression nodes from being evaluated (typically specific
+    ///     methods).
+    /// </summary>
+    private readonly IEvaluatableExpressionFilter _evaluatableExpressionFilter;
+
+    /// <summary>
+    ///     <see cref="ParameterExpression" /> 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 <see cref="ParameterExpression" /> instances for that case, to allow
+    ///     evaluatability.
+    /// </summary>
+    private readonly HashSet<ParameterExpression> _evaluatableParameters = new();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    private readonly Dictionary<Expression, ParameterExpression> _parameterizedValues = new(ExpressionEqualityComparer.Instance);
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    private bool _evaluateRoot;
+
+    /// <summary>
+    ///     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).
+    /// </summary>
+    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<DbLoggerCategory.Query> _logger;
+
+    private static readonly MethodInfo ReadOnlyCollectionIndexerGetter = typeof(ReadOnlyCollection<Expression>).GetProperties()
+        .Single(p => p.GetIndexParameters() is { Length: 1 } indexParameters && indexParameters[0].ParameterType == typeof(int)).GetMethod!;
+
+    private static readonly MethodInfo ReadOnlyMemberBindingCollectionIndexerGetter = typeof(ReadOnlyCollection<MemberBinding>)
+        .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<State> StateArrayPool = ArrayPool<State>.Shared;
+
+    private const string QueryFilterPrefix = "ef_filter";
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    public ExpressionTreeFuncletizer(
+        IModel model,
+        IEvaluatableExpressionFilter evaluatableExpressionFilter,
+        Type contextType,
+        bool generateContextAccessors,
+        IDiagnosticsLogger<DbLoggerCategory.Query> logger)
+    {
+        _model = model;
+        _evaluatableExpressionFilter = evaluatableExpressionFilter;
+        _generateContextAccessors = generateContextAccessors;
+        _contextParameterReplacer = _generateContextAccessors
+            ? new ContextParameterReplacer(contextType)
+            : null!;
+        _logger = logger;
+    }
+
+    /// <summary>
+    /// 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).
+    /// </summary>
+    /// <remarks>
+    ///     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.
+    /// </remarks>
+    public virtual Expression ExtractParameters(
+        Expression 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;
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    /// <returns>A tree representing the path to each evaluatable root node in the tree.</returns>
+    /// <remarks>
+    ///     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.
+    /// </remarks>
+    public virtual PathNode? CalculatePathsToEvaluatableRoots(Expression expression)
+    {
+        Reset();
+        _calculatingPath = true;
+        _parameterize = 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 Expression? Visit(Expression? expression, out State state)
+    {
+        _state = default;
+        var result = base.Visit(expression);
+        state = _state;
+        return result;
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    [return: NotNullIfNotNull("expression")]
+    public override Expression? Visit(Expression? 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
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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 Constant(b);
+                        default:
+                            return left;
+                    }
+
+                case ExpressionType.OrElse or ExpressionType.AndAlso when Evaluate(left) is bool leftBoolValue:
+                {
+                    left = 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 Constant(rightBoolValue);
+                }
+            }
+        }
+
+        // We're done with simplification/short-circuiting checks specific to BinaryExpression.
+        var state = CombineStateTypes(leftState.StateType, rightState.StateType);
+
+        switch (state)
+        {
+            case StateType.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+                if (IsGenerallyEvaluatable(binary))
+                {
+                    _state = State.CreateEvaluatable(typeof(BinaryExpression), state is StateType.EvaluatableWithCapturedVariable);
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+
+            case StateType.ContainsEvaluatable:
+                if (leftState.IsEvaluatable)
+                {
+                    left = ProcessEvaluatableRoot(left, ref leftState);
+                }
+
+                if (rightState.IsEvaluatable)
+                {
+                    right = ProcessEvaluatableRoot(right, ref rightState);
+                }
+
+                List<PathNode>? children = null;
+
+                if (_calculatingPath)
+                {
+                    if (leftState.ContainsEvaluatable)
+                    {
+                        children =
+                        [
+                            leftState.Path! with { PathFromParent = static e => Property(e, nameof(BinaryExpression.Left)) }
+                        ];
+                    }
+
+                    if (rightState.ContainsEvaluatable)
+                    {
+                        children ??= new();
+                        children.Add(rightState.Path! with { PathFromParent = static e => Property(e, nameof(BinaryExpression.Right)) });
+                    }
+                }
+
+                _state = children is null
+                    ? State.NoEvaluatability
+                    : State.CreateContainsEvaluatable(typeof(BinaryExpression), children);
+                break;
+
+            default:
+                throw new UnreachableException();
+        }
+
+        return binary.Update(left, binary.Conversion, right);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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)
+        {
+            case StateType.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            // 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 or StateType.Unknown:
+                if (IsGenerallyEvaluatable(conditional))
+                {
+                    _state = State.CreateEvaluatable(typeof(ConditionalExpression), state is StateType.EvaluatableWithCapturedVariable);
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+
+            case StateType.ContainsEvaluatable:
+                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<PathNode>? children = null;
+
+                if (_calculatingPath)
+                {
+                    if (testState.ContainsEvaluatable)
+                    {
+                        children ??= new();
+                        children.Add(
+                            testState.Path! with { PathFromParent = static e => Property(e, nameof(ConditionalExpression.Test)) });
+                    }
+
+                    if (ifTrueState.ContainsEvaluatable)
+                    {
+                        children ??= new();
+                        children.Add(
+                            ifTrueState.Path! with { PathFromParent = static e => Property(e, nameof(ConditionalExpression.IfTrue)) });
+                    }
+
+                    if (ifFalseState.ContainsEvaluatable)
+                    {
+                        children ??= new();
+                        children.Add(
+                            ifFalseState.Path! with { PathFromParent = static e => Property(e, nameof(ConditionalExpression.IfFalse)) });
+                    }
+                }
+
+                _state = children is null
+                    ? State.NoEvaluatability
+                    : State.CreateContainsEvaluatable(typeof(ConditionalExpression), children);
+                break;
+
+            default:
+                throw new UnreachableException();
+        }
+
+        return conditional.Update(test, ifTrue, ifFalse);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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;
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitDefault(DefaultExpression node)
+    {
+        _state = State.CreateEvaluatable(typeof(DefaultExpression), containsCapturedVariable: false);
+        return node;
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitExtension(Expression 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);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+                if (IsGenerallyEvaluatable(invocation))
+                {
+                    _state = State.CreateEvaluatable(typeof(InvocationExpression), state is StateType.EvaluatableWithCapturedVariable);
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+
+            case StateType.ContainsEvaluatable:
+                List<PathNode>? children = null;
+
+                if (expressionState.IsEvaluatable)
+                {
+                    expression = ProcessEvaluatableRoot(expression, ref expressionState);
+                }
+
+                if (expressionState.ContainsEvaluatable && _calculatingPath)
+                {
+                    children =
+                    [
+                        expressionState.Path! with { PathFromParent = static e => Property(e, nameof(InvocationExpression.Expression)) }
+                    ];
+                }
+
+                arguments = EvaluateList(
+                    ((IReadOnlyList<Expression>?)arguments) ?? invocation.Arguments,
+                    argumentStates,
+                    ref children,
+                    static i => e =>
+                        Call(
+                            Property(e, nameof(InvocationExpression.Arguments)),
+                            ReadOnlyCollectionIndexerGetter,
+                            arguments: [Constant(i)]));
+
+                _state = children is null
+                    ? State.NoEvaluatability
+                    : State.CreateContainsEvaluatable(typeof(InvocationExpression), children);
+                break;
+
+            default:
+                throw new UnreachableException();
+        }
+
+        StateArrayPool.Return(argumentStates);
+        return invocation.Update(expression, ((IReadOnlyList<Expression>?)arguments) ?? invocation.Arguments);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+                if (IsGenerallyEvaluatable(index))
+                {
+                    _state = State.CreateEvaluatable(typeof(IndexExpression), state is StateType.EvaluatableWithCapturedVariable);
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+
+            case StateType.ContainsEvaluatable:
+                List<PathNode>? children = null;
+
+                if (objectState.IsEvaluatable)
+                {
+                    @object = ProcessEvaluatableRoot(@object, ref objectState);
+                }
+
+                if (objectState.ContainsEvaluatable && _calculatingPath)
+                {
+                    children = [objectState.Path! with { PathFromParent = static e => Property(e, nameof(IndexExpression.Object)) }];
+                }
+
+                arguments = EvaluateList(
+                    ((IReadOnlyList<Expression>?)arguments) ?? index.Arguments,
+                    argumentStates,
+                    ref children,
+                    static i => e =>
+                        Call(
+                            Property(e, nameof(IndexExpression.Arguments)),
+                            ReadOnlyCollectionIndexerGetter,
+                            arguments: [Constant(i)]));
+
+                _state = children is null
+                    ? State.NoEvaluatability
+                    : State.CreateContainsEvaluatable(typeof(IndexExpression), children);
+                break;
+
+            default:
+                throw new UnreachableException();
+        }
+
+        StateArrayPool.Return(argumentStates);
+
+        // TODO: https://github.com/dotnet/runtime/issues/96626
+        return index.Update(@object!, ((IReadOnlyList<Expression>?)arguments) ?? index.Arguments);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitLambda<T>(Expression<T> 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 => Property(e, nameof(Expression<T>.Body)) }]);
+        }
+
+        _inLambda = oldInLambda;
+
+        return lambda;
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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 => Property(e, nameof(MemberExpression.Expression)) }]);
+        }
+
+        return member.Update(expression);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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 objectState);
+        var state = objectState.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 = objectState.StateType;
+            arguments = Visit(methodCall.Arguments, ref state, out argumentStates);
+        }
+
+        // We've visited everything and know all the states.
+        switch (state)
+        {
+            case StateType.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+                if (IsGenerallyEvaluatable(methodCall))
+                {
+                    _state = State.CreateEvaluatable(typeof(MethodCallExpression), state is StateType.EvaluatableWithCapturedVariable);
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+
+            case StateType.ContainsEvaluatable:
+                List<PathNode>? children = null;
+
+                if (objectState.IsEvaluatable)
+                {
+                    @object = ProcessEvaluatableRoot(@object, ref objectState);
+                }
+
+                if (objectState.ContainsEvaluatable && _calculatingPath)
+                {
+                    children = [objectState.Path! with { PathFromParent = static 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<NotParameterizedAttribute>() is not null
+                            || _model.IsIndexerMethod(methodCall.Method))
+                        {
+                            argumentStates[i] = argumentState with
+                            {
+                                StateType = StateType.EvaluatableWithoutCapturedVariable, ForceConstantization = true
+                            };
+                        }
+                    }
+                }
+
+                arguments = EvaluateList(
+                    ((IReadOnlyList<Expression>?)arguments) ?? methodCall.Arguments,
+                    argumentStates,
+                    ref children,
+                    static i => e =>
+                        Call(
+                            Property(e, nameof(MethodCallExpression.Arguments)),
+                            ReadOnlyCollectionIndexerGetter,
+                            arguments: [Constant(i)]));
+
+                _state = children is null
+                    ? State.NoEvaluatability
+                    : State.CreateContainsEvaluatable(typeof(MethodCallExpression), children);
+                break;
+
+            default:
+                throw new UnreachableException();
+        }
+
+        return methodCall.Update(@object, ((IReadOnlyList<Expression>?)arguments) ?? methodCall.Arguments);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitNewArray(NewArrayExpression newArray)
+    {
+        StateType state = default;
+        var expressions = Visit(newArray.Expressions, ref state, out var expressionStates, poolExpressionStates: false);
+
+        switch (state)
+        {
+            case StateType.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+            {
+                if (IsGenerallyEvaluatable(newArray))
+                {
+                    // Avoid allocating for the notEvaluatableAsRootHandler closure below unless we actually end up in the evaluatable case
+                    var (newArray2, expressions2, expressionStates2) = (newArray, expressions, expressionStates);
+                    _state = State.CreateEvaluatable(
+                        typeof(NewExpression),
+                        state is StateType.EvaluatableWithCapturedVariable,
+                        // See note below on EvaluateChildren
+                        notEvaluatableAsRootHandler: () => EvaluateChildren(newArray2, expressions2, expressionStates2));
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+            }
+
+            case StateType.ContainsEvaluatable:
+                return EvaluateChildren(newArray, expressions, expressionStates);
+
+            default:
+                throw new UnreachableException();
+        }
+
+        return newArray.Update(((IReadOnlyList<Expression>?)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, Expression[]? expressions, State[] expressionStates)
+        {
+            List<PathNode>? children = null;
+
+            expressions = EvaluateList(
+                ((IReadOnlyList<Expression>?)expressions) ?? newArray.Expressions,
+                expressionStates,
+                ref children,
+                i => e => Call(
+                    Property(e, nameof(NewArrayExpression.Expressions)),
+                    ReadOnlyCollectionIndexerGetter,
+                    arguments: [Constant(i)]));
+
+            _state = children is null
+                ? State.NoEvaluatability
+                : State.CreateContainsEvaluatable(typeof(NewArrayExpression), children);
+
+            return newArray.Update(((IReadOnlyList<Expression>?)expressions) ?? newArray.Expressions);
+        }
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitNew(NewExpression @new)
+    {
+        StateType state = default;
+        var arguments = Visit(@new.Arguments, ref state, out var argumentStates, poolExpressionStates: false);
+
+        switch (state)
+        {
+            case StateType.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+            {
+                if (IsGenerallyEvaluatable(@new))
+                {
+                    // Avoid allocating for the notEvaluatableAsRootHandler closure below unless we actually end up in the evaluatable case
+                    var (new2, arguments2, argumentStates2) = (@new, arguments, argumentStates);
+                    _state = State.CreateEvaluatable(
+                        typeof(NewExpression),
+                        state is StateType.EvaluatableWithCapturedVariable,
+                        // See note below on EvaluateChildren
+                        notEvaluatableAsRootHandler: () => EvaluateChildren(new2, arguments2, argumentStates2));
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+            }
+
+            case StateType.ContainsEvaluatable:
+                return EvaluateChildren(@new, arguments, argumentStates);
+
+            default:
+                throw new UnreachableException();
+        }
+
+        return @new.Update(((IReadOnlyList<Expression>?)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, Expression[]? arguments, State[] argumentStates)
+        {
+            List<PathNode>? children = null;
+
+            arguments = EvaluateList(
+                ((IReadOnlyList<Expression>?)arguments) ?? @new.Arguments,
+                argumentStates,
+                ref children,
+                i => e => Call(
+                    Property(e, nameof(NewExpression.Arguments)),
+                    ReadOnlyCollectionIndexerGetter,
+                    arguments: [Constant(i)]));
+
+            _state = children is null
+                ? State.NoEvaluatability
+                : State.CreateContainsEvaluatable(typeof(NewExpression), children);
+
+            return @new.Update(((IReadOnlyList<Expression>?)arguments) ?? @new.Arguments);
+        }
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitParameter(ParameterExpression parameterExpression)
+    {
+        // ParameterExpressions are lambda parameters, which we cannot evaluate.
+        // However, _allowedParameters is a mechanism to allow evaluating Select(), see VisitMethodCall.
+        _state = _evaluatableParameters.Contains(parameterExpression)
+            ? State.CreateEvaluatable(typeof(ParameterExpression), containsCapturedVariable: false)
+            : State.NoEvaluatability;
+
+        return parameterExpression;
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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 => Property(e, nameof(TypeBinaryExpression.Expression)) }]);
+        }
+
+        return typeBinary.Update(expression);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+            {
+                if (IsGenerallyEvaluatable(memberInit))
+                {
+                    // Avoid allocating for the notEvaluatableAsRootHandler closure below unless we actually end up in the evaluatable case
+                    var (memberInit2, new2, newState2, bindings2, bindingStates2) = (memberInit, @new, newState, bindings, bindingStates);
+                    _state = State.CreateEvaluatable(
+                        typeof(InvocationExpression),
+                        state is StateType.EvaluatableWithCapturedVariable,
+                        notEvaluatableAsRootHandler: () => EvaluateChildren(memberInit2, new2, newState2, bindings2, bindingStates2));
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+            }
+
+            case StateType.ContainsEvaluatable:
+                return EvaluateChildren(memberInit, @new, newState, bindings, bindingStates);
+
+            default:
+                throw new UnreachableException();
+        }
+
+        return memberInit.Update(@new, ((IReadOnlyList<MemberBinding>?)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<PathNode>? children = null;
+
+            if (newState.ContainsEvaluatable && _calculatingPath)
+            {
+                children =
+                [
+                    newState.Path! with { PathFromParent = static 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 =>
+                                Property(
+                                    Convert(
+                                        Call(
+                                            Property(e, nameof(MemberInitExpression.Bindings)),
+                                            ReadOnlyMemberBindingCollectionIndexerGetter,
+                                            arguments: [Constant(index)]), typeof(MemberAssignment)),
+                                    MemberAssignmentExpressionProperty)
+                        });
+                }
+            }
+
+            _state = children is null
+                ? State.NoEvaluatability
+                : State.CreateContainsEvaluatable(typeof(MemberInitExpression), children);
+
+            return memberInit.Update(@new, ((IReadOnlyList<MemberBinding>?)bindings) ?? memberInit.Bindings);
+        }
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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<Expression>[]? 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<Expression>[initializers.Count];
+                    for (var j = 0; j < i; j++)
+                    {
+                        visitedInitializersArguments[j] = initializers[j].Arguments;
+                    }
+                }
+            }
+
+            if (visitedInitializersArguments is not null)
+            {
+                visitedInitializersArguments[i] = (IReadOnlyList<Expression>?)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.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+                if (IsGenerallyEvaluatable(listInit))
+                {
+                    _state = State.CreateEvaluatable(typeof(ListInitExpression), state is StateType.EvaluatableWithCapturedVariable);
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+
+            case StateType.ContainsEvaluatable:
+                // 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<PathNode>? children = null;
+
+                if (newState.ContainsEvaluatable)
+                {
+                    children =
+                    [
+                        newState.Path! with { PathFromParent = static 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],
+                        ref children,
+                        static i => e =>
+                            Call(
+                                Property(e, nameof(MethodCallExpression.Arguments)),
+                                ReadOnlyCollectionIndexerGetter,
+                                arguments: [Constant(i)]));
+
+                    if (visitedArguments is not null && visitedInitializersArguments is null)
+                    {
+                        visitedInitializersArguments = new IReadOnlyList<Expression>[initializers.Count];
+                        for (var j = 0; j < i; j++)
+                        {
+                            visitedInitializersArguments[j] = initializers[j].Arguments;
+                        }
+                    }
+
+                    if (visitedInitializersArguments is not null)
+                    {
+                        visitedInitializersArguments[i] = (IReadOnlyList<Expression>?)visitedArguments ?? initializer.Arguments;
+                    }
+                }
+
+                _state = children is null
+                    ? State.NoEvaluatability
+                    : State.CreateContainsEvaluatable(typeof(ListInitExpression), children);
+                break;
+
+            default:
+                throw new UnreachableException();
+        }
+
+        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);
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitUnary(UnaryExpression unary)
+    {
+        var operand = Visit(unary.Operand, out var operandState);
+
+        switch (operandState.StateType)
+        {
+            case StateType.NoEvaluatability:
+                _state = State.NoEvaluatability;
+                break;
+
+            case StateType.EvaluatableWithCapturedVariable or StateType.EvaluatableWithoutCapturedVariable or StateType.Unknown:
+            {
+                if (IsGenerallyEvaluatable(unary))
+                {
+                    // Avoid allocating for the notEvaluatableAsRootHandler closure below unless we actually end up in the evaluatable case
+                    var (unary2, operand2, operandState2) = (unary, operand, operandState);
+                    _state = State.CreateEvaluatable(
+                        typeof(UnaryExpression),
+                        _state.ContainsCapturedVariable,
+                        // See note below on EvaluateChildren
+                        notEvaluatableAsRootHandler: () => EvaluateOperand(unary2, operand2, operandState2));
+                    break;
+                }
+
+                goto case StateType.ContainsEvaluatable;
+            }
+
+            case StateType.ContainsEvaluatable:
+                return EvaluateOperand(unary, operand, operandState);
+
+            default:
+                throw new UnreachableException();
+        }
+
+        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 => Property(e, nameof(UnaryExpression.Operand)) }])
+                    : State.NoEvaluatability;
+            }
+
+            return unary.Update(operand);
+        }
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override ElementInit VisitElementInit(ElementInit node)
+        => throw new UnreachableException(); // Handled in VisitListInit
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override MemberListBinding VisitMemberListBinding(MemberListBinding node)
+        => throw new InvalidOperationException(CoreStrings.MemberListBindingNotSupported);
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override MemberMemberBinding VisitMemberMemberBinding(MemberMemberBinding node)
+        => throw new InvalidOperationException(CoreStrings.MemberMemberBindingNotSupported);
+
+    #endregion Visitation implementations
+
+    #region Unsupported node types
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitBlock(BlockExpression node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override CatchBlock VisitCatchBlock(CatchBlock node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitDebugInfo(DebugInfoExpression node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitDynamic(DynamicExpression node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitGoto(GotoExpression node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override LabelTarget VisitLabelTarget(LabelTarget? node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitLabel(LabelExpression node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitLoop(LoopExpression node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression VisitSwitch(SwitchExpression node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override SwitchCase VisitSwitchCase(SwitchCase node)
+        => throw new NotSupportedException();
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    protected override Expression 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 Expression[]? Visit(
+        ReadOnlyCollection<Expression> expressions,
+        ref StateType aggregateStateType,
+        out State[] expressionStates,
+        bool poolExpressionStates = true)
+        => Visit(expressions, Visit, ref aggregateStateType, out expressionStates, poolExpressionStates);
+
+    // This follows the ExpressionVisitor.Visit(ReadOnlyCollection<T>) pattern.
+    private T[]? Visit<T>(
+        ReadOnlyCollection<T> expressions,
+        Func<T, T> elementVisitor,
+        ref StateType aggregateStateType,
+        out State[] expressionStates,
+        bool poolExpressionStates = true)
+    {
+        if (expressions.Count == 0)
+        {
+            aggregateStateType = CombineStateTypes(aggregateStateType, StateType.EvaluatableWithoutCapturedVariable);
+            expressionStates = [];
+            return null;
+        }
+
+        // In the normal case, the array for containing the expression states is pooled - we allocate it here and return it in the calling
+        // function at the end of processing.
+        // However, we have cases where a node is evaluatable, but not as an evaluatable root (e.g. NewExpression, NewArrayExpression - see
+        // e.g. VisitNewExpression for more details). In these cases we return Evaluatable state, but with a "NotEvaluatableAsRootHandler"
+        // that allows evaluating the node's children up the stack in case it's the root. The state array must continue living for that case
+        // even once VisitNew returns, as the callback may be called later and needs to access the states. But the callback may also never
+        // be called (if the NewExpression isn't a root, but rather part of a larger evaluatable fragment).
+        // So we lack an easy place to return the array to the pool, and refrain from pooling it for that case (at least for now).
+        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 Expression[]? EvaluateList(
+        IReadOnlyList<Expression> expressions,
+        State[] expressionStates,
+        ref List<PathNode>? children,
+        Func<int, Func<Expression, Expression>> 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 Expression[];
+
+        for (var i = 0; i < expressions.Count; i++)
+        {
+            var argumentState = expressionStates[i];
+            if (argumentState.IsEvaluatable)
+            {
+                if (visitedExpressions is null)
+                {
+                    visitedExpressions = new Expression[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 Expression? ProcessEvaluatableRoot(Expression? 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<PathNode>()
+                    }
+                };
+
+                // 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] = 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 = Constant(value, value?.GetType() ?? returnType);
+
+        return constantExpression.Type != returnType
+            ? Convert(constantExpression, returnType)
+            : constantExpression;
+
+        bool TryHandleNonEvaluatableAsRoot(Expression 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(Expression 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(Expression? expression)
+        => Evaluate(expression, out _, out _);
+
+    private object? Evaluate(Expression? 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(Expression? 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 Lambda(visited, _contextParameterReplacer.ContextParameterExpression);
+                }
+
+                static Expression RemoveConvert(Expression 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 Lambda<Func<object>>(
+                        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(Expression 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
+    {
+        /// <summary>
+        /// A temporary initial state, before any children have been examined.
+        /// </summary>
+        Unknown,
+
+        /// <summary>
+        /// Means that the current node is neither evaluatable, nor does it contains an evaluatable node.
+        /// </summary>
+        NoEvaluatability,
+
+        /// <summary>
+        ///     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.
+        /// </summary>
+        EvaluatableWithoutCapturedVariable,
+
+        /// <summary>
+        ///     Whether the current node is evaluatable, i.e. contains no references to server-side resources, but contains captured
+        ///     variables. Such nodes can be parameterized.
+        /// </summary>
+        EvaluatableWithCapturedVariable,
+
+        /// <summary>
+        /// Whether the current node contains (parameterizable) evaluatable nodes anywhere within its children.
+        /// </summary>
+        ContainsEvaluatable
+    }
+
+    private readonly record struct State
+    {
+        public static State CreateEvaluatable(
+            Type expressionType,
+            bool containsCapturedVariable,
+            Func<Expression>? notEvaluatableAsRootHandler = null)
+            => new()
+            {
+                StateType = containsCapturedVariable
+                    ? StateType.EvaluatableWithCapturedVariable
+                    : StateType.EvaluatableWithoutCapturedVariable,
+                ExpressionType = expressionType,
+                NotEvaluatableAsRootHandler = notEvaluatableAsRootHandler
+            };
+
+        public static State CreateContainsEvaluatable(Type expressionType, IReadOnlyList<PathNode> children)
+            => new()
+            {
+                StateType = StateType.ContainsEvaluatable,
+                Path = new() { ExpressionType = expressionType, Children = children }
+            };
+
+        /// <summary>
+        /// 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).
+        /// </summary>
+        public static readonly State NoEvaluatability = new() { StateType = StateType.NoEvaluatability };
+
+        public StateType StateType { get; init; }
+
+        public Type? ExpressionType { get; init; }
+
+        /// <summary>
+        /// A tree containing information on reaching all evaluatable nodes contained within this node.
+        /// </summary>
+        public PathNode? Path { get; init; }
+
+        public bool ForceConstantization { get; init; }
+
+        public Func<Expression>? NotEvaluatableAsRootHandler { get; init; }
+
+        public bool IsEvaluatable
+            => StateType is StateType.EvaluatableWithoutCapturedVariable or StateType.EvaluatableWithCapturedVariable or StateType.Unknown;
+
+        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()
+            };
+    }
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    [EntityFrameworkInternal]
+    public sealed record PathNode
+    {
+        /// <summary>
+        ///     The type of the expression represented by this <see cref="PathNode" />.
+        /// </summary>
+        public required Type ExpressionType { get; init; }
+
+        /// <summary>
+        ///     Children of this node which contain parameterizable fragments.
+        /// </summary>
+        public required IReadOnlyList<PathNode>? Children { get; init; }
+
+        /// <summary>
+        ///     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.
+        /// </summary>
+        public Func<Expression, Expression>? PathFromParent { get; init; }
+
+        /// <summary>
+        ///     For nodes representing parameterizable roots, contains the preferred parameter name, generated based on the expression
+        ///     node type/contents.
+        /// </summary>
+        public string? ParameterName { get; init; }
+    }
+
+    private sealed class ContextParameterReplacer(Type contextType) : ExpressionVisitor
+    {
+        public ParameterExpression ContextParameterExpression { get; } = Parameter(contextType, "context");
+
+        [return: NotNullIfNotNull("expression")]
+        public override Expression? Visit(Expression? expression)
+            => expression?.Type != typeof(object)
+                && expression?.Type.IsAssignableFrom(contextType) == true
+                    ? ContextParameterExpression
+                    : base.Visit(expression);
+    }
+
+    private sealed class DummyParameterValues : IParameterValues
+    {
+        private readonly Dictionary<string, object?> _parameterValues = new();
+
+        public IReadOnlyDictionary<string, object?> 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<string> _parameterNames = [];
-    private readonly ParameterExtractingExpressionVisitor _parameterExtractingExpressionVisitor;
+    private readonly ExpressionTreeFuncletizer _funcletizer;
     private readonly INavigationExpansionExtensibilityHelper _extensibilityHelper;
     private readonly HashSet<IEntityType> _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;
-
-/// <summary>
-///     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.
-/// </summary>
-public class ParameterExtractingExpressionVisitor : ExpressionVisitor
-{
-    private const string QueryFilterPrefix = "ef_filter";
-
-    private readonly IParameterValues _parameterValues;
-    private readonly IDiagnosticsLogger<DbLoggerCategory.Query> _logger;
-    private readonly bool _parameterize;
-    private readonly bool _generateContextAccessors;
-    private readonly EvaluatableExpressionFindingExpressionVisitor _evaluatableExpressionFindingExpressionVisitor;
-    private readonly ContextParameterReplacingExpressionVisitor _contextParameterReplacingExpressionVisitor;
-
-    private readonly Dictionary<Expression, EvaluatedValues> _evaluatedValues = new(ExpressionEqualityComparer.Instance);
-
-    private IDictionary<Expression, bool> _evaluatableExpressions;
-    private IQueryProvider? _currentQueryProvider;
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    public ParameterExtractingExpressionVisitor(
-        IEvaluatableExpressionFilter evaluatableExpressionFilter,
-        IParameterValues parameterValues,
-        Type contextType,
-        IModel model,
-        IDiagnosticsLogger<DbLoggerCategory.Query> 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!;
-    }
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    public virtual Expression ExtractParameters(Expression expression)
-        => ExtractParameters(expression, clearEvaluatedValues: true);
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    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();
-            }
-        }
-    }
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    [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;
-    }
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    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));
-    }
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    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);
-    }
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    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));
-
-    /// <summary>
-    ///     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.
-    /// </summary>
-    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<Func<object>>(
-                    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<ParameterExpression> _allowedParameters = new HashSet<ParameterExpression>();
-        private readonly IModel _model;
-        private readonly bool _parameterize;
-
-        private bool _evaluatable;
-        private bool _containsClosure;
-        private bool _inLambda;
-        private IDictionary<Expression, bool> _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<Expression, bool> Find(Expression expression)
-        {
-            _evaluatable = true;
-            _containsClosure = false;
-            _inLambda = false;
-            _evaluatableExpressions = new Dictionary<Expression, bool>();
-            _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<T>(Expression<T> 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<NotParameterizedAttribute>() != 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;
 
 /// <summary>
@@ -54,33 +56,36 @@ public QueryCompiler(
     ///     doing so can result in application failures when updating to a new Entity Framework Core release.
     /// </summary>
     public virtual TResult Execute<TResult>(Expression query)
+        => ExecuteCore<TResult>(query, async: false, CancellationToken.None);
+
+    /// <summary>
+    ///     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.
+    /// </summary>
+    public virtual TResult ExecuteAsync<TResult>(Expression query, CancellationToken cancellationToken = default)
+        => ExecuteCore<TResult>(query, async: true, cancellationToken);
+
+    private TResult ExecuteCore<TResult>(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<TResult>(_database, query, _model, false));
+                    _compiledQueryCacheKeyGenerator.GenerateCacheKey(queryAfterExtraction, async),
+                    () => RuntimeFeature.IsDynamicCodeSupported
+                        ? CompileQueryCore<TResult>(_database, queryAfterExtraction, _model, async)
+                        : throw new InvalidOperationException("Query wasn't precompiled and dynamic code isn't supported (NativeAOT)"));
 
         return compiledQuery(queryContext);
     }
 
-    /// <summary>
-    ///     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.
-    /// </summary>
-    public virtual Func<QueryContext, TResult> CompileQueryCore<TResult>(
-        IDatabase database,
-        Expression query,
-        IModel model,
-        bool async)
-        => database.CompileQuery<TResult>(query, async);
-
     /// <summary>
     ///     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<QueryContext, TResult> CompileQueryCore<TResult>(
     /// </summary>
     public virtual Func<QueryContext, TResult> CreateCompiledQuery<TResult>(Expression query)
     {
-        query = ExtractParameters(query, _queryContextFactory.Create(), _logger, parameterize: false);
+        var queryAfterExtraction = ExtractParameters(query, _queryContextFactory.Create(), _logger, compiledQuery: true);
 
-        return CompileQueryCore<TResult>(_database, query, _model, false);
+        return CompileQueryCore<TResult>(_database, queryAfterExtraction, _model, false);
     }
 
     /// <summary>
@@ -100,21 +105,11 @@ public virtual Func<QueryContext, TResult> CreateCompiledQuery<TResult>(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.
     /// </summary>
-    public virtual TResult ExecuteAsync<TResult>(Expression query, CancellationToken cancellationToken = default)
+    public virtual Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(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<TResult>(_database, query, _model, true));
-
-        return compiledQuery(queryContext);
+        return CompileQueryCore<TResult>(_database, queryAfterExtraction, _model, true);
     }
 
     /// <summary>
@@ -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.
     /// </summary>
-    public virtual Func<QueryContext, TResult> CreateCompiledAsyncQuery<TResult>(Expression query)
-    {
-        query = ExtractParameters(query, _queryContextFactory.Create(), _logger, parameterize: false);
-
-        return CompileQueryCore<TResult>(_database, query, _model, true);
-    }
+    public virtual Func<QueryContext, TResult> CompileQueryCore<TResult>(
+        IDatabase database,
+        Expression query,
+        IModel model,
+        bool async)
+        => database.CompileQuery<TResult>(query, async);
 
     /// <summary>
     ///     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<DbLoggerCategory.Query> 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);
 }


Test patch:
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
index 91f66c225f4..6fdef18e57c 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindMiscellaneousQueryCosmosTest.cs
@@ -3713,11 +3713,11 @@ public override async Task Entity_equality_with_null_coalesce_client_side(bool a
 
         AssertSql(
             """
-@__entity_equality_p_0_CustomerID='ALFKI'
+@__entity_equality_a_0_CustomerID='ALFKI'
 
 SELECT c
 FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = @__entity_equality_p_0_CustomerID))
+WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = @__entity_equality_a_0_CustomerID))
 """);
     }
 
@@ -4148,9 +4148,7 @@ public override async Task Ternary_should_not_evaluate_both_sides(bool async)
 
         AssertSql(
             """
-@__p_0='none'
-
-SELECT VALUE {"CustomerID" : c["CustomerID"], "Data1" : @__p_0}
+SELECT VALUE {"CustomerID" : c["CustomerID"], "Data1" : "none"}
 FROM root c
 WHERE (c["Discriminator"] = "Customer")
 """);
@@ -4527,6 +4525,9 @@ public override void Can_cast_CreateQuery_result_to_IQueryable_T_bug_1730()
         AssertSql();
     }
 
+    public override Task IQueryable_captured_variable()
+        => AssertTranslationFailed(() => base.IQueryable_captured_variable());
+
     public override async Task Multiple_context_instances(bool async)
     {
         await base.Multiple_context_instances(async);
@@ -4676,11 +4677,11 @@ public override async Task Contains_over_concatenated_column_and_parameter(bool
 
         AssertSql(
             """
-@__someVariable_1='SomeVariable'
+@__someVariable_0='SomeVariable'
 
 SELECT c
 FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] || @__someVariable_1) IN ("ALFKISomeVariable", "ANATRSomeVariable", "ALFKIX"))
+WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] || @__someVariable_0) IN ("ALFKISomeVariable", "ANATRSomeVariable", "ALFKIX"))
 """);
     }
 
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
index 6545ff7fae8..959bc977eae 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/NorthwindWhereQueryCosmosTest.cs
@@ -1685,7 +1685,19 @@ FROM root c
             """
 SELECT c
 FROM root c
-WHERE ((c["Discriminator"] = "Customer") AND ((c["CustomerID"] = "ALFKI") AND true))
+WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
+""",
+            //
+            """
+SELECT c
+FROM root c
+WHERE ((c["Discriminator"] = "Customer") AND (c["CustomerID"] = "ALFKI"))
+""",
+            //
+            """
+SELECT c
+FROM root c
+WHERE (c["Discriminator"] = "Customer")
 """);
     }
 
diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs
index e884932bdfd..7c3561f5e35 100644
--- a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs
+++ b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs
@@ -505,6 +505,9 @@ FROM root c
 """);
     }
 
+    public override Task Preserve_includes_when_applying_skip_take_after_anonymous_type_select(bool async) =>
+        AssertTranslationFailed(() => base.Preserve_includes_when_applying_skip_take_after_anonymous_type_select(async));
+
     private void AssertSql(params string[] expected)
         => Fixture.TestSqlLoggerFactory.AssertBaseline(expected);
 
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<Product>)products.Provider.CreateQuery(products.Expression);
     }
 
+    [ConditionalFact]
+    public virtual async Task IQueryable_captured_variable()
+    {
+        await using var context = CreateContext();
+
+        IQueryable<Order> 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<Customer>().Where(c => c.CustomerID == "ALFKI" && boolean),
             assertEmpty: true);
 
+        await AssertQuery(
+            async,
+            ss => ss.Set<Customer>().Where(c => c.CustomerID == "ALFKI" || boolean));
+
         boolean = true;
 
         await AssertQuery(
             async,
             ss => ss.Set<Customer>().Where(c => c.CustomerID == "ALFKI" && boolean));
+
+        await AssertQuery(
+            async,
+            ss => ss.Set<Customer>().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<Customer>().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<Customer>().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..b21830a08fa 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)
 """);
     }
 
@@ -1354,21 +1354,21 @@ FROM [Orders] AS [o]
     [ConditionalFact]
     public virtual void DataLength_compare_with_local_variable()
     {
-        int? lenght = 100;
+        int? length = 100;
         using (var context = CreateContext())
         {
             var count = context.Orders
-                .Count(c => lenght < EF.Functions.DataLength(c.OrderDate));
+                .Count(c => length < EF.Functions.DataLength(c.OrderDate));
 
             Assert.Equal(0, count);
 
             AssertSql(
                 """
-@__lenght_0='100' (Nullable = true)
+@__length_1='100' (Nullable = true)
 
 SELECT COUNT(*)
 FROM [Orders] AS [o]
-WHERE @__lenght_0 < DATALENGTH([o].[OrderDate])
+WHERE @__length_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
 """);
     }
 
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
index 251f4eafa97..c4c8ab796ee 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/GearsOfWarQuerySqliteTest.cs
@@ -9735,40 +9735,40 @@ public override async Task Nested_contains_with_enum(bool async)
 
         AssertSql(
             """
-@__ranks_1='[1]' (Size = 3)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0A47BCB7-A1CB-4345-8944-C58F82D6AAC7","5F221FB9-66F4-442A-92C9-D97ED5989CC7"]' (Size = 79)
+@__ranks_0='[1]' (Size = 3)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0A47BCB7-A1CB-4345-8944-C58F82D6AAC7","5F221FB9-66F4-442A-92C9-D97ED5989CC7"]' (Size = 79)
 
 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 json_each(@__ranks_1) AS "r"
-    ) THEN @__key_2
-    ELSE @__key_2
+        FROM json_each(@__ranks_0) AS "r"
+    ) THEN @__key_1
+    ELSE @__key_1
 END IN (
     SELECT "k"."value"
-    FROM json_each(@__keys_0) AS "k"
+    FROM json_each(@__keys_2) AS "k"
 )
 """,
             //
             """
-@__ammoTypes_1='[1]' (Size = 3)
-@__key_2='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
-@__keys_0='["0A47BCB7-A1CB-4345-8944-C58F82D6AAC7","5F221FB9-66F4-442A-92C9-D97ED5989CC7"]' (Size = 79)
+@__ammoTypes_0='[1]' (Size = 3)
+@__key_1='5f221fb9-66f4-442a-92c9-d97ed5989cc7'
+@__keys_2='["0A47BCB7-A1CB-4345-8944-C58F82D6AAC7","5F221FB9-66F4-442A-92C9-D97ED5989CC7"]' (Size = 79)
 
 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 json_each(@__ammoTypes_1) AS "a"
-    ) THEN @__key_2
-    ELSE @__key_2
+        FROM json_each(@__ammoTypes_0) AS "a"
+    ) THEN @__key_1
+    ELSE @__key_1
 END IN (
     SELECT "k"."value"
-    FROM json_each(@__keys_0) AS "k"
+    FROM json_each(@__keys_2) AS "k"
 )
 """);
     }
diff --git a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
index 4542ee4027f..6309949b328 100644
--- a/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
+++ b/test/EFCore.Sqlite.FunctionalTests/Query/PrimitiveCollectionsQuerySqliteTest.cs
@@ -1343,20 +1343,20 @@ public override async Task Nested_contains_with_Lists_and_no_inferred_type_mappi
 
         AssertSql(
             """
-@__ints_1='[1,2,3]' (Size = 7)
-@__strings_0='["one","two","three"]' (Size = 21)
+@__ints_0='[1,2,3]' (Size = 7)
+@__strings_1='["one","two","three"]' (Size = 21)
 
 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 json_each(@__ints_1) AS "i"
+        FROM json_each(@__ints_0) AS "i"
     ) THEN 'one'
     ELSE 'two'
 END IN (
     SELECT "s"."value"
-    FROM json_each(@__strings_0) AS "s"
+    FROM json_each(@__strings_1) AS "s"
 )
 """);
     }
@@ -1367,22 +1367,22 @@ public override async Task Nested_contains_with_arrays_and_no_inferred_type_mapp
 
         AssertSql(
             """
-    @__ints_1='[1,2,3]' (Size = 7)
-    @__strings_0='["one","two","three"]' (Size = 21)
+@__ints_0='[1,2,3]' (Size = 7)
+@__strings_1='["one","two","three"]' (Size = 21)
 
-    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 json_each(@__ints_1) AS "i"
-        ) THEN 'one'
-        ELSE 'two'
-    END IN (
-        SELECT "s"."value"
-        FROM json_each(@__strings_0) AS "s"
-    )
-    """);
+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 json_each(@__ints_0) AS "i"
+    ) THEN 'one'
+    ELSE 'two'
+END IN (
+    SELECT "s"."value"
+    FROM json_each(@__strings_1) AS "s"
+)
+""");
     }
 
     [ConditionalTheory]