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
- Use .HasOriginalValueParameter(x => x.Foo) in the delete sproc configuration.
- 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).
- 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
- Bind only original-value parameters (and unbound value parameters) in DeleteUsingStoredProcedure.
- For the PK on delete, rely on the convention default (original value) — don't override it with HasParameter.
- Treat delete sproc config as distinct from insert/update.
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
- Input parameter ' ' of insert stored procedure ' ' is…
- No property named ' ' found on the entity type ' '…
- Original value parameter
- The property ' . ' is mapped to a result column of the…
- Both entity type ' ' and ' ' were configured to use ' '…
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)