dotnet/efcore · error · InvalidOperationException

Input parameter ' ' of update stored procedure ' ' is…

Error message

Input parameter '{parameter}' of update stored procedure '{sproc}' is mapped to property '{property}' of entity type '{entityType}', but that property is configured with AfterSaveBehavior '{behavior}', and so cannot be saved on update. You may need to use HasOriginalValueParameter() instead of HasParameter().

What it means

Thrown during model validation when an UPDATE stored procedure mapping declares an INPUT parameter bound to an entity property whose AfterSaveBehavior is not PropertySaveBehavior.Save. EF Core refuses to send a value the model says is read-only after the entity exists, because the update sproc parameter would imply the property is writable. The validator guards the HasParameter() mapping call against the property's after-save contract.

Solutions

  1. Remove the offending HasParameter(p => p.Foo) call from the UpdateUsingStoredProcedure configuration since the property is not writable on update.
  2. If EF should pass the original DB value for concurrency, use HasOriginalValueParameter(p => p.Foo) instead of HasParameter(p => p.Foo).
  3. If the property genuinely must be written on update, set its after-save behavior to Save: builder.Property(p => p.Foo).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save) (or remove the .ValueGeneratedOnAdd()/AfterSaveBehavior.Ignore that suppressed it).
  4. Move that property to an output parameter / result column mapping if the database regenerates it on update.

Example fix

// before
entityType.UpdateUsingStoredProcedure("UpdateCustomer",
    sp =>
    {
        sp.HasParameter(c => c.CreatedOn); // CreatedOn is ValueGeneratedOnAdd
    });

// after
entityType.UpdateUsingStoredProcedure("UpdateCustomer",
    sp =>
    {
        // CreatedOn removed; it is not writable on update
        sp.HasParameter(c => c.Name);
    });
Defensive patterns

Strategy: validation

Validate before calling

// Before finalizing the model, verify each UPDATE sproc input parameter
// maps to a Save-after-save property.
foreach (var et in model.GetEntityTypes())
{
    var updateSproc = et.GetUpdateStoredProcedure();
    if (updateSproc == null) continue;
    foreach (var p in updateSproc.Parameters)
    {
        if (p.Direction.HasFlag(System.Data.ParameterDirection.Input)
            && p.PropertyName is string name)
        {
            var prop = et.FindProperty(name);
            if (prop?.GetAfterSaveBehavior() != Microsoft.EntityFrameworkCore.Metadata.PropertySaveBehavior.Save)
            {
                Console.WriteLine($"{et.Name}.{name} is not Save on update; remove or use HasOriginalValueParameter.");
            }
        }
    }
}

Prevention

When it happens

Trigger: Calling entityType.UpdateUsingStoredProcedure(...).HasParameter(p => p.Foo) where Foo has AfterSaveBehavior != Save (e.g. .ValueGeneratedOnAdd(), .Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Ignore), a store-generated computed column, or a property marked read-only after save). Surfaces when the model is finalized (EnsureModelFinalized / first query / SaveChanges) and validator runs.

Common situations: Marking an audit/created-on column or a computed column as input parameter on an update sproc; mapping a property that should only be written on INSERT; switching a column to ValueGeneratedOnAdd but forgetting to drop it from the update sproc parameters; using the same HasParameter call for insert and update sproc configs.

Related errors


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

Appendix: source

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

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

                                break;

                            case StoreObjectType.UpdateStoredProcedure:
                                if (property.GetAfterSaveBehavior() != PropertySaveBehavior.Save)
                                {
                                    throw new InvalidOperationException(
                                        RelationalStrings.StoredProcedureInputParameterForUpdateNonSaveProperty(
                                            parameter.Name,
                                            storeObjectIdentifier.DisplayName(),
                                            parameter.PropertyName,
                                            entityType.DisplayName(),
                                            property.GetAfterSaveBehavior()));
                                }

                                break;

                            case StoreObjectType.DeleteStoredProcedure:
                                break;

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

View on GitHub (pinned to 3a2006ef56)