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

  1. Remove the per-derived .ToFunction so all types share the root function.
  2. Switch away from TPH (drop the discriminator) if each type genuinely needs its own function (TPT-style).
  3. 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

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


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