{"record":{"id":"8e2e73128cdfb400","repo":"dotnet/efcore","slug":"both-entitytype-and-otherentitytype-are-e","errorCode":null,"errorMessage":"Both '{entityType}' and  '{otherEntityType}' are explicitly mapped to the stored procedure '{sproc}' using the 'TPH' mapping strategy. Configure the stored procedure mapping on the root entity type, including all parameters for the derived types. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.","messagePattern":"Both '(.+?)' and  '(.+?)' are explicitly mapped to the stored procedure '(.+?)' using the 'TPH' mapping strategy\\. Configure the stored procedure mapping on the root entity type, including all parameters for the derived types\\. See https://go\\.microsoft\\.com/fwlink/\\?linkid=2130430 for more information\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs","lineNumber":2318,"sourceCode":"        var rootSproc = isSproc ? StoredProcedure.FindDeclaredStoredProcedure(rootEntityType, storeObjectType) : null;\n        var rootId = StoreObjectIdentifier.Create(rootEntityType, storeObjectType);\n        foreach (var entityType in rootEntityType.GetDerivedTypes())\n        {\n            var entityId = StoreObjectIdentifier.Create(entityType, storeObjectType);\n            if (entityId == null)\n            {\n                continue;\n            }\n\n            if (rootId == entityId)\n            {\n                if (rootSproc != null)\n                {\n                    var sproc = StoredProcedure.FindDeclaredStoredProcedure(entityType, storeObjectType);\n                    if (sproc != null\n                        && sproc != rootSproc)\n                    {\n                        throw new InvalidOperationException(\n                            RelationalStrings.StoredProcedureTphDuplicate(\n                                entityType.DisplayName(), rootEntityType.DisplayName(), rootId?.DisplayName()));\n                    }\n                }\n\n                continue;\n            }\n\n            switch (storeObjectType)\n            {\n                case StoreObjectType.Table:\n                    throw new InvalidOperationException(\n                        RelationalStrings.TphTableMismatch(\n                            entityType.DisplayName(), entityId.Value.DisplayName(),\n                            rootEntityType.DisplayName(), rootId?.DisplayName()));\n                case StoreObjectType.View:\n                    throw new InvalidOperationException(\n                        RelationalStrings.TphViewMismatch(","sourceCodeStart":2300,"sourceCodeEnd":2336,"githubUrl":"https://github.com/dotnet/efcore/blob/3a2006ef569de08368d59db5e1468aa8f407e4f8/src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs#L2300-L2336","documentation":"Thrown inside ValidateTphMapping when, under TPH, a derived entity type has its own declared stored procedure (StoredProcedure.FindDeclaredStoredProcedure returns one) that differs from the stored procedure declared on the root (rootSproc). In TPH all derived types must use the root's stored procedure; you cannot give a derived type a separate sproc.","triggerScenarios":"A TPH hierarchy where the root has *UsingStoredProcedure and a derived type ALSO calls *UsingStoredProcedure with a different stored procedure name. The check fires when rootId == entityId (same store object) but sproc != rootSproc.","commonSituations":"Adding a per-type stored procedure while still on TPH; copy-pasting sproc config onto derived types; misunderstanding that TPH sproc mapping lives only on the root.","solutions":["Remove the stored procedure mapping from the derived type — define all parameters (including derived-type columns) on the root's stored procedure.","If separate stored procedures per type are truly required, switch the hierarchy to TPT/TPC."],"exampleFix":"// before (TPH, derived type declares its own sproc)\nmodelBuilder.Entity<Person>().DeleteUsingStoredProcedure(\"sp_DeletePerson\")\n    .HasParameter(p => p.Id);\nmodelBuilder.Entity<Customer>().DeleteUsingStoredProcedure(\"sp_DeleteCustomer\"); // throws 431\n\n// after - only the root has the sproc; add Customer-specific parameters there\nmodelBuilder.Entity<Person>().DeleteUsingStoredProcedure(\"sp_DeletePerson\")\n    .HasParameter(\"ShippingAddress\", p => ((Customer)p).ShippingAddress);","handlingStrategy":"validation","validationCode":"foreach (var root in modelBuilder.Model.GetEntityTypes().Where(t => t.BaseType == null))\n{\n    if (root.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy\n        || root.FindDiscriminatorProperty() == null) continue;\n    foreach (var sot in new[] { StoreObjectType.InsertStoredProcedure, StoreObjectType.DeleteStoredProcedure, StoreObjectType.UpdateStoredProcedure })\n    {\n        var rootSproc = StoredProcedure.FindDeclaredStoredProcedure(root, sot);\n        if (rootSproc == null) continue;\n        foreach (var et in root.GetDerivedTypes())\n        {\n            var sproc = StoredProcedure.FindDeclaredStoredProcedure(et, sot);\n            if (sproc != null && sproc != rootSproc)\n                throw new InvalidOperationException($\"Derived {et.DisplayName()} declares its own {sot}; define it on {root.DisplayName()} only.\");\n        }\n    }\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["In TPH, declare stored procedures on the root only and add all parameters there.","Search the configuration for *UsingStoredProcedure on derived types and remove them.","Switch to TPT/TPC if per-type stored procedures are genuinely required."],"tags":["ef-core","model-validation","mapping","inheritance","tph","stored-procedures"],"backgroundTag":null,"analyzedSha":"3a2006ef569de08368d59db5e1468aa8f407e4f8","analyzedAt":"2026-08-11T23:42:04.146Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}