dotnet/efcore · error · InvalidOperationException

The mapping strategy

Error message

The mapping strategy '{mappingStrategy}' used for '{entityType}' is not supported for keyless entity types.  See https://go.microsoft.com/fwlink/?linkid=2130430 for more information.

What it means

Thrown during hierarchy validation when an entity type has no primary key (is keyless), has one or more derived types, and the hierarchy is not using the TPC mapping strategy. TPT/TPH inheritance requires each type to have a primary key so the rows can be linked/resolved, so a keyless type in a non-TPC hierarchy is illegal. Note the message defaults the strategy to TPT when it is null.

Solutions

  1. Define a primary key on the entity with HasKey(...) or a [Key] attribute.
  2. If the type must stay keyless, use TPC: modelBuilder.UseTpcMappingStrategy() — the only strategy that tolerates keyless types in a hierarchy.
  3. Remove the inheritance relationship (make the derived types standalone) if a key is genuinely impossible.

Example fix

// before
modelBuilder.Entity<KeylessBase>().HasNoKey();
modelBuilder.Entity<Derived : KeylessBase>();
// default TPH strategy -> throws 421

// after (add a key)
modelBuilder.Entity<KeylessBase>().HasKey(x => x.Id);
// or use TPC
// modelBuilder.UseTpcMappingStrategy();
Defensive patterns

Strategy: validation

Validate before calling

foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
    var strategy = entityType.GetMappingStrategy();
    var isKeyless = entityType.FindPrimaryKey() == null;
    var hasDerived = entityType.GetDirectlyDerivedTypes().Any();
    if (isKeyless && hasDerived
        && strategy != RelationalAnnotationNames.TpcMappingStrategy)
    {
        throw new InvalidOperationException($"{entityType.DisplayName()} is keyless with derived types; add a key or use TPC.");
    }
}

Prevention

When it happens

Trigger: A keyless entity type (HasNoKey) that is the base of an inheritance hierarchy while the mapping strategy is TPH (default) or TPT, i.e. FindPrimaryKey() == null && GetDirectlyDerivedTypes().Any() && strategy != TPC.

Common situations: Using a keyless query result type as a base for other types; forgetting a [Key] or HasKey on the base of a hierarchy; mixing owned/keyless concepts with inheritance; switching a hierarchy to include a keyless view-backed type.

Related errors


AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11). Data as JSON: /api/errors/e63649aab6e86d07. Report an issue: GitHub.

Appendix: source

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

                    RelationalStrings.NonTphMappingStrategy(mappingStrategy, entityType.DisplayName()));
            }

            ValidateTphMapping(entityType, StoreObjectType.Table);
            ValidateTphMapping(entityType, StoreObjectType.View);
            ValidateTphMapping(entityType, StoreObjectType.Function);
            ValidateTphMapping(entityType, StoreObjectType.InsertStoredProcedure);
            ValidateTphMapping(entityType, StoreObjectType.DeleteStoredProcedure);
            ValidateTphMapping(entityType, StoreObjectType.UpdateStoredProcedure);

            ValidateDiscriminatorValues(entityType);
        }
        else
        {
            if (mappingStrategy != RelationalAnnotationNames.TpcMappingStrategy
                && entityType.FindPrimaryKey() == null
                && entityType.GetDirectlyDerivedTypes().Any())
            {
                throw new InvalidOperationException(
                    RelationalStrings.KeylessMappingStrategy(
                        mappingStrategy ?? RelationalAnnotationNames.TptMappingStrategy, entityType.DisplayName()));
            }

            ValidateNonTphMapping(entityType, StoreObjectType.Table);
            ValidateNonTphMapping(entityType, StoreObjectType.View);
            ValidateNonTphMapping(entityType, StoreObjectType.InsertStoredProcedure);
            ValidateNonTphMapping(entityType, StoreObjectType.DeleteStoredProcedure);
            ValidateNonTphMapping(entityType, StoreObjectType.UpdateStoredProcedure);

            var derivedTypes = entityType.GetDerivedTypesInclusive().ToList();
            var discriminatorValues = new Dictionary<string, IEntityType>();
            foreach (var derivedType in derivedTypes)
            {
                foreach (var complexProperty in derivedType.GetDeclaredComplexProperties())
                {
                    ValidateDiscriminatorValues(complexProperty.ComplexType);
                }

View on GitHub (pinned to 3a2006ef56)