dotnet/efcore · error · InvalidOperationException

The property '{entityType}.{property}' is mapped to a parame

Error message

The property '{entityType}.{property}' is mapped to a parameter of the stored procedure '{sproc}', but only concurrency token and key properties are supported for Delete stored procedures.

What it means

A Delete stored procedure may only receive key and concurrency-token properties as parameters, because deletion identifies the row by key (and optionally checks a concurrency token for optimistic concurrency). The validator at line 814-821 throws when a Delete sproc parameter maps to a property that is neither part of the primary key nor marked as a concurrency token.

Source

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

                                && p.ForOriginalValue != parameter.ForOriginalValue
                                && p.Direction != ParameterDirection.Input))
                        {
                            throw new InvalidOperationException(
                                RelationalStrings.StoredProcedureOutputParameterConflict(
                                    entityType.DisplayName(), parameter.PropertyName, storeObjectIdentifier.DisplayName()));
                        }

                        throw new InvalidOperationException(
                            RelationalStrings.StoredProcedureOutputParameterNotGenerated(
                                entityType.DisplayName(), parameter.PropertyName, storeObjectIdentifier.DisplayName()));
                    }

                    break;
                case StoreObjectType.DeleteStoredProcedure:
                    if (!property!.IsPrimaryKey()
                        && !property.IsConcurrencyToken)
                    {
                        throw new InvalidOperationException(
                            RelationalStrings.StoredProcedureDeleteNonKeyProperty(
                                entityType.DisplayName(), parameter.PropertyName, storeObjectIdentifier.DisplayName()));
                    }

                    break;
                default:
                    Check.DebugFail("Unexpected stored procedure type: " + storeObjectIdentifier.StoreObjectType);
                    break;
            }
        }

        foreach (var resultColumn in sproc.ResultColumns)
        {
            if (resultColumn.PropertyName == null)
            {
                continue;
            }

View on GitHub (pinned to dbf9771522)

Solutions

  1. Remove the non-key/non-token parameters from the Delete sproc mapping so only PK and concurrency-token columns are sent.
  2. If the sproc genuinely needs the extra column, move that logic into the key/concurrency check inside the sproc itself (e.g. compose the PK to include it) — but the EF mapping must only reference key/token properties.
  3. If you need to delete using non-key criteria, use a raw SQL command or ExecuteDelete instead of a mapped delete sproc.

Example fix

// before
modelBuilder.Entity<Order>()
    .DeleteStoredProcedure(o => o
        .Parameter(p => p.Id, "id")
        .Parameter(p => p.CustomerId, "cust_id")); // CustomerId is not a key/token

// after
modelBuilder.Entity<Order>()
    .DeleteStoredProcedure(o => o
        .Parameter(p => p.Id, "id")
        .OriginalValueParameter(p => p.RowVersion, "rowver")); // concurrency token only
Defensive patterns

Strategy: validation

Validate before calling

foreach (var et in modelBuilder.Model.GetEntityTypes())
{
    var deleteSproc = et.GetStoredProcedures()
        .FirstOrDefault(s => s.StoreObjectType == StoreObjectType.DeleteStoredProcedure);
    if (deleteSproc == null) continue;
    foreach (var param in deleteSproc.Parameters)
    {
        if (param.PropertyName is not string name) continue;
        var prop = et.FindProperty(name);
        if (prop != null && !prop.IsPrimaryKey() && !prop.IsConcurrencyToken)
        {
            throw new InvalidOperationException(
                $"Delete sproc param '{param.Name}' maps non-key/non-token property '{name}'.");
        }
    }
}

Prevention

When it happens

Trigger: Configuring `.DeleteStoredProcedure(...)` and mapping a regular non-key property via `.Parameter(...)` (e.g. `.Parameter(p => p.Name, "name")`). The validator iterates each parameter and rejects any whose property fails `IsPrimaryKey()` and `IsConcurrencyToken`.

Common situations: Porting an existing `DELETE` sproc that takes extra WHERE columns into EF without trimming the parameter list; assuming EF will send full entity state to a delete sproc like it does for update.

Related errors


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