{"record":{"id":"01a7deadcf2f7f89","repo":"dotnet/efcore","slug":"jsonnodemustbehandledbyproviderspecificvisitor","errorCode":"JsonNodeMustBeHandledByProviderSpecificVisitor","errorMessage":"This node should be handled by provider-specific SQL generator.","messagePattern":"This node should be handled by provider-specific SQL generator\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Query/QuerySqlGenerator.cs","lineNumber":1673,"sourceCode":"\n            for (var i = 1; i < rowValues.Count; i++)\n            {\n                if (i > 1)\n                {\n                    _relationalCommandBuilder.Append(\", \");\n                }\n\n                Visit(valuesExpression.RowValues[i]);\n            }\n        }\n    }\n\n    /// <summary>\n    ///     Generates SQL for a JSON scalar lookup expression.\n    /// </summary>\n    /// <param name=\"jsonScalarExpression\">The <see cref=\"JsonScalarExpression\" /> for which to generate SQL.</param>\n    protected virtual Expression VisitJsonScalar(JsonScalarExpression jsonScalarExpression)\n        => throw new InvalidOperationException(\n            RelationalStrings.JsonNodeMustBeHandledByProviderSpecificVisitor);\n\n    /// <summary>\n    ///     Returns a bool value indicating if the inner SQL expression required to be put inside parenthesis when generating SQL for outer\n    ///     SQL expression.\n    /// </summary>\n    /// <param name=\"outerExpression\">The outer expression which provides context in which SQL is being generated.</param>\n    /// <param name=\"innerExpression\">The inner expression which may need to be put inside parenthesis.</param>\n    /// <returns>A bool value indicating that parenthesis is required or not. </returns>\n    protected virtual bool RequiresParentheses(SqlExpression outerExpression, SqlExpression innerExpression)\n    {\n        int outerPrecedence, innerPrecedence;\n\n        // Convert is rendered as a function (CAST()) and not as an operator, so we never need to add parentheses around the inner\n        if (outerExpression is SqlUnaryExpression { OperatorType: ExpressionType.Convert })\n        {\n            return false;\n        }","sourceCodeStart":1655,"sourceCodeEnd":1691,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Query/QuerySqlGenerator.cs#L1655-L1691","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Use a provider that implements JSON support (SQL Server, PostgreSQL with the JSON plugin) so VisitJsonScalar is overridden.","Remove the JSON mapping (config.ToJson or OwnsOne(...).ToJson()) and map the owned type via table splitting or as a complex type instead.","Upgrade the provider package to a version with JSON query support.","If authoring a provider, override VisitJsonScalar (and the JSON visitor hooks) in your QuerySqlGenerator."],"exampleFix":"// before - JSON-owned entity on a provider without JSON SQL support\nmodelBuilder.Entity<Order>().OwnsOne(o => o.Address, a => a.ToJson(\"address\"));\n// after - map via table splitting instead\nmodelBuilder.Entity<Order>().OwnsOne(o => o.Address);\n// or switch to a JSON-capable provider (UseSqlServer / Npgsql with jsonb)","handlingStrategy":"validation","validationCode":"// Check the provider supports JSON before configuring JSON mapping\nvar supportsJson = db.Database.ProviderName switch\n{\n    \"Microsoft.EntityFrameworkCore.SqlServer\" => true,\n    \"Npgsql.EntityFrameworkCore.PostgreSQL\" => true,\n    _ => false,\n};\nif (!supportsJson) modelBuilder.Entity<Order>().OwnsOne(o => o.Address); // table splitting, no ToJson","typeGuard":null,"tryCatchPattern":"try { return await db.Orders.Where(o => o.Address.City == \"X\").ToListAsync(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"provider-specific SQL generator\"))\n{\n    // fall back: load entities and filter client-side, or switch mapping\n    return (await db.Orders.ToListAsync()).Where(o => o.Address?.City == \"X\").ToList();\n}","preventionTips":["Only enable ToJson on JSON-capable providers (SqlServer, Npgsql).","Prefer ComplexProperty over ToJson for owned value objects unless JSON storage is required.","Pin provider versions that match your EF Core runtime version for JSON support.","If authoring a provider, override VisitJsonScalar and the JSON query hooks."],"tags":["json","provider","sql-generation","owned-types","complex-types"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}