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

  1. Remove the HasParameter binding for the store-generated property and use HasResultColumn instead (with .ValueGeneratedOnAdd()).
  2. If you intend to send the value on insert, change BeforeSaveBehavior to Save: .Property(x => x.Foo).ValueGeneratedNever() or .Metadata.SetBeforeSaveBehavior(PropertySaveBehavior.Save).
  3. 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

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


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)