dotnet/efcore · error · InvalidOperationException

ExecuteOperationOnEntitySplitting

ExecuteOperationOnEntitySplitting

Error message

The operation '{operation}' is being applied on entity type '{entityType}', which uses entity splitting. 'ExecuteDelete'/'ExecuteUpdate' operations on entity types using entity splitting are not supported.

What it means

Thrown by TranslateExecuteDelete when the target entity type has more than one table mapping - i.e. it uses entity splitting (an entity spread across multiple tables via multiple ToTable calls). ExecuteDelete cannot safely modify multiple tables in one DML statement, so it is rejected.

Source

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

                    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.
        var projectionBindingExpression = (ProjectionBindingExpression)shaper.ValueBufferExpression;
        var projection = (StructuralTypeProjectionExpression)selectExpression.GetProjection(projectionBindingExpression);
        var column = projection.BindProperty(shaper.StructuralType.GetProperties().First());
        var tableExpression = selectExpression.GetTable(column, out var tableIndex);

        // If the projected table expression (the thing to be deleted) isn't a TableExpression (e.g. it's a set operation), we can't
        // translate to a simple DELETE (which requires a simple target table), and must fall back to rewriting as a subquery.
        if (tableExpression.UnwrapJoin() is TableExpression unwrappedTableExpression)
        {
            // In normal cases, the table expression will be refer to the same table model we found above for the entity type.
            if (unwrappedTableExpression.Table is ITable)
            {

View on GitHub (pinned to dbf9771522)

Solutions

  1. Remove the entity splitting so the entity maps to a single table, then ExecuteDelete works.
  2. Load the entities and delete via SaveChanges, which correctly issues deletes across all split tables.
  3. Issue separate ExecuteSqlRaw deletes against each split table keyed by the entity key.
  4. Restructure the model (e.g. move split-off columns into owned/complex types) so bulk DML is supported.

Example fix

// before - entity splitting
modelBuilder.Entity<Customer>()
    .ToTable("Customers")
    .SplitToTable("CustomerDetails", t => t.MapKey(c => c.Id));
await db.Customers.Where(c => c.Id == id).ExecuteDeleteAsync();
// after - single table mapping
modelBuilder.Entity<Customer>().ToTable("Customers");
await db.Customers.Where(c => c.Id == id).ExecuteDeleteAsync();
// or tracked delete across splits
var c = await db.Customers.FindAsync(id);
db.Customers.Remove(c);
await db.SaveChangesAsync();
Defensive patterns

Strategy: validation

Validate before calling

var tableCount = entityType.GetTableMappings().Count();
if (tableCount > 1)
    throw new InvalidOperationException($"{entityType.Name} uses entity splitting; ExecuteDelete unsupported.");

Try / catch

try { await db.Customers.Where(c => c.Id == id).ExecuteDeleteAsync(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("entity splitting"))
{
    var c = await db.Customers.FindAsync(id);
    db.Customers.Remove(c);
    await db.SaveChangesAsync();
}

Prevention

When it happens

Trigger: Configuring an entity with entity splitting: modelBuilder.Entity<Customer>().ToTable("Customers").SplitToTable(t => t.Property(c => c.Details), "CustomerDetails") or multiple ToTable calls, then calling ExecuteDelete on that entity.

Common situations: Performance-motivated entity splitting (wide tables split vertically) combined with bulk delete attempts; legacy schemas forcing splits.

Related errors


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