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

  1. Remove the .ToTable call from the derived type so it inherits the root's table.
  2. If you want per-type tables, remove the discriminator property to move to TPT, then give each type its own table.
  3. 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

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


AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06). Data as JSON: /api/errors/a26c1dd50aedfa71. Report an issue: GitHub.