dotnet/efcore · error · InvalidOperationException
Input parameter ' ' of insert stored procedure ' ' is…
Error message
Input parameter '{parameter}' of insert stored procedure '{sproc}' is mapped to property '{property}' of entity type '{entityType}', but that property is configured with BeforeSaveBehavior '{behavior}', and so cannot be saved on insert. What it means
Thrown in the input-parameter switch for Insert sprocs when the bound property's GetBeforeSaveBehavior() != PropertySaveBehavior.Save (RelationalModelValidator.cs:741-751). Insert input parameters must map to properties that EF will actually save on insert; a property configured with BeforeSaveBehavior.Throw or Ignore cannot be passed as an insert parameter because the value wouldn't be written.
Solutions
- Remove the HasParameter binding for the store-generated property and use HasResultColumn instead (with .ValueGeneratedOnAdd()).
- If you intend to send the value on insert, change BeforeSaveBehavior to Save: .Property(x => x.Foo).ValueGeneratedNever() or .Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Save).
- For an identity PK, let the DB generate it and read it back as a result column rather than as an input parameter.
Example fix
// before — Id is identity but passed as input
modelBuilder.Entity<Customer>(b =>
{
b.Property(c => c.Id).ValueGeneratedOnAdd(); // BeforeSaveBehavior = Ignore
b.InsertUsingStoredProcedure("dbo.CustomerInsert", sp =>
{
sp.HasParameter(c => c.Id); // throws
sp.HasParameter(c => c.Name);
});
});
// after
modelBuilder.Entity<Customer>(b =>
{
b.Property(c => c.Id).ValueGeneratedOnAdd();
b.InsertUsingStoredProcedure("dbo.CustomerInsert", sp =>
{
sp.HasParameter(c => c.Name);
sp.HasResultColumn(c => c.Id); // read generated value back
});
}); Defensive patterns
Strategy: validation
Validate before calling
foreach (var et in modelBuilder.Model.GetEntityTypes())
{
var insert = et.GetInsertStoredProcedure();
if (insert is null) continue;
foreach (var p in insert.Parameters)
{
if (p.ForOriginalValue == true) continue;
if (!p.Direction.HasFlag(ParameterDirection.Input)) continue;
if (p.PropertyName is null) continue;
var prop = et.FindProperty(p.PropertyName);
if (prop is not null && prop.GetBeforeSaveBehavior() != PropertySaveBehavior.Save)
throw new InvalidOperationException($"Insert sproc input parameter '{p.Name}' maps to '{et.Name}.{p.PropertyName}' whose BeforeSaveBehavior is {prop.GetBeforeSaveBehavior()} (not Save).");
}
} Try / catch
try { _ = ctx.Model; }
catch (InvalidOperationException ex) when (ex.Message.Contains("cannot be saved on insert"))
{
logger.LogError("Insert input parameter maps to a non-savable property. {Msg}", ex.Message);
throw;
} Prevention
- For identity/computed columns, use HasResultColumn + ValueGeneratedOnAdd instead of HasParameter on insert.
- Set BeforeSaveBehavior to Save only for properties you genuinely write on insert.
- Audit insert sproc configs after changing a property's ValueGenerated setting.
When it happens
Trigger: Marking a property .ValueGeneratedOnAdd()/.ValueGeneratedOnAddOrUpdate() (which sets BeforeSaveBehavior to Ignore) and then binding it as an input parameter on the insert sproc via .HasParameter(x => ...). Or explicitly setting .Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Throw).
Common situations: Treating an identity/computed column as both input parameter and result column; setting a column to DB-generated and still passing a value in the insert sproc; scaffolding where computed columns were accidentally wired as input parameters.
Related errors
- Original value parameter
- Current value parameter
- No property named ' ' found on the entity type ' '…
- 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/809020e0b0b4b2b2.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.Relational/Infrastructure/RelationalModelValidator.cs:744
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()));
}
break;
case StoreObjectType.UpdateStoredProcedure:
if (property.GetAfterSaveBehavior() != PropertySaveBehavior.Save)
{
throw new InvalidOperationException(
RelationalStrings.StoredProcedureInputParameterForUpdateNonSaveProperty(
parameter.Name,
storeObjectIdentifier.DisplayName(),
parameter.PropertyName,View on GitHub (pinned to 3a2006ef56)