dotnet/efcore · error · InvalidOperationException

Input parameter '{parameter}' of update stored procedure '{s

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 when an update input parameter is mapped to a property whose AfterSaveBehavior is not Save. Read-only-after-save properties (e.g. computed or throw-on-update) cannot be persisted during an update. If the original value is what you need (for a concurrency token), use HasOriginalValueParameter instead. The validator requires AfterSaveBehavior == Save for update input parameters.

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 dbf9771522)

Solutions

  1. Set the property's AfterSaveBehavior to Save if it should be writable on update.
  2. Use HasOriginalValueParameter if you want the original (pre-update) value, e.g. for a concurrency token.
  3. Remove the input parameter if the property should not be saved on update.

Example fix

// before
modelBuilder.Entity<Blog>().Property(b => b.CreatedAt).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Throw);
sp.HasParameter("CreatedAt"); // update input param

// after
sp.HasOriginalValueParameter("CreatedAt");
// or, if it should be writable:
modelBuilder.Entity<Blog>().Property(b => b.CreatedAt).Metadata.SetAfterSaveBehavior(PropertySaveBehavior.Save);
Defensive patterns

Strategy: validation

Validate before calling

foreach (var et in modelBuilder.Model.GetEntityTypes())
{
    var upd = et.GetUpdateStoredProcedure();
    if (upd == null) continue;
    foreach (var p in upd.Parameters.Where(p => p.PropertyName != null && p.ForOriginalValue != true && p.Direction.HasFlag(System.Data.ParameterDirection.Input)))
    {
        var prop = et.FindProperty(p.PropertyName);
        if (prop != null && prop.GetAfterSaveBehavior() != PropertySaveBehavior.Save)
            throw new InvalidOperationException($"{p.Name} maps to non-Save AfterSaveBehavior property");
    }
}

Try / catch

try { _ = ctx.Model; } catch (InvalidOperationException ex) when (ex.Message.Contains("cannot be saved on update")) { /* set AfterSaveBehavior.Save or switch to HasOriginalValueParameter */ }

Prevention

When it happens

Trigger: Mapping a property with .AfterSave(PropertySaveBehavior.Throw/Ignore) to an update input parameter; binding a concurrency token as a current-value param when its original value is needed.

Common situations: Marking a property read-only but still binding it as an update param; intending original-value semantics but using HasParameter.

Related errors


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