dotnet/efcore · error · InvalidOperationException

The entity type ' ' is mapped to the stored procedure ' '…

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

Thrown when an UPDATE or DELETE sproc mapping returns both meaningful result columns (other than the rows-affected result column) AND a rows-affected value. EF reads entity data back through result columns; in that case a separate rows-affected value is redundant and confusing, so the validator rejects the combination. Only the if-branch where sproc.ResultColumns.Any(c => c != sproc.FindRowsAffectedResultColumn()) trips this.

Solutions

  1. Remove the rows-affected configuration (.RowsAffectedReturn(), .RowsAffectedResultColumn, .RowsAffectedParameter) since the result columns already imply success and provide data.
  2. Alternatively, remove the result columns if you genuinely want only rows-affected semantics — but EF cannot then read back store-generated values.

Example fix

// before
entityType.UpdateUsingStoredProcedure("UpdateOrder",
    sp =>
    {
        sp.HasParameter(o => o.OrderId);
        sp.HasResultColumn(o => o.UpdatedAt);
        sp.RowsAffectedReturn(); // redundant
    });

// after
entityType.UpdateUsingStoredProcedure("UpdateOrder",
    sp =>
    {
        sp.HasParameter(o => o.OrderId);
        sp.HasResultColumn(o => o.UpdatedAt);
    });
Defensive patterns

Strategy: validation

Validate before calling

foreach (var et in model.GetEntityTypes())
{
    foreach (var sproc in new[] { et.GetUpdateStoredProcedure(), et.GetDeleteStoredProcedure() })
    {
        if (sproc == null) continue;
        bool hasRowsAffected = sproc.IsRowsAffectedReturned
            || sproc.FindRowsAffectedParameter() != null
            || sproc.FindRowsAffectedResultColumn() != null;
        bool hasResultColumns = sproc.ResultColumns.Any(c => c != sproc.FindRowsAffectedResultColumn());
        if (hasRowsAffected && hasResultColumns)
            Console.WriteLine($"{sproc.Name} has both result columns and rows-affected; remove one.");
    }
}

Prevention

When it happens

Trigger: UpdateUsingStoredProcedure or DeleteUsingStoredProcedure has both .HasResultColumn(p => p.SomeProp) (a property result column) and a .RowsAffectedReturn() / .RowsAffectedResultColumn(...) / .RowsAffectedParameter(...) configured together.

Common situations: Migrating from a rows-affected sproc to a result-set-based sproc without removing the rows-affected call; adding a result column for an updated property while keeping the legacy rows-affected return.

Related errors


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

Appendix: 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 3a2006ef56)