{"record":{"id":"8186b7343a13bae5","repo":"dotnet/efcore","slug":"executeoperationontpc-8186b7","errorCode":"ExecuteOperationOnTPC","errorMessage":"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.","messagePattern":"The operation '(.+?)' is being applied on entity type '(.+?)', 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\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs","lineNumber":43,"sourceCode":"    {\n        Check.DebugAssert(setters.Count > 0, \"Empty setters list\");\n\n        // Our source may have IncludeExpressions because of owned entities or auto-include; unwrap these, as they're meaningless for\n        // ExecuteUpdate's lambdas. Note that we don't currently support updates across tables.\n        source = source.UpdateShaperExpression(new IncludePruner().Visit(source.ShaperExpression));\n\n        var selectExpression = (SelectExpression)source.QueryExpression;\n\n        // Translate the setters: the left (property) selectors get translated to ColumnExpressions, the right (value) selectors to\n        // arbitrary SqlExpressions.\n        // Note that if the query isn't natively supported, we'll do a pushdown (see PushdownWithPkInnerJoinPredicate below); if that\n        // happens, we'll have to re-translate the setters over the new query (which includes a JOIN). However, we still translate here\n        // since we need the target table in order to perform the check below.\n        var translatedSetters = TranslateSetters(source, setters, out var targetTable);\n\n        if (targetTable is TpcTablesExpression tpcTablesExpression)\n        {\n            throw new InvalidOperationException(\n                RelationalStrings.ExecuteOperationOnTPC(\n                    nameof(EntityFrameworkQueryableExtensions.ExecuteUpdate),\n                    tpcTablesExpression.EntityType.DisplayName()));\n        }\n\n        // Check if the provider has a native translation for the update represented by the select expression.\n        // The default relational implementation handles simple, universally-supported cases (i.e. no operators except for predicate).\n        // Providers may override IsValidSelectExpressionForExecuteUpdate to add support for more cases via provider-specific UPDATE syntax.\n        if (IsValidSelectExpressionForExecuteUpdate(selectExpression, targetTable, out var tableExpression))\n        {\n            selectExpression.ReplaceProjection([]);\n            selectExpression.ApplyProjection();\n\n            return new UpdateExpression(tableExpression, selectExpression, translatedSetters);\n        }\n\n        return PushdownWithPkInnerJoinPredicate();\n","sourceCodeStart":25,"sourceCodeEnd":61,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteUpdate.cs#L25-L61","documentation":"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.","triggerScenarios":"Configuring a hierarchy with .UseTpcMappingStrategy() and calling ExecuteUpdate on any type (leaf or not) in that hierarchy.","commonSituations":"Choosing TPC for insert performance and then needing bulk updates; targeting a TPC leaf type with ExecuteUpdate (still unsupported).","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."],"exampleFix":"// before\nmodelBuilder.Entity<Animal>().UseTpcMappingStrategy();\nawait db.Animals.Where(a => a.Id == id)\n    .ExecuteUpdateAsync(s => s.SetProperty(a => a.Name, \"x\"));\n// after - TPH supports bulk update\nmodelBuilder.Entity<Animal>().UseTphMappingStrategy();\nawait db.Animals.Where(a => a.Id == id)\n    .ExecuteUpdateAsync(s => s.SetProperty(a => a.Name, \"x\"));\n// or tracked update\nvar a = await db.Animals.FindAsync(id);\na.Name = \"x\";\nawait db.SaveChangesAsync();","handlingStrategy":"validation","validationCode":"var strategy = entityType.GetMappingStrategy();\nif (strategy == RelationalAnnotationNames.TpcMappingStrategy)\n    throw new InvalidOperationException(\"ExecuteUpdate is not supported on TPC hierarchies; use TPH or SaveChanges.\");","typeGuard":null,"tryCatchPattern":"try { await db.Animals.Where(predicate).ExecuteUpdateAsync(setters); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"TPC mapping strategy\"))\n{\n    var entities = await db.Animals.Where(predicate).ToListAsync();\n    foreach (var a in entities) ApplySetters(a);\n    await db.SaveChangesAsync();\n}","preventionTips":["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."],"tags":["executeupdate","inheritance","tpc","bulk-operations"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}