dotnet/efcore · error · InvalidOperationException

The rows affected parameter cannot be added to the stored…

Error message

The rows affected parameter cannot be added to the stored procedure '{sproc}' because the rows affected are already returned via another parameter, via the stored procedure return value or via a result column.

What it means

EF Core forbids a stored procedure from returning the rows-affected count through more than one channel. AddRowsAffectedParameter() throws when rows affected is already sourced from a parameter, a result column, or the procedure's return value, because the runtime could not disambiguate which channel to read.

Solutions

  1. Inspect the existing mapping first: call FindRowsAffectedParameter(), FindRowsAffectedResultColumn(), and check IsRowsAffectedReturned before adding a new channel.
  2. Choose exactly ONE rows-affected source per procedure and remove the duplicate fluent call.
  3. If the procedure returns rows affected via its RETURN value, use .RowsAffectedReturned() only and do not add a parameter or result column.
  4. If a parameter must carry rows affected, ensure no .RowsAffectedResultColumn() or .RowsAffectedReturned() was configured for that procedure.

Example fix

// before
modelBuilder.Entity<Order>()
    .UpdateUsingStoredProcedure("usp_UpdateOrder", sp =>
    {
        sp.RowsAffectedReturned();
        sp.RowsAffectedParameter(); // throws: already returned
    });

// after
modelBuilder.Entity<Order>()
    .UpdateUsingStoredProcedure("usp_UpdateOrder", sp =>
    {
        sp.RowsAffectedReturned();
    });
Defensive patterns

Strategy: validation

Validate before calling

// Before adding a rows-affected channel, verify none exists.
var sp = entityType.GetUpdateStoredProcedure();
if (sp is not null
    && sp.FindRowsAffectedParameter() is null
    && sp.FindRowsAffectedResultColumn() is null
    && !sp.IsRowsAffectedReturned)
{
    sp.RowsAffectedParameter();
}

Type guard

static bool CanAddRowsAffectedParameter(IStoredProcedure sp)
    => sp.FindRowsAffectedParameter() is null
       && sp.FindRowsAffectedResultColumn() is null
       && !sp.IsRowsAffectedReturned;

Prevention

When it happens

Trigger: Calling entity.UpdateUsingStoredProcedure(...).RowsAffectedParameter() after a prior .RowsAffectedParameter(), .RowsAffectedResultColumn(), or .RowsAffectedReturned() on the same stored procedure. The guard fires when any of _rowsAffectedParameter, _rowsAffectedResultColumn, or _isRowsAffectedReturned is already set.

Common situations: Migrating a manual stored-procedure mapping where the DBA set rows-affected via the RETURN value, then the developer adds an output parameter; or copy-pasting fluent calls between Insert/Update/Delete procedures; or upgrading EF versions where the default rows-affected strategy changed.

Related errors


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

Appendix: source

Thrown at src/EFCore.Relational/Metadata/Internal/StoredProcedure.cs:577

    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual StoredProcedureParameter? FindRowsAffectedParameter()
        => _rowsAffectedParameter;

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual StoredProcedureParameter AddRowsAffectedParameter()
    {
        if (_rowsAffectedParameter != null
            || _rowsAffectedResultColumn != null
            || _isRowsAffectedReturned)
        {
            throw new InvalidOperationException(
                RelationalStrings.StoredProcedureDuplicateRowsAffectedParameter(
                    ((IReadOnlyStoredProcedure)this).GetStoreIdentifier()?.DisplayName()));
        }

        var parameter = new StoredProcedureParameter(this, rowsAffected: true, propertyName: null, originalValue: null);
        _parameters.Add(parameter);
        _rowsAffectedParameter = parameter;

        return parameter;
    }

    /// <summary>
    ///     This is an internal API that supports the Entity Framework Core infrastructure and not subject to
    ///     the same compatibility standards as public APIs. It may be changed or removed without notice in
    ///     any release. You should only use it directly in your code with extreme caution and knowing that
    ///     doing so can result in application failures when updating to a new Entity Framework Core release.
    /// </summary>
    public virtual IReadOnlyList<StoredProcedureResultColumn> ResultColumns

View on GitHub (pinned to 3a2006ef56)