dotnet/efcore · error · InvalidOperationException
'{entityType}' is mapped to the table '{table}' while '{othe
Error message
'{entityType}' is mapped to the table '{table}' while '{otherEntityType}' is mapped to the table '{otherTable}'. Map all the entity types in the hierarchy to the same table, or remove the discriminator and map them all to different tables. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. What it means
In ValidateTphMapping for StoreObjectType.Table: a TPH hierarchy (one that has a discriminator property) must keep every type in the same table, because the discriminator column only exists once and queries rely on a single physical table. When a derived type resolves to a different Table StoreObjectIdentifier than the root, EF throws.
Source
Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:2330
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(
entityType.DisplayName(), entityId.Value.DisplayName(),
rootEntityType.DisplayName(), rootId?.DisplayName()));
case StoreObjectType.Function:
throw new InvalidOperationException(
RelationalStrings.TphDbFunctionMismatch(
entityType.DisplayName(), entityId.Value.DisplayName(),
rootEntityType.DisplayName(), rootId?.DisplayName()));
case StoreObjectType.InsertStoredProcedure:
case StoreObjectType.DeleteStoredProcedure:
case StoreObjectType.UpdateStoredProcedure:
throw new InvalidOperationException(
RelationalStrings.TphStoredProcedureMismatch(View on GitHub (pinned to dbf9771522)
Solutions
- Remove the .ToTable call from the derived type so it inherits the root's table.
- If you want per-type tables, remove the discriminator property to move to TPT, then give each type its own table.
- Explicitly map the derived type to the SAME table name/schema as the root to satisfy the equality check.
Example fix
// before
modelBuilder.Entity<Base>().HasDiscriminator(b => b.Kind);
modelBuilder.Entity<Derived>().ToTable("Derived"); // TPH but split table
// after
modelBuilder.Entity<Base>().HasDiscriminator(b => b.Kind);
// derived inherits the root table, OR drop discriminator and use TPT Defensive patterns
Strategy: validation
Validate before calling
bool TphAllTypesSameTable(DbContext context)
{
foreach (var root in context.Model.GetEntityTypes()
.Where(e => e.BaseType == null && e.FindDiscriminatorProperty() != null && e.GetDerivedTypes().Any()))
{
var rootTable = StoreObjectIdentifier.Create(root, StoreObjectType.Table);
if (rootTable is null) continue;
foreach (var d in root.GetDerivedTypes())
if (StoreObjectIdentifier.Create(d, StoreObjectType.Table) is { } dt && dt != rootTable) return false;
}
return true;
} Try / catch
try { _ = context.Model; }
catch (InvalidOperationException ex) when (ex.Message.Contains("mapped to the table", StringComparison.Ordinal) && ex.Message.Contains("same table", StringComparison.Ordinal))
{
throw new InvalidOperationException("A TPH hierarchy must keep all types on one table. Remove the derived .ToTable or drop the discriminator for TPT.", ex);
} Prevention
- In TPH, never call .ToTable on derived types with a different name.
- If per-type tables are needed, remove the discriminator to switch to TPT.
- Add a model validation test asserting one table per TPH root.
When it happens
Trigger: Root has a discriminator property (TPH) but a derived type was configured with .ToTable("OtherTable"), so its StoreObjectIdentifier differs from the root's. Detected by comparing entityId != rootId inside ValidateTphMapping.
Common situations: Adding .ToTable to a single derived type in a TPH hierarchy; scaffolding then later setting a discriminator; copy-paste of TPT config into a TPH model.
Related errors
- Both '{entityType}' and '{otherEntityType}' are mapped to th
- Both '{entityType}' and '{otherEntityType}' are explicitly
- '{entityType}' is mapped to the view '{view}' while '{otherE
- '{entityType}' is mapped to the database function '{function
- '{entityType}' is mapped to the stored procedure '{sproc}' w
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/a26c1dd50aedfa71.
Report an issue: GitHub.