dotnet/efcore · error · InvalidOperationException

Current value parameter

Error message

Current value parameter '{parameter}' is not allowed on delete stored procedure '{sproc}'. Use HasOriginalValueParameter() instead.

What it means

Thrown on the current-value parameter path when StoreObjectType == DeleteStoredProcedure (RelationalModelValidator.cs:730-735). Delete sprocs operate on the entity's pre-save state, so only original-value parameters are valid; a current-value parameter on delete is rejected. Use HasOriginalValueParameter instead.

Solutions

  1. Use .HasOriginalValueParameter(x => x.Foo) in the delete sproc configuration.
  2. For the key specifically, ensure it's bound as an original-value parameter (the convention default already does this for the PK — verify you didn't override it).
  3. If you truly need the current value on delete (rare), reconsider the sproc design — EF delete mapping uses pre-save values.

Example fix

// before
modelBuilder.Entity<Customer>()
    .DeleteUsingStoredProcedure("dbo.CustomerDelete", sp =>
    {
        sp.HasParameter(c => c.Id); // throws — current value not allowed on delete
        sp.HasParameter(c => c.VersionTag);
    });

// after
modelBuilder.Entity<Customer>()
    .DeleteUsingStoredProcedure("dbo.CustomerDelete", sp =>
    {
        sp.HasOriginalValueParameter(c => c.Id);
        sp.HasOriginalValueParameter(c => c.VersionTag);
    });
Defensive patterns

Strategy: validation

Validate before calling

foreach (var et in modelBuilder.Model.GetEntityTypes())
{
    var delete = et.GetDeleteStoredProcedure();
    if (delete is null) continue;
    if (delete.Parameters.Any(p => p.ForOriginalValue != true && p.PropertyName is not null))
        throw new InvalidOperationException($"Delete sproc '{delete.GetStoreIdentifier().DisplayName}' on '{et.Name}' must not have current-value parameters; use HasOriginalValueParameter.");
}

Try / catch

try { _ = ctx.Model; }
catch (InvalidOperationException ex) when (ex.Message.Contains("not allowed on delete stored procedure"))
{
    logger.LogError("Use HasOriginalValueParameter on delete sprocs. {Msg}", ex.Message);
    throw;
}

Prevention

When it happens

Trigger: Calling .HasParameter(x => x.Foo) (current value) inside a .DeleteUsingStoredProcedure(... ) configuration. Usually copy-paste from insert/update sproc; misunderstanding that delete needs the original key/token, not the current value.

Common situations: Refactoring a shared sproc builder across all three CUD operations; pasting an update template into a delete sproc; using a concurrency token via HasParameter instead of HasOriginalValueParameter on delete.

Related errors


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

Appendix: source

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

                    if (storeObjectIdentifier.StoreObjectType == StoreObjectType.InsertStoredProcedure)
                    {
                        throw new InvalidOperationException(
                            RelationalStrings.StoredProcedureOriginalValueParameterOnInsert(
                                parameter.Name, storeObjectIdentifier.DisplayName()));
                    }
                }
                else
                {
                    if (!properties.TryGetAndRemove(parameter.PropertyName, out property))
                    {
                        throw new InvalidOperationException(
                            RelationalStrings.StoredProcedureParameterNotFound(
                                parameter.PropertyName, entityType.DisplayName(), storeObjectIdentifier.DisplayName()));
                    }

                    if (storeObjectIdentifier.StoreObjectType == StoreObjectType.DeleteStoredProcedure)
                    {
                        throw new InvalidOperationException(
                            RelationalStrings.StoredProcedureCurrentValueParameterOnDelete(
                                parameter.Name, storeObjectIdentifier.DisplayName()));
                    }

                    if (parameter.Direction.HasFlag(ParameterDirection.Input))
                    {
                        switch (storeObjectIdentifier.StoreObjectType)
                        {
                            case StoreObjectType.InsertStoredProcedure:
                                if (property.GetBeforeSaveBehavior() != PropertySaveBehavior.Save)
                                {
                                    throw new InvalidOperationException(
                                        RelationalStrings.StoredProcedureInputParameterForInsertNonSaveProperty(
                                            parameter.Name,
                                            storeObjectIdentifier.DisplayName(),
                                            parameter.PropertyName,
                                            entityType.DisplayName(),
                                            property.GetBeforeSaveBehavior()));

View on GitHub (pinned to 3a2006ef56)