dotnet/efcore · error · InvalidOperationException

ExecuteOperationOnTPT

ExecuteOperationOnTPT

Error message

The operation '{operation}' is being applied on entity type '{entityType}', which is using the TPT mapping strategy. 'ExecuteDelete'/'ExecuteUpdate' operations on hierarchies mapped as TPT are not supported.

What it means

Thrown by TranslateExecuteDelete when the target entity type uses the TPT (Table-Per-Type) mapping strategy. EF cannot translate ExecuteDelete/ExecuteUpdate for TPT because rows for a derived type are spread across multiple tables and a single bulk DML statement cannot correctly cascade across them.

Source

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

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

        // 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,

View on GitHub (pinned to dbf9771522)

Solutions

  1. Switch the hierarchy to TPH (single table) which fully supports ExecuteDelete/ExecuteUpdate.
  2. Use TPC and target only leaf types (ExecuteDelete supports TPC leaves).
  3. Load entities and delete/update via the change tracker (SaveChanges) which handles multi-table TPT correctly.
  4. Restructure so the deletable type is not part of a TPT hierarchy.

Example fix

// before
modelBuilder.Entity<Animal>().UseTptMappingStrategy();
await db.Animals.Where(a => a.Id == id).ExecuteDeleteAsync();
// after - TPH supports bulk delete
modelBuilder.Entity<Animal>().UseTphMappingStrategy();
await db.Animals.Where(a => a.Id == id).ExecuteDeleteAsync();
// or fall back to tracked delete
var a = await db.Animals.FindAsync(id);
db.Animals.Remove(a);
await db.SaveChangesAsync();
Defensive patterns

Strategy: validation

Validate before calling

var strategy = entityType.GetMappingStrategy();
if (strategy == RelationalAnnotationNames.TptMappingStrategy)
    throw new InvalidOperationException("ExecuteDelete/ExecuteUpdate not supported on TPT hierarchies; use TPH or SaveChanges.");

Try / catch

try { await db.Animals.Where(a => a.Id == id).ExecuteDeleteAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("TPT mapping strategy"))
{
    var a = await db.Animals.FindAsync(id);
    db.Animals.Remove(a);
    await db.SaveChangesAsync();
}

Prevention

When it happens

Trigger: Configuring an inheritance hierarchy with .UseTptMappingStrategy() (or per-type tables) and then calling ExecuteDelete/ExecuteUpdate on any type in that hierarchy.

Common situations: Migrating from TPH to TPT for storage reasons and forgetting that bulk DML is unsupported there; applying ExecuteDelete to a base entity in a TPT hierarchy.

Related errors


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