dotnet/efcore · error · InvalidOperationException

'{operation}' used over owned type '{entityType}' which is m

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

TranslateExecuteDelete checks entityType.IsMappedToJson() and refuses to delete JSON-mapped owned entities. JSON-mapped owned data lives as a JSON document inside the owner's column; deleting it independently of the owner is not a meaningful server operation in EF's model. The message suggests complex types as an alternative mapping.

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 3a2006ef56)

Solutions

  1. Delete the owner entity (which removes its JSON document), or update the owner to null/remove the element from the collection via SaveChanges.
  2. If independent deletes are required, remap the owned type to its own table (ToTable) or use a complex type instead of .ToJson().
  3. Use raw SQL to mutate the JSON document if a partial JSON delete is needed (database-specific JSON functions).

Example fix

// before (delete JSON-mapped owned)
await db.Owners
    .SelectMany(o => o.Tags /* OwnsMany().ToJson() */)
    .ExecuteDeleteAsync();
// after (delete owner, or remap owned to a table)
await db.Owners.Where(o => o.Id == id).ExecuteDeleteAsync();
Defensive patterns

Strategy: validation

Validate before calling

// Block ExecuteDelete on JSON-mapped owned entities up front.
var et = db.Model.FindEntityType(typeof(TEntity))!;
if (et.IsMappedToJson())
    throw new InvalidOperationException(
        $"{et.Name} is JSON-mapped; delete the owner or remap to a table/complex type.");

Prevention

When it happens

Trigger: context.Owners.SelectMany(o => o.JsonCollection).ExecuteDelete() where JsonCollection is an OwnsMany(...).ToJson() navigation; calling ExecuteDelete on a query rooted at a JSON-mapped owned entity; targeting an owned entity mapped via .ToJson().

Common situations: Treating a JSON collection as a regular related entity and attempting bulk delete; refactoring from table-splitting to JSON mapping and reusing an existing ExecuteDelete call.

Related errors


AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11). Data as JSON: /api/errors/576b807b0d95fc8a. Report an issue: GitHub.