dotnet/efcore · error · InvalidOperationException
ExecuteOperationOnTPC
ExecuteOperationOnTPC
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
Thrown by TranslateExecuteUpdate when the resolved target table is a TpcTablesExpression - i.e. the entity uses TPC (Table-Per-Concrete-Type) mapping. Unlike ExecuteDelete (which tolerates TPC leaf types), ExecuteUpdate has no support for TPC at all, because an update would have to fan out across multiple concrete tables and EF cannot generate that.
Source
Thrown at src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs:43
{
Check.DebugAssert(setters.Count > 0, "Empty setters list");
// Our source may have IncludeExpressions because of owned entities or auto-include; unwrap these, as they're meaningless for
// ExecuteUpdate's lambdas. Note that we don't currently support updates across tables.
source = source.UpdateShaperExpression(new IncludePruner().Visit(source.ShaperExpression));
var selectExpression = (SelectExpression)source.QueryExpression;
// Translate the setters: the left (property) selectors get translated to ColumnExpressions, the right (value) selectors to
// arbitrary SqlExpressions.
// Note that if the query isn't natively supported, we'll do a pushdown (see PushdownWithPkInnerJoinPredicate below); if that
// happens, we'll have to re-translate the setters over the new query (which includes a JOIN). However, we still translate here
// since we need the target table in order to perform the check below.
var translatedSetters = TranslateSetters(source, setters, out var targetTable);
if (targetTable is TpcTablesExpression tpcTablesExpression)
{
throw new InvalidOperationException(
RelationalStrings.ExecuteOperationOnTPC(
nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate),
tpcTablesExpression.EntityType.DisplayName()));
}
// Check if the provider has a native translation for the update represented by the select expression.
// The default relational implementation handles simple, universally-supported cases (i.e. no operators except for predicate).
// Providers may override IsValidSelectExpressionForExecuteUpdate to add support for more cases via provider-specific UPDATE syntax.
if (IsValidSelectExpressionForExecuteUpdate(selectExpression, targetTable, out var tableExpression))
{
selectExpression.ReplaceProjection([]);
selectExpression.ApplyProjection();
return new UpdateExpression(tableExpression, selectExpression, translatedSetters);
}
return PushdownWithPkInnerJoinPredicate();
View on GitHub (pinned to dbf9771522)
Solutions
- Switch the hierarchy to TPH so ExecuteUpdate is supported.
- Load entities and update via SaveChanges (which supports TPC).
- Issue ExecuteUpdate per concrete leaf type by querying each DbSet separately, if feasible.
- Restructure so the updatable type is not part of a TPC hierarchy.
Example fix
// before
modelBuilder.Entity<Animal>().UseTpcMappingStrategy();
await db.Animals.Where(a => a.Id == id)
.ExecuteUpdateAsync(s => s.SetProperty(a => a.Name, "x"));
// after - TPH supports bulk update
modelBuilder.Entity<Animal>().UseTphMappingStrategy();
await db.Animals.Where(a => a.Id == id)
.ExecuteUpdateAsync(s => s.SetProperty(a => a.Name, "x"));
// or tracked update
var a = await db.Animals.FindAsync(id);
a.Name = "x";
await db.SaveChangesAsync(); Defensive patterns
Strategy: validation
Validate before calling
var strategy = entityType.GetMappingStrategy();
if (strategy == RelationalAnnotationNames.TpcMappingStrategy)
throw new InvalidOperationException("ExecuteUpdate is not supported on TPC hierarchies; use TPH or SaveChanges."); Try / catch
try { await db.Animals.Where(predicate).ExecuteUpdateAsync(setters); }
catch (InvalidOperationException ex) when (ex.Message.Contains("TPC mapping strategy"))
{
var entities = await db.Animals.Where(predicate).ToListAsync();
foreach (var a in entities) ApplySetters(a);
await db.SaveChangesAsync();
} Prevention
- Do not use ExecuteUpdate on TPC hierarchies; use TPH or SaveChanges.
- Pin a model-validation rule that rejects ExecuteUpdate on TPC types.
- Document TPC as insert-optimized but unsupported for bulk update.
- Write tests for bulk-update across each mapping strategy.
When it happens
Trigger: Configuring a hierarchy with .UseTpcMappingStrategy() and calling ExecuteUpdate on any type (leaf or not) in that hierarchy.
Common situations: Choosing TPC for insert performance and then needing bulk updates; targeting a TPC leaf type with ExecuteUpdate (still unsupported).
Related errors
- ExecuteOperationOnTPC
- ExecuteOperationOnTPT
- Using '{methodName}' on DbSet of '{entityType}' is not suppo
- The entity type '{entityType}' cannot be instantiated becaus
- The derived entity type '{entityType}' was configured with t
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/8186b7343a13bae5.
Report an issue: GitHub.