dotnet/efcore · error · InvalidOperationException

The entity type '{entityType}' is mapped to the stored proce

Error message

The entity type '{entityType}' is mapped to the stored procedure '{sproc}' which returns both result columns and a rows affected value. If the stored procedure returns result columns, a rows affected value isn't needed and can be safely removed.

What it means

An Update/Delete stored procedure can signal optimistic-concurrency failure either by returning zero rows-affected OR by returning a result set whose columns re-select the current row values for EF to compare. Using both is redundant and ambiguous. The validator at line 925-931 throws when the sproc has result columns beyond the rows-affected result column AND any rows-affected signal is configured.

Source

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

        if (sproc.IsRowsAffectedReturned
            || sproc.FindRowsAffectedParameter() != null
            || sproc.FindRowsAffectedResultColumn() != null)
        {
            if (storeObjectIdentifier.StoreObjectType == StoreObjectType.InsertStoredProcedure)
            {
                throw new InvalidOperationException(
                    RelationalStrings.StoredProcedureRowsAffectedForInsert(
                        storeObjectIdentifier.DisplayName()));
            }

            if (originalValueProperties.Values.FirstOrDefault(p => p.IsConcurrencyToken) is { } missedConcurrencyToken)
            {
                logger.StoredProcedureConcurrencyTokenNotMapped(entityType, missedConcurrencyToken, storeObjectIdentifier.DisplayName());
            }

            if (sproc.ResultColumns.Any(c => c != sproc.FindRowsAffectedResultColumn()))
            {
                throw new InvalidOperationException(
                    RelationalStrings.StoredProcedureRowsAffectedWithResultColumns(
                        entityType.DisplayName(),
                        storeObjectIdentifier.DisplayName()));
            }
        }
    }

    /// <summary>
    ///     Validates a <see cref="bool" /> property with defaults.
    /// </summary>
    /// <param name="property">The property to validate.</param>
    /// <param name="logger">The logger to use.</param>
    protected virtual void ValidateBoolWithDefaults(
        IProperty property,
        IDiagnosticsLogger<DbLoggerCategory.Model.Validation> logger)
    {
        if (!property.ClrType.IsNullableType()
            && (property.ClrType.IsEnum || property.ClrType == typeof(bool))

View on GitHub (pinned to dbf9771522)

Solutions

  1. If the sproc returns result columns, remove all rows-affected configuration (.RowsAffectedParameter/.RowsAffectedResultColumn/.ReturnsRowsAffected).
  2. Alternatively, remove the .ResultColumn mappings and keep only the rows-affected signal if the sproc does not re-select row data.
  3. Align the database stored procedure to use one strategy consistently.

Example fix

// before
modelBuilder.Entity<Order>()
    .UpdateStoredProcedure(o => o
        .RowsAffectedParameter("rc")
        .ResultColumn(p => p.Amount, "amt")
        .ResultColumn(p => p.Status, "status"));

// after - keep result columns, drop rows-affected
modelBuilder.Entity<Order>()
    .UpdateStoredProcedure(o => o
        .ResultColumn(p => p.Amount, "amt")
        .ResultColumn(p => p.Status, "status"));
Defensive patterns

Strategy: validation

Validate before calling

foreach (var et in modelBuilder.Model.GetEntityTypes())
{
    foreach (var sproc in et.GetStoredProcedures())
    {
        bool hasRowsAffected = sproc.IsRowsAffectedReturned
            || sproc.FindRowsAffectedParameter() != null
            || sproc.FindRowsAffectedResultColumn() != null;
        bool hasResultColumns = sproc.ResultColumns
            .Any(c => c != sproc.FindRowsAffectedResultColumn());
        if (hasRowsAffected && hasResultColumns)
        {
            throw new InvalidOperationException(
                $"{et.Name} sproc combines rows-affected with result columns.");
        }
    }
}

Prevention

When it happens

Trigger: On an Update/Delete sproc: simultaneously calling `.ResultColumn(...)` for re-selected columns AND `.RowsAffectedParameter(...)` / `.RowsAffectedResultColumn(...)` / `.ReturnsRowsAffected()`.

Common situations: Copy-pasting a sproc template that returns both a row count and the full row; migrating from a rows-affected model to a result-column model but forgetting to remove the old config.

Related errors


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