dotnet/efcore · error · InvalidOperationException

ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn

ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn

Error message

'ExecuteUpdate' cannot currently set a property in a JSON column to a regular, non-JSON column; see https://github.com/dotnet/efcore/issues/36688.

What it means

When SetProperty targets a scalar property inside a JSON column (JsonScalarExpression, line 371-400), EF must serialize the value to its JSON representation via TrySerializeScalarToJson. If the value is another regular ColumnExpression (a non-JSON column), there is no way to convert a column's runtime value into JSON server-side, so the non-JSON-column branch at line 391-394 throws ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn (tracked by issue #36688).

Source

Thrown at src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs:391

                    var typeMapping = jsonScalar.TypeMapping;
                    Check.DebugAssert(typeMapping is not null);

                    // We should never see a JsonScalarExpression without a path - that means we're mapping a JSON scalar directly to a relational column.
                    // This is in theory possible (e.g. map a DateTime to a 'json' column with a single string timestamp representation inside, instead of to
                    // SQL Server datetime2), but contrived and unsupported.
                    Check.DebugAssert(jsonScalar.Path.Count > 0);

                    ProcessColumn(jsonColumn, targetProperty);

                    var translatedValue = TranslateScalarSetterValueSelector(source, valueSelector, jsonScalar.Type, typeMapping);

                    // We now have the relational scalar expression for the value; but we need the JSON representation to pass to the provider's JSON modification
                    // function (e.g. SQL Server JSON_MODIFY()).
                    // For example, for a DateTime we'd have e.g. a SqlConstantExpression containing a DateTime instance, but we need a string containing
                    // the JSON-encoded ISO8601 representation.
                    if (!TrySerializeScalarToJson(jsonScalar, translatedValue, out var jsonValue))
                    {
                        throw new InvalidOperationException(
                            translatedValue is ColumnExpression
                                ? RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToNonJsonColumn
                                : RelationalStrings.ExecuteUpdateCannotSetJsonPropertyToArbitraryExpression);
                    }

                    // We now have a serialized JSON value (number, string or bool) - generate a setter for it.
                    GenerateJsonPartialUpdateSetterWrapper(jsonScalar, jsonColumn, jsonValue);
                    continue;
                }

                case StructuralTypeShaperExpression { ValueBufferExpression: JsonQueryExpression jsonQuery }:
                    ProcessStructuralJsonSetter(jsonQuery);
                    continue;

                case CollectionResultExpression { QueryExpression: JsonQueryExpression jsonQuery }:
                    ProcessStructuralJsonSetter(jsonQuery);
                    continue;

View on GitHub (pinned to dbf9771522)

Solutions

  1. Assign a constant, a parameter, or another JSON scalar property to the JSON path instead of a regular column.
  2. Project the source column value into the application first, then pass it as a parameter to ExecuteUpdate.
  3. Perform the migration via raw SQL using the database's JSON functions if a server-side copy is mandatory.

Example fix

// before (copying a flat column into a JSON path)
db.Records.ExecuteUpdate(s => s.SetProperty(
    r => r.JsonData.Notes, r => r.LegacyNotes));

// after (materialize the value client-side and pass as a constant/parameter)
var notes = await db.Records.Where(r => r.Id == id)
    .Select(r => r.LegacyNotes).FirstAsync();
db.Records.Where(r => r.Id == id)
    .ExecuteUpdate(s => s.SetProperty(
        r => r.JsonData.Notes, notes));
Defensive patterns

Strategy: validation

Validate before calling

// Before assigning a column to a JSON scalar path, materialize the source value client-side.
// (No runtime API to detect this; guard by reviewing the SetProperty value lambda.)
// Rule: JSON scalar SetProperty values must be constant/parameter/JSON-scalar only.

Try / catch

try { await q.ExecuteUpdateAsync(s => s.SetProperty(e => e.Json.X, e => e.PlainCol)); }
catch (InvalidOperationException ex) when (ex.Message.Contains("non-JSON column"))
{ /* read PlainCol client-side and pass as parameter */ }

Prevention

When it happens

Trigger: Assigning a regular relational column to a JSON scalar path: SetProperty(e => e.JsonDetails.Notes, e => e.PlainTextColumn) where JsonDetails is JSON-mapped and PlainTextColumn is a normal column.

Common situations: Trying to copy a value from one column into a JSON sub-property; modeling data migration via ExecuteUpdate that moves flat columns into JSON; using two entities sharing a table where one side is JSON.

Related errors


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