dotnet/efcore · error · NotSupportedException

Unnamed captured variable

Error message

Unnamed captured variable

What it means

When a ParameterExpression is referenced from outside the translated tree (a captured variable), VisitParameter emits it by its Name, which must match the caller's variable. If parameter.Name is null there is no valid identifier to emit, so it throws NotSupportedException. Compiler-generated captured parameters always have names; null names come from Expression.Parameter(type) with no name.

Source

Thrown at src/EFCore.Design/Query/Internal/LinqToCSharpSyntaxTranslator.cs:2166

    protected override Expression VisitParameter(ParameterExpression parameter)
    {
        // Note that the parameter in the lambda declaration is handled separately in VisitLambda
        if (_stack.Peek().Variables.TryGetValue(parameter, out var name)
            || _liftedState.Variables.TryGetValue(parameter, out name))
        {
            Result = IdentifierName(name);

            return parameter;
        }

        // This parameter is unknown to us - it's captured from outside the entire expression tree.
        // Simply return its name without worrying about uniquification, since the variable needs to correspond to the outside in any
        // case (it's the callers responsibility).
        _capturedVariables.Add(parameter);

        if (parameter.Name is null)
        {
            throw new NotSupportedException("Unnamed captured variable");
        }

        Result = IdentifierName(parameter.Name);
        return parameter;
    }

    /// <inheritdoc />
    protected override Expression VisitRuntimeVariables(RuntimeVariablesExpression node)
        => throw new NotSupportedException();

    /// <inheritdoc />
    protected override SwitchCase VisitSwitchCase(SwitchCase node)
        => throw new NotSupportedException("Translation happens as part of VisitSwitch");

    /// <inheritdoc />
    protected override Expression VisitSwitch(SwitchExpression switchNode)
    {
        Result = TranslateSwitch(switchNode, lowerableAssignmentVariable: null);

View on GitHub (pinned to dbf9771522)

Solutions

  1. Always name parameters: Expression.Parameter(type, "name").
  2. Avoid closure capture of unnamed parameters in precompiled queries.
  3. Inline the captured value as a constant instead of referencing an unnamed parameter.

Example fix

// before
var p = Expression.Parameter(typeof(int)); // Name is null -> throws when captured
// after
var p = Expression.Parameter(typeof(int), "threshold");
Defensive patterns

Strategy: validation

Validate before calling

public sealed class UnnamedParamDetector : ExpressionVisitor {
    public bool Found;
    protected override Expression VisitParameter(ParameterExpression p) { if (p.Name is null) Found = true; return p; }
}

Type guard

static bool IsNamed(ParameterExpression p) => !string.IsNullOrEmpty(p.Name);

Prevention

When it happens

Trigger: A lambda closes over a ParameterExpression created via Expression.Parameter(type) (no name argument) that the translator then treats as captured.

Common situations: Programmatic expression-tree building with unnamed parameters that get referenced across tree boundaries; dynamic query composition that stitches together subtrees.

Related errors


AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06). Data as JSON: /api/errors/9abce4c9d6edb7f2. Report an issue: GitHub.