dotnet/efcore · error · InvalidOperationException

Both '{entityType}' and '{otherEntityType}' are mapped to th

Error message

Both '{entityType}' and '{otherEntityType}' are mapped to the view '{view}'. All the entity types in a non-TPH hierarchy (one that doesn't have a discriminator) must be mapped to different views. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.

What it means

Same collision check as NonTphTableClash but for StoreObjectType.View: two types in a non-TPH (discriminator-less) hierarchy both resolve to the same view. Without a discriminator EF cannot distinguish rows, so the model is rejected.

Source

Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:2245

                            entityType.DisplayName(),
                            unmappedOwnedType.FindOwnership()!.PrincipalToDependent?.Name,
                            unmappedOwnedType.DisplayName(),
                            storeObjectType));
                }

                continue;
            }

            if (derivedTypes.TryGetValue(storeObject.Value, out var otherType))
            {
                switch (storeObjectType)
                {
                    case StoreObjectType.Table:
                        throw new InvalidOperationException(
                            RelationalStrings.NonTphTableClash(
                                entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));
                    case StoreObjectType.View:
                        throw new InvalidOperationException(
                            RelationalStrings.NonTphViewClash(
                                entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));
                    case StoreObjectType.InsertStoredProcedure:
                    case StoreObjectType.DeleteStoredProcedure:
                    case StoreObjectType.UpdateStoredProcedure:
                        throw new InvalidOperationException(
                            RelationalStrings.NonTphStoredProcedureClash(
                                entityType.DisplayName(), otherType.DisplayName(), storeObject.Value.DisplayName()));
                }
            }

            if (isTpc)
            {
                var rowInternalFk = entityType.FindDeclaredReferencingRowInternalForeignKeys(storeObject.Value)
                    .FirstOrDefault();
                if (rowInternalFk != null
                    && entityType.GetDirectlyDerivedTypes().Any())
                {

View on GitHub (pinned to dbf9771522)

Solutions

  1. Map each derived type to its own view: modelBuilder.Entity<DerivedA>().ToView("v_DerivedA").
  2. If one shared view is intended, switch the hierarchy to TPH (set a discriminator) and keep all types on the same view.
  3. Drop the ToView call on the derived type and rely on the default per-type view name.

Example fix

// before
modelBuilder.Entity<Manager>().ToView("v_People");
modelBuilder.Entity<Employee>().ToView("v_People");

// after
modelBuilder.Entity<Manager>().ToView("v_Managers");
modelBuilder.Entity<Employee>().ToView("v_Employees");
Defensive patterns

Strategy: validation

Validate before calling

bool NoNonTphViewClash(DbContext context)
{
    foreach (var root in context.Model.GetEntityTypes()
        .Where(e => e.BaseType == null && e.FindDiscriminatorProperty() == null && e.GetDerivedTypes().Any()))
    {
        var names = new HashSet<StoreObjectIdentifier>();
        foreach (var et in root.GetDerivedTypesInclusive())
        {
            var so = StoreObjectIdentifier.Create(et, StoreObjectType.View);
            if (so.HasValue && !names.Add(so.Value)) return false;
        }
    }
    return true;
}

Try / catch

try { _ = context.Model; }
catch (InvalidOperationException ex) when (ex.Message.Contains("non-TPH hierarchy", StringComparison.Ordinal) && ex.Message.Contains("view '", StringComparison.Ordinal))
{
    throw new InvalidOperationException("Two types in a TPT/TPC hierarchy share a view. Give each a distinct ToView, or switch to TPH.", ex);
}

Prevention

When it happens

Trigger: Two non-TPH sibling types both call ToView("V") (or inherit the same view name) and collide in the derivedTypes dictionary during ValidateNonTphMapping(..., StoreObjectType.View).

Common situations: Read-only query scenarios mapping a hierarchy to a single shared view; copy-pasting ToView across derived types; scaffolding views without renaming per type.

Related errors


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