dotnet/efcore · error · InvalidOperationException

The entity type '{entityType}' cannot be instantiated becaus

Error message

The entity type '{entityType}' cannot be instantiated because its corresponding CLR type is abstract, but the entity type was mapped to '{storeObject}' using the 'TPC' mapping strategy. Only instantiable types should be mapped. See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.

What it means

An entity type whose CLR type is abstract (cannot be instantiated) was mapped to a concrete store object (table/view/function/stored procedure) using the TPC (Table-per-Concrete-type) mapping strategy. TPC gives each type its own table, so abstract types - which can never be queried or inserted as instances - must not be mapped to a store object. Thrown from ValidateInheritanceMapping when mappingStrategy == Tpc and the CLR type is not instantiable.

Source

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

        IEntityType entityType,
        IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
    {
        var mappingStrategy = (string?)entityType[RelationalAnnotationNames.MappingStrategy];
        if (mappingStrategy != null)
        {
            ValidateMappingStrategy(entityType, mappingStrategy);
            var storeObjectName = entityType.GetSchemaQualifiedTableName()
                ?? entityType.GetSchemaQualifiedViewName()
                ?? entityType.GetFunctionName()
                ?? entityType.GetSqlQuery()
                ?? entityType.GetInsertStoredProcedure()?.GetSchemaQualifiedName()
                ?? entityType.GetDeleteStoredProcedure()?.GetSchemaQualifiedName()
                ?? entityType.GetUpdateStoredProcedure()?.GetSchemaQualifiedName();
            if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy
                && !entityType.ClrType.IsInstantiable()
                && storeObjectName != null)
            {
                throw new InvalidOperationException(
                    RelationalStrings.AbstractTpc(entityType.DisplayName(), storeObjectName));
            }
        }

        if (entityType.BaseType != null)
        {
            if (mappingStrategy != null
                && mappingStrategy != (string?)entityType.BaseType[RelationalAnnotationNames.MappingStrategy])
            {
                throw new InvalidOperationException(
                    RelationalStrings.DerivedStrategy(entityType.DisplayName(), mappingStrategy));
            }

            return;
        }

        // Hierarchy mapping strategy must be the same across all types of mappings (only for root types)
        if (entityType.FindDiscriminatorProperty() != null)

View on GitHub (pinned to dbf9771522)

Solutions

  1. Remove the table/view/function mapping from the abstract base (do not call ToTable on it; in TPC the base should not be a store object).
  2. If the base must participate, make its CLR class concrete (sealed or non-abstract), though usually the right fix is to leave it abstract and unmapped.
  3. Confirm UseTpcMappingStrategy is only declared on the concrete root or each concrete derived type as supported by the provider.

Example fix

// before
public abstract class Animal { public int Id { get; set; } }
modelBuilder.Entity<Animal>().ToTable("Animals").UseTpcMappingStrategy();
// after (abstract base is not mapped to a store object in TPC)
public abstract class Animal { public int Id { get; set; } }
modelBuilder.Entity<Animal>().UseTpcMappingStrategy();
// 'Animals' table is removed; only Cat/Dog tables exist
Defensive patterns

Strategy: validation

Validate before calling

using (var ctx = new MyContext()) { ctx.Model.ToDebugString(); }
// Assert that no abstract entity in a TPC hierarchy is mapped to a store object.
foreach (var et in ctx.Model.GetEntityTypes())
{
    var strategy = et.GetMappingStrategy();
    if (strategy == "TPC" && et.ClrType.IsAbstract)
    {
        var table = et.GetSchemaQualifiedTableName() ?? et.GetSchemaQualifiedViewName();
        Debug.Assert(table == null, $"Abstract type {et.Name} is mapped to '{table}' under TPC.");
    }
}

Type guard

static bool IsInstantiable(Type t) => !t.IsAbstract && !t.IsGenericTypeDefinition && !t.IsInterface;
// Guard: do not call .Entity<T>().ToTable(...) when IsInstantiable(typeof(T)) is false in TPC.

Try / catch

try { using var ctx = new MyContext(); _ = ctx.Model; }
catch (InvalidOperationException ex) when (ex.Message.Contains("abstract") && ex.Message.Contains("TPC"))
{ log.Error("Abstract type mapped in TPC; remove its ToTable mapping: {Msg}", ex.Message); throw; }

Prevention

When it happens

Trigger: Calling modelBuilder.Entity<AbstractBase>().UseTpcMappingStrategy() where AbstractBase is an abstract C# class that also resolves to a table (e.g. via ToTable or default convention); configuring a TPC hierarchy root whose base class is abstract and ends up with a table mapping.

Common situations: Migrating from TPH/TPC and forgetting to mark the abstract base as unmapped; convention-based ToTable on an abstract root in a TPC hierarchy; refactoring a base class to abstract without removing its table mapping.

Related errors


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