dotnet/efcore · error · InvalidOperationException

JsonNodeMustBeHandledByProviderSpecificVisitor

JsonNodeMustBeHandledByProviderSpecificVisitor

Error message

This node should be handled by provider-specific SQL generator.

What it means

Thrown by the base relational QuerySqlGenerator.VisitJsonScalar because JsonScalarExpression (a scalar value extracted from a JSON column) can only be rendered using provider-specific JSON functions (e.g. SQL Server JSON_VALUE, PostgreSQL ->/->>). The base relational provider has no JSON syntax, so reaching this visitor means no provider-specific SQL generator handled the node.

Source

Thrown at src/EFCore.Relational/Query/QuerySqlGenerator.cs:1673

            for (var i = 1; i < rowValues.Count; i++)
            {
                if (i > 1)
                {
                    _relationalCommandBuilder.Append(", ");
                }

                Visit(valuesExpression.RowValues[i]);
            }
        }
    }

    /// <summary>
    ///     Generates SQL for a JSON scalar lookup expression.
    /// </summary>
    /// <param name="jsonScalarExpression">The <see cref="JsonScalarExpression" /> for which to generate SQL.</param>
    protected virtual Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression)
        => throw new InvalidOperationException(
            RelationalStrings.JsonNodeMustBeHandledByProviderSpecificVisitor);

    /// <summary>
    ///     Returns a bool value indicating if the inner SQL expression required to be put inside parenthesis when generating SQL for outer
    ///     SQL expression.
    /// </summary>
    /// <param name="outerExpression">The outer expression which provides context in which SQL is being generated.</param>
    /// <param name="innerExpression">The inner expression which may need to be put inside parenthesis.</param>
    /// <returns>A bool value indicating that parenthesis is required or not. </returns>
    protected virtual bool RequiresParentheses(SqlExpression outerExpression, SqlExpression innerExpression)
    {
        int outerPrecedence, innerPrecedence;

        // Convert is rendered as a function (CAST()) and not as an operator, so we never need to add parentheses around the inner
        if (outerExpression is SqlUnaryExpression { OperatorType: ExpressionType.Convert })
        {
            return false;
        }

View on GitHub (pinned to dbf9771522)

Solutions

  1. Use a provider that implements JSON support (SQL Server, PostgreSQL with the JSON plugin) so VisitJsonScalar is overridden.
  2. Remove the JSON mapping (config.ToJson or OwnsOne(...).ToJson()) and map the owned type via table splitting or as a complex type instead.
  3. Upgrade the provider package to a version with JSON query support.
  4. If authoring a provider, override VisitJsonScalar (and the JSON visitor hooks) in your QuerySqlGenerator.

Example fix

// before - JSON-owned entity on a provider without JSON SQL support
modelBuilder.Entity<Order>().OwnsOne(o => o.Address, a => a.ToJson("address"));
// after - map via table splitting instead
modelBuilder.Entity<Order>().OwnsOne(o => o.Address);
// or switch to a JSON-capable provider (UseSqlServer / Npgsql with jsonb)
Defensive patterns

Strategy: validation

Validate before calling

// Check the provider supports JSON before configuring JSON mapping
var supportsJson = db.Database.ProviderName switch
{
    "Microsoft.EntityFrameworkCore.SqlServer" => true,
    "Npgsql.EntityFrameworkCore.PostgreSQL" => true,
    _ => false,
};
if (!supportsJson) modelBuilder.Entity<Order>().OwnsOne(o => o.Address); // table splitting, no ToJson

Try / catch

try { return await db.Orders.Where(o => o.Address.City == "X").ToListAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("provider-specific SQL generator"))
{
    // fall back: load entities and filter client-side, or switch mapping
    return (await db.Orders.ToListAsync()).Where(o => o.Address?.City == "X").ToList();
}

Prevention

When it happens

Trigger: Querying JSON-mapped owned/complex properties on a provider that does not implement JSON SQL generation (a bare/incomplete relational provider), or a provider version that supports JSON mapping in the model but not in SQL emission. Triggered by any query that projects or filters a JSON scalar.

Common situations: Using a third-party or in-house relational provider without JSON support; enabling JSON columns in the model (ToJson/owning JSON) against SQLite (pre-JSON support) or another minimal provider; mismatched provider/model where JSON mapping was configured but the SQL generator cannot emit it.

Related errors


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