{"record":{"id":"7f7cc845edd3353b","repo":"dotnet/efcore","slug":"executeoperationontpt","errorCode":"ExecuteOperationOnTPT","errorMessage":"The operation '{operation}' is being applied on entity type '{entityType}', which is using the TPT mapping strategy. 'ExecuteDelete'/'ExecuteUpdate' operations on hierarchies mapped as TPT are not supported.","messagePattern":"The operation '(.+?)' is being applied on entity type '(.+?)', which is using the TPT mapping strategy\\. 'ExecuteDelete'/'ExecuteUpdate' operations on hierarchies mapped as TPT are not supported\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs","lineNumber":29,"sourceCode":"    protected override DeleteExpression TranslateExecuteDelete(ShapedQueryExpression source)\n    {\n        source = source.UpdateShaperExpression(new IncludePruner().Visit(source.ShaperExpression));\n\n        if (source.ShaperExpression is not StructuralTypeShaperExpression { StructuralType: IEntityType entityType } shaper)\n        {\n            throw new InvalidOperationException(RelationalStrings.ExecuteDeleteOnNonEntityType);\n        }\n\n        if (entityType.IsMappedToJson())\n        {\n            throw new InvalidOperationException(\n                RelationalStrings.ExecuteOperationOnOwnedJsonIsNotSupported(\"ExecuteDelete\", entityType.DisplayName()));\n        }\n\n        switch (entityType.GetMappingStrategy())\n        {\n            case RelationalAnnotationNames.TptMappingStrategy:\n                throw new InvalidOperationException(\n                    RelationalStrings.ExecuteOperationOnTPT(\n                        nameof(EntityFrameworkQueryableExtensions.ExecuteDelete),\n                        entityType.DisplayName()));\n\n            // Note that we do allow TPC if the target is a leaf type\n            case RelationalAnnotationNames.TpcMappingStrategy when entityType.GetDirectlyDerivedTypes().Any():\n                throw new InvalidOperationException(\n                    RelationalStrings.ExecuteOperationOnTPC(\n                        nameof(EntityFrameworkQueryableExtensions.ExecuteDelete),\n                        entityType.DisplayName()));\n        }\n\n        // Find the table model that maps to the entity type; there must be exactly one (e.g. no entity splitting).\n        var targetTable = entityType.GetTableMappings().ToList() switch\n        {\n            [] => throw new InvalidOperationException(\n                RelationalStrings.ExecuteUpdateDeleteOnEntityNotMappedToTable(entityType.DisplayName())),\n            [var singleTableMapping] => singleTableMapping.Table,","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/dotnet/efcore/blob/dbf9771522148d61a2467854921bd5dc6f6e6916/src/EFCore.Relational/Query/RelationalQueryableMethodTranslatingExpressionVisitor.ExecuteDelete.cs#L11-L47","documentation":"Thrown by TranslateExecuteDelete when the target entity type uses the TPT (Table-Per-Type) mapping strategy. EF cannot translate ExecuteDelete/ExecuteUpdate for TPT because rows for a derived type are spread across multiple tables and a single bulk DML statement cannot correctly cascade across them.","triggerScenarios":"Configuring an inheritance hierarchy with .UseTptMappingStrategy() (or per-type tables) and then calling ExecuteDelete/ExecuteUpdate on any type in that hierarchy.","commonSituations":"Migrating from TPH to TPT for storage reasons and forgetting that bulk DML is unsupported there; applying ExecuteDelete to a base entity in a TPT hierarchy.","solutions":["Switch the hierarchy to TPH (single table) which fully supports ExecuteDelete/ExecuteUpdate.","Use TPC and target only leaf types (ExecuteDelete supports TPC leaves).","Load entities and delete/update via the change tracker (SaveChanges) which handles multi-table TPT correctly.","Restructure so the deletable type is not part of a TPT hierarchy."],"exampleFix":"// before\nmodelBuilder.Entity<Animal>().UseTptMappingStrategy();\nawait db.Animals.Where(a => a.Id == id).ExecuteDeleteAsync();\n// after - TPH supports bulk delete\nmodelBuilder.Entity<Animal>().UseTphMappingStrategy();\nawait db.Animals.Where(a => a.Id == id).ExecuteDeleteAsync();\n// or fall back to tracked delete\nvar a = await db.Animals.FindAsync(id);\ndb.Animals.Remove(a);\nawait db.SaveChangesAsync();","handlingStrategy":"validation","validationCode":"var strategy = entityType.GetMappingStrategy();\nif (strategy == RelationalAnnotationNames.TptMappingStrategy)\n    throw new InvalidOperationException(\"ExecuteDelete/ExecuteUpdate not supported on TPT hierarchies; use TPH or SaveChanges.\");","typeGuard":null,"tryCatchPattern":"try { await db.Animals.Where(a => a.Id == id).ExecuteDeleteAsync(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"TPT mapping strategy\"))\n{\n    var a = await db.Animals.FindAsync(id);\n    db.Animals.Remove(a);\n    await db.SaveChangesAsync();\n}","preventionTips":["Use TPH if you need bulk delete/update across a hierarchy.","Reserve TPT for hierarchies updated via SaveChanges only.","Add model-validation rules that flag ExecuteDelete usage on TPT types.","Document mapping-strategy trade-offs for bulk DML in your team."],"tags":["executedelete","executeupdate","inheritance","tpt","bulk-operations"],"analyzedSha":"dbf9771522148d61a2467854921bd5dc6f6e6916","analyzedAt":"2026-08-06T20:46:03.226Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}