dotnet/efcore · error · InvalidOperationException

The operation '{operation}' is being applied on entity type

Error message

The operation '{operation}' is being applied on entity type '{entityType}', which is using the TPC mapping strategy and is not a leaf type. 'ExecuteDelete'/'ExecuteUpdate' operations on entity types participating in TPC hierarchies is only supported for leaf types.

What it means

TranslateExecuteDelete allows TPC only for leaf types. This throw fires for a TPC-mapped entity that has directly derived types (non-leaf), because deleting a non-leaf TPC type would have to delete from multiple concrete tables and there is no single table that represents the abstract type. Leaf types map to exactly one table and are safe.

Source

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

        }

        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()));
        }

        // Find the table model that maps to the entity type; there must be exactly one (e.g. no entity splitting).
        var targetTable = entityType.GetTableMappings().ToList() switch
        {
            [] => throw new InvalidOperationException(
                RelationalStrings.ExecuteUpdateDeleteOnEntityNotMappedToTable(entityType.DisplayName())),
            [var singleTableMapping] => singleTableMapping.Table,
            _ => throw new InvalidOperationException(
                RelationalStrings.ExecuteOperationOnEntitySplitting(
                    nameof(EntityFrameworkQueryableExtensions.ExecuteDelete), entityType.DisplayName())),
        };
        var selectExpression = (SelectExpression)source.QueryExpression;

        // Find the table expression in the SelectExpression that corresponds to the projected entity type.

View on GitHub (pinned to 3a2006ef56)

Solutions

  1. Target each concrete leaf subtype separately, or query for instances and use RemoveRange + SaveChanges.
  2. Restructure the query so it filters to a single leaf type before ExecuteDelete.
  3. If you must delete across the hierarchy, issue raw SQL deletes against each concrete table.
  4. Reconsider TPC if hierarchy-wide bulk operations are common.

Example fix

// before (TPC non-leaf)
await db.Set<TpcBase>().Where(b => b.Stale).ExecuteDeleteAsync();
// after (per leaf type)
await db.Set<TpcLeafA>().Where(b => b.Stale).ExecuteDeleteAsync();
await db.Set<TpcLeafB>().Where(b => b.Stale).ExecuteDeleteAsync();
Defensive patterns

Strategy: validation

Validate before calling

// Only allow ExecuteDelete on TPC leaf types.
var et = db.Model.FindEntityType(typeof(TEntity))!;
if (et.GetMappingStrategy() == RelationalAnnotationNames.TpcMappingStrategy
    && et.GetDirectlyDerivedTypes().Any())
{
    throw new InvalidOperationException(
        $"{et.Name} is a non-leaf TPC type; target a concrete leaf subtype for ExecuteDelete.");
}

Prevention

When it happens

Trigger: context.Set<TpcAbstract>().ExecuteDelete() where TpcAbstract is mapped with .UseTpcMappingStrategy() and has derived types; targeting an intermediate abstract type in a TPC hierarchy.

Common situations: Bulk-deleting against a TPC root or intermediate node; assuming ExecuteDelete on a base type fans out to all subtype tables.

Related errors


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