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
TranslateExecuteUpdate resolves the target table from the setters; if it resolves to a TpcTablesExpression (the multi-table form produced when ExecuteUpdate is applied to a non-leaf TPC entity), the operation is refused. TPC non-leaf types span multiple concrete tables, and a single parametrized UPDATE cannot target them all.
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 3a2006ef56)
Solutions
- Target each concrete leaf subtype separately with its own ExecuteUpdate call.
- Switch the hierarchy to TPH or a strategy that yields a single update target.
- Load entities and use SaveChanges, or issue raw SQL UPDATEs against each concrete table.
Example fix
// before (TPC non-leaf update -> 598)
await db.Set<TpcBase>()
.Where(b => b.Active)
.ExecuteUpdateAsync(s => s.SetProperty(b => b.UpdatedAt, DateTime.UtcNow));
// after (per leaf type)
await db.Set<TpcLeafA>().Where(b => b.Active)
.ExecuteUpdateAsync(s => s.SetProperty(b => b.UpdatedAt, DateTime.UtcNow));
await db.Set<TpcLeafB>().Where(b => b.Active)
.ExecuteUpdateAsync(s => s.SetProperty(b => b.UpdatedAt, DateTime.UtcNow)); Defensive patterns
Strategy: validation
Validate before calling
// Refuse ExecuteUpdate on non-leaf TPC types before invoking.
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; call ExecuteUpdate per concrete leaf subtype.");
} Prevention
- Apply ExecuteUpdate to concrete TPC leaf subtypes, not abstract roots.
- Provide a helper that fans ExecuteUpdate across all leaf subtypes in a TPC hierarchy.
- Reconsider TPC if hierarchy-wide bulk updates are frequent.
When it happens
Trigger: context.Set<TpcAbstract>().ExecuteUpdate(s => s.SetProperty(...)) where TpcAbstract is TPC-mapped and has derived types. The setters' entity resolves to the abstract type, producing a TpcTablesExpression that aggregates all concrete subtype tables.
Common situations: Bulk-updating against a TPC root or intermediate node; assuming ExecuteUpdate fans out across subtype tables.
Related errors
- The operation '{operation}' is being applied on entity type
- The element type of the result of '{dbFunction}' is mapped t
- The mapping strategy '{mappingStrategy}' specified on '{enti
- The mapping strategy '{mappingStrategy}' used for '{entityTy
- The specified discriminator value '{value}' for '{entityType
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/2a179b0d87bfba99.
Report an issue: GitHub.