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
- Remove the entity splitting so the entity maps to a single table, then ExecuteDelete works.
- Load the entities and delete via SaveChanges, which correctly issues deletes across all split tables.
- Issue separate ExecuteSqlRaw deletes against each split table keyed by the entity key.
- 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
- Avoid entity splitting for types you need to bulk-delete.
- Use SaveChanges for split entities (it handles multiple tables).
- Move wide/seldom-used columns to owned or complex types instead of split tables.
- Document split entities as excluded from ExecuteDelete.
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
- ExecuteUpdateDeleteOnEntityNotMappedToTable
- ExecuteDeleteOnNonEntityType
- ExecuteOperationOnOwnedJsonIsNotSupported
- ExecuteOperationOnTPT
- ExecuteOperationOnTPC
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/527bc2b7621e53f8.
Report an issue: GitHub.