dotnet/efcore · error · InvalidOperationException

The keyless entity type

Error message

The keyless entity type '{entityType}' was configured to use '{sproc}'. An entity type requires a primary key to be able to be mapped to a stored procedure.

What it means

Thrown at the start of ValidateSproc when the entity type has no primary key (FindPrimaryKey() returns null) but is mapped to a stored procedure (RelationalModelValidator.cs:570-576). Stored procedures require a PK so EF can populate key parameters and result columns for identity/computed values. Keyless types (HasNoKey, query types, many-to-many join payloads without PK) cannot use sproc mapping.

Solutions

  1. Define a primary key on the entity: modelBuilder.Entity<T>().HasKey(x => x.Id); (or via [Key] / KeyAttribute).
  2. Remove the stored-procedure configuration from genuinely keyless types — keyless types are read-only by design.
  3. If the type was wrongly marked HasNoKey, remove that call so the convention picks up the key.

Example fix

// before
modelBuilder.Entity<SearchIndex>()
    .HasNoKey()
    .ToView("vw_Search")
    .InsertUsingStoredProcedure("sp_SearchUpsert", sp => sp.HasParameter(s => s.Term));

// after — give it a key, or drop the sproc
modelBuilder.Entity<SearchIndex>()
    .HasKey(s => s.Term);
modelBuilder.Entity<SearchIndex>()
    .ToTable("Search")
    .InsertUsingStoredProcedure("sp_SearchUpsert", sp => sp.HasParameter(s => s.Term));
Defensive patterns

Strategy: validation

Validate before calling

foreach (var et in modelBuilder.Model.GetEntityTypes())
{
    bool anySproc = et.GetInsertStoredProcedure() is not null
                 || et.GetUpdateStoredProcedure() is not null
                 || et.GetDeleteStoredProcedure() is not null;
    if (anySproc && et.FindPrimaryKey() is null)
        throw new InvalidOperationException($"Entity '{et.Name}' has sproc mapping but no primary key; declare a key or remove the sproc config.");
}

Try / catch

try { _ = ctx.Model; }
catch (InvalidOperationException ex) when (ex.Message.Contains("requires a primary key"))
{
    logger.LogError("Keyless entity cannot use sproc mapping. {Msg}", ex.Message);
    throw;
}

Prevention

When it happens

Trigger: Configuring .InsertUsingStoredProcedure on a HasNoKey() entity; on a keyless query type; on a view-backed read model. Also on a type whose key was removed via .HasNoKey() after a refactor.

Common situations: Migrating a query type to an entity but forgetting to declare a key; scaffolding a view as keyless and then trying to add CUD sprocs; misapplying HasNoKey on a real entity.

Related errors


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

Appendix: source

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

                        mappedTypes[0].DisplayName(),
                        mappedType.DisplayName(),
                        storedProcedure.DisplayName()));
            }
        }
    }

    private static void ValidateSproc(
        IStoredProcedure sproc,
        string mappingStrategy,
        IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
    {
        var entityType = sproc.EntityType;
        var storeObjectIdentifier = sproc.GetStoreIdentifier();

        var primaryKey = entityType.FindPrimaryKey();
        if (primaryKey == null)
        {
            throw new InvalidOperationException(
                RelationalStrings.StoredProcedureKeyless(
                    entityType.DisplayName(), storeObjectIdentifier.DisplayName()));
        }

        var properties = entityType.GetDeclaredProperties().ToDictionary(p => p.Name);
        if (mappingStrategy == RelationalAnnotationNames.TphMappingStrategy)
        {
            if (entityType.BaseType != null)
            {
                return;
            }

            foreach (var property in entityType.GetDerivedProperties())
            {
                properties.Add(property.Name, property);
            }
        }
        else if (mappingStrategy == RelationalAnnotationNames.TpcMappingStrategy)

View on GitHub (pinned to 3a2006ef56)