dotnet/efcore · error · InvalidOperationException
'{entityType}' is mapped to the database function '{function
Error message
'{entityType}' is mapped to the database function '{function}' while '{otherEntityType}' is mapped to the database function '{otherFunction}'. Map all the entity types in the hierarchy to the same database function. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information. What it means
ValidateTphMapping for StoreObjectType.Function: a TPH hierarchy mapped to a database function must keep all types on the same function. If a derived type resolves to a different function StoreObjectIdentifier than the root, EF throws because it cannot unify query roots across functions.
Source
Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:2340
}
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(
entityType.DisplayName(), entityId.Value.DisplayName(),
rootEntityType.DisplayName(), rootId?.DisplayName()));
}
}
}
/// <inheritdoc />
protected override bool IsRedundant(IForeignKey foreignKey)
=> base.IsRedundant(foreignKey)
&& !foreignKey.DeclaringEntityType.GetMappingFragments().Any();View on GitHub (pinned to dbf9771522)
Solutions
- Remove the per-derived .ToFunction so all types share the root function.
- Switch away from TPH (drop the discriminator) if each type genuinely needs its own function (TPT-style).
- Map the derived type to the SAME function name/schema as the root.
Example fix
// before
modelBuilder.Entity<Base>().HasDiscriminator(b => b.Kind).ToFunction("fn_Base");
modelBuilder.Entity<Derived>().ToFunction("fn_Derived");
// after
modelBuilder.Entity<Base>().HasDiscriminator(b => b.Kind).ToFunction("fn_Base");
// derived omitted, inherits root function Defensive patterns
Strategy: validation
Validate before calling
bool TphAllTypesSameFunction(DbContext context)
{
foreach (var root in context.Model.GetEntityTypes()
.Where(e => e.BaseType == null && e.FindDiscriminatorProperty() != null && e.GetDerivedTypes().Any()))
{
var rootFn = StoreObjectIdentifier.Create(root, StoreObjectType.Function);
if (rootFn is null) continue;
foreach (var d in root.GetDerivedTypes())
if (StoreObjectIdentifier.Create(d, StoreObjectType.Function) is { } df && df != rootFn) return false;
}
return true;
} Try / catch
try { _ = context.Model; }
catch (InvalidOperationException ex) when (ex.Message.Contains("database function", StringComparison.Ordinal) && ex.Message.Contains("same database function", StringComparison.Ordinal))
{
throw new InvalidOperationException("A TPH hierarchy must map all types to one database function. Remove the derived .ToFunction or drop the discriminator.", ex);
} Prevention
- In TPH, map only the root to a function.
- Switch to TPT/TPC if per-type functions are required.
- Validate the built model in tests.
When it happens
Trigger: Root has a discriminator (TPH) and is mapped via .ToFunction("fn_Base"), and a derived type is mapped via .ToFunction("fn_Derived") (or otherwise resolves to a different function), so entityId != rootId.
Common situations: Query-only TPH models backed by TVFs where a derived type was separately mapped; scaffolding functions then adding a discriminator.
Related errors
- Both '{entityType}' and '{otherEntityType}' are explicitly
- '{entityType}' is mapped to the table '{table}' while '{othe
- '{entityType}' is mapped to the view '{view}' while '{otherE
- '{entityType}' is mapped to the stored procedure '{sproc}' w
- The short name for '{entityType1}' is '{discriminatorValue}'
AI-assisted analysis of dotnet/efcore@dbf9771522 (2026-08-06).
Data as JSON: /api/errors/fe27248a73177bb1.
Report an issue: GitHub.