dotnet/efcore · error · InvalidOperationException

ExecuteOperationOnOwnedJsonIsNotSupported

ExecuteOperationOnOwnedJsonIsNotSupported

Error message

'{operation}' used over owned type '{entityType}' which is mapped to JSON; '{operation}' on JSON-mapped owned entities is not supported. Consider mapping your type as a complex type instead.

What it means

Thrown by TranslateExecuteDelete (and TranslateExecuteUpdate setters) when the target entity type is mapped to JSON (an owned entity mapped with ToJson). Bulk modify operations cannot generate partial JSON updates/deletes in this path, so the operation is rejected and the message suggests using a complex type instead.

Source

Thrown at src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs:22

using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace Microsoft.EntityFrameworkCore.Query;

public partial class RelationalQueryableMethodTranslatingExpressionVisitor
{
    /// <inheritdoc />
    protected override DeleteExpression TranslateExecuteDelete(ShapedQueryExpression source)
    {
        source = source.UpdateShaperExpression(new IncludePruner().Visit(source.ShaperExpression));

        if (source.ShaperExpression is not StructuralTypeShaperExpression { StructuralType: IEntityType entityType } shaper)
        {
            throw new InvalidOperationException(RelationalStrings.ExecuteDeleteOnNonEntityType);
        }

        if (entityType.IsMappedToJson())
        {
            throw new InvalidOperationException(
                RelationalStrings.ExecuteOperationOnOwnedJsonIsNotSupported("ExecuteDelete", entityType.DisplayName()));
        }

        switch (entityType.GetMappingStrategy())
        {
            case RelationalAnnotationNames.TptMappingStrategy:
                throw new InvalidOperationException(
                    RelationalStrings.ExecuteOperationOnTPT(
                        nameof(EntityFrameworkQueryableExtensions.ExecuteDelete),
                        entityType.DisplayName()));

            // Note that we do allow TPC if the target is a leaf type
            case RelationalAnnotationNames.TpcMappingStrategy when entityType.GetDirectlyDerivedTypes().Any():
                throw new InvalidOperationException(
                    RelationalStrings.ExecuteOperationOnTPC(
                        nameof(EntityFrameworkQueryableExtensions.ExecuteDelete),
                        entityType.DisplayName()));
        }

View on GitHub (pinned to dbf9771522)

Solutions

  1. Map the owned type as a complex type (OwnsOne without ToJson, or ComplexProperty) instead of JSON, which ExecuteUpdate/ExecuteDelete fully support.
  2. Load the entities and update/delete them through the regular change tracker rather than the bulk ExecuteUpdate/ExecuteDelete.
  3. Restrict ExecuteDelete to the owning entity type only, not the JSON-owned entity.
  4. For JSON partial updates, fall back to raw SQL via Database.ExecuteSqlRaw with the provider's JSON functions.

Example fix

// before
modelBuilder.Entity<Order>().OwnsOne(o => o.Address, a => a.ToJson("address"));
await db.Set<Address>().ExecuteDeleteAsync(); // owned JSON entity
// after - map as a complex type so bulk ops are supported
modelBuilder.Entity<Order>().ComplexProperty(o => o.Address);
await db.Orders.Where(o => o.Id == id).ExecuteDeleteAsync();
Defensive patterns

Strategy: validation

Validate before calling

// Avoid JSON mapping for types you intend to bulk-modify
foreach (var et in db.Model.GetEntityTypes())
    if (et.IsMappedToJson()) Console.WriteLine($"{et.Name} is JSON-mapped; bulk ops unsupported.");

Try / catch

try { await db.Set<Address>().ExecuteDeleteAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("mapped to JSON"))
{
    // load and remove via change tracker, or switch mapping
    foreach (var a in db.Set<Address>()) db.Remove(a);
    await db.SaveChangesAsync();
}

Prevention

When it happens

Trigger: Calling ExecuteDelete/ExecuteUpdate (via a setter referencing a JSON-owned entity) on an entity whose owned types are mapped to JSON. Example: an Order with OwnsOne(o => o.Address, a => a.ToJson("addr")) and then ExecuteDelete targeting a query that includes the JSON-mapped owned entity, or SetProperty referencing it.

Common situations: Adopting JSON column mapping for owned entities and then attempting bulk updates; mixing ToJson with ExecuteUpdate setters that assign the owned object.

Related errors


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