dotnet/efcore · error · InvalidOperationException
The entity type ' ' cannot be instantiated because its…
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/SQL query) under the TPC (Table-Per-Concrete-Type) mapping strategy. TPC generates one table per concrete type and inserts/queries instances of it, so an abstract type with a store object would be unreachable at runtime yet create a phantom table. EF rejects this at model validation.
Solutions
- Remove the ToTable/ToView/ToFunction/ToSqlQuery mapping from the abstract base type so it has no store object.
- Make the CLR type concrete (non-abstract) if it genuinely needs its own table under TPC.
- Move the store mapping to each concrete derived type instead of the abstract base.
Example fix
// before
public abstract class Animal { public int Id { get; set; } }
modelBuilder.Entity<Animal>().ToTable("Animals").UseTpcMappingStrategy();
// after - abstract root has no table under TPC
modelBuilder.Entity<Animal>().UseTpcMappingStrategy();
modelBuilder.Entity<Dog>().ToTable("Dogs");
modelBuilder.Entity<Cat>().ToTable("Cats"); Defensive patterns
Strategy: validation
Validate before calling
// For each TPC entity, ensure abstract types have no store object.
foreach (var et in db.Model.GetEntityTypes())
{
var strategy = et[RelationalAnnotationNames.MappingStrategy] as string;
if (strategy != RelationalAnnotationNames.TpcMappingStrategy) continue;
if (et.ClrType.IsAbstract)
{
var store = et.GetSchemaQualifiedTableName() ?? et.GetSchemaQualifiedViewName() ?? et.GetFunctionName() ?? et.GetSqlQuery();
if (store != null) Console.WriteLine($"Abstract TPC type {et.Name} maps to {store}");
}
} Type guard
static bool IsInstantiable(Type t) => !t.IsAbstract && !t.IsGenericTypeDefinition && !(t.IsGenericType && t.GetGenericArguments().Any(a => a.IsGenericParameter)) && t.GetConstructor(Type.EmptyTypes) != null || !t.IsAbstract;
Try / catch
try { _ = db.Model; } catch (InvalidOperationException ex) when (ex.Message.Contains("cannot be instantiated because its corresponding CLR type is abstract") && ex.Message.Contains("TPC")) { // remove the store mapping from the abstract type or make it concrete } Prevention
- Under TPC, never call ToTable/ToView on an abstract base type.
- When switching a hierarchy to TPC, audit the previous root's mappings.
- Add a startup model-build test for any TPC hierarchy.
When it happens
Trigger: ValidateInheritanceMapping / ValidateMappingStrategy checks mappingStrategy == TpcMappingStrategy, !entityType.ClrType.IsInstantiable(), and a non-null store object (table/view/function/query/sproc); throws RelationalStrings.AbstractTpc. Triggered when an abstract base class in a TPC hierarchy has ToTable/ToView/etc.
Common situations: Marking the base class 'abstract' but leaving ToTable("Bases") on it after switching to TPC; introducing an abstract intermediate type and not removing its mapping; TPC migration from TPH where the root entity had a table.
Related errors
- The derived entity type
- The element type of the result of
- Both ' ' and ' ' are mapped to the table ' '. All the…
- Both ' ' and ' ' are mapped to the stored procedure ' '…
- Both ' ' and ' ' are mapped to the view ' '. All the entity…
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/3308cfbd66ddfb4d.
Report an issue: GitHub.
Appendix: 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 3a2006ef56)