dotnet/efcore · error · InvalidOperationException
Both ' ' and ' ' are explicitly mapped to the stored…
Error message
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. What it means
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.
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.
Example fix
// before (TPH, derived type declares its own sproc)
modelBuilder.Entity<Person>().DeleteUsingStoredProcedure("sp_DeletePerson")
.HasParameter(p => p.Id);
modelBuilder.Entity<Customer>().DeleteUsingStoredProcedure("sp_DeleteCustomer"); // throws 431
// after - only the root has the sproc; add Customer-specific parameters there
modelBuilder.Entity<Person>().DeleteUsingStoredProcedure("sp_DeletePerson")
.HasParameter("ShippingAddress", p => ((Customer)p).ShippingAddress); Defensive patterns
Strategy: validation
Validate before calling
foreach (var root in modelBuilder.Model.GetEntityTypes().Where(t => t.BaseType == null))
{
if (root.GetMappingStrategy() == RelationalAnnotationNames.TphMappingStrategy
|| root.FindDiscriminatorProperty() == null) continue;
foreach (var sot in new[] { StoreObjectType.InsertStoredProcedure, StoreObjectType.DeleteStoredProcedure, StoreObjectType.UpdateStoredProcedure })
{
var rootSproc = StoredProcedure.FindDeclaredStoredProcedure(root, sot);
if (rootSproc == null) continue;
foreach (var et in root.GetDerivedTypes())
{
var sproc = StoredProcedure.FindDeclaredStoredProcedure(et, sot);
if (sproc != null && sproc != rootSproc)
throw new InvalidOperationException($"Derived {et.DisplayName()} declares its own {sot}; define it on {root.DisplayName()} only.");
}
}
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ' ' is mapped to the stored procedure ' ' while ' ' is…
- Both ' ' and ' ' are mapped to the stored procedure ' '…
- ' ' is mapped to the database function ' ' while ' ' is…
- ' ' is mapped to the table ' ' while ' ' is mapped to the…
- ' ' is mapped to the view ' ' while ' ' is mapped to the…
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/8e2e73128cdfb400.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:2318
var rootSproc = isSproc ? StoredProcedure.FindDeclaredStoredProcedure(rootEntityType, storeObjectType) : null;
var rootId = StoreObjectIdentifier.Create(rootEntityType, storeObjectType);
foreach (var entityType in rootEntityType.GetDerivedTypes())
{
var entityId = StoreObjectIdentifier.Create(entityType, storeObjectType);
if (entityId == null)
{
continue;
}
if (rootId == entityId)
{
if (rootSproc != null)
{
var sproc = StoredProcedure.FindDeclaredStoredProcedure(entityType, storeObjectType);
if (sproc != null
&& sproc != rootSproc)
{
throw new InvalidOperationException(
RelationalStrings.StoredProcedureTphDuplicate(
entityType.DisplayName(), rootEntityType.DisplayName(), rootId?.DisplayName()));
}
}
continue;
}
switch (storeObjectType)
{
case StoreObjectType.Table:
throw new InvalidOperationException(
RelationalStrings.TphTableMismatch(
entityType.DisplayName(), entityId.Value.DisplayName(),
rootEntityType.DisplayName(), rootId?.DisplayName()));
case StoreObjectType.View:
throw new InvalidOperationException(
RelationalStrings.TphViewMismatch(View on GitHub (pinned to 3a2006ef56)