DapperLib/Dapper · error · NotSupportedException

MultiExec is not supported by ExecuteReader

Error message

MultiExec is not supported by ExecuteReader

What it means

Thrown by GetParameterReader (the ExecuteReader code path) when the `param` object is a multi-exec enumerable. Only `Execute`/`ExecuteAsync` are designed to loop over a sequence and run the command once per item; the reader paths (ExecuteReader/ExecuteReaderAsync, and the Query methods built on them) take a single command, so passing a list is rejected with NotSupportedException.

Source

Thrown at Dapper/SqlMapper.cs:3081

            finally
            {
                if (wasClosed) cnn.Close();
                if (cmd is not null && disposeCommand)
                {
                    cmd.Parameters.Clear();
                    cmd.Dispose();
                }
            }
        }

        private static Action<IDbCommand, object?>? GetParameterReader(IDbConnection cnn, ref CommandDefinition command)
        {
            object? param = command.Parameters;
            IEnumerable? multiExec = GetMultiExec(param);
            CacheInfo? info = null;
            if (multiExec is not null)
            {
                throw new NotSupportedException("MultiExec is not supported by ExecuteReader");
            }

            // nice and simple
            if (param is not null)
            {
                var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType());
                info = GetCacheInfo(identity, param, command.AddToCache);
            }
            var paramReader = info?.ParamReader;
            return paramReader;
        }

        private static Func<DbDataReader, object> GetSimpleValueDeserializer(Type type, Type effectiveType, int index, bool useGetFieldValue)
        {
            // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!)
#pragma warning disable 618
            if (type == typeof(char))
            { // this *does* need special handling, though

View on GitHub (pinned to 72a54c475f)

Solutions

  1. Use `Execute`/`ExecuteAsync` for batch (multi-exec) operations — that is the only method that iterates a sequence.
  2. If you need a reader, call ExecuteReader once per item in your own loop, or unwrap the sequence into a single parameter object.
  3. For bulk inserts, use `Execute` with the list, or a bulk-insert facility of your provider.
  4. Pass an anonymous object (`new { ... }`) rather than a list if you want a single reader call.

Example fix

// before
using var rd = cnn.ExecuteReader("insert into t(id) values(@Id)", listOfDtos);
// after
cnn.Execute("insert into t(id) values(@Id)", listOfDtos);
Defensive patterns

Strategy: validation

Validate before calling

// Use Execute for batch/multi-exec; unwrap lists before ExecuteReader.
if (param is IEnumerable and not string and not IDynamicParameters) cnn.Execute(sql, param);
else { using var rd = cnn.ExecuteReader(sql, param); }

Type guard

static bool IsMultiExec(object? p) => p is IEnumerable and not string and not IDynamicParameters and not IEnumerable<KeyValuePair<string,object>>;

Prevention

When it happens

Trigger: Calling `cnn.ExecuteReader(sql, new[] { new {Id=1}, new {Id=2} })` or `ExecuteReaderAsync` with an array/list as `param`. GetMultiExec (SqlMapper.cs:623) classifies it as a multi-exec sequence, and GetParameterReader (SqlMapper.cs:3081) refuses it.

Common situations: Trying to batch-insert via ExecuteReader instead of Execute; passing a collection of DTOs expecting per-row reader execution; misunderstanding which Dapper methods support multi-exec.

Related errors


AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13). Data as JSON: /api/errors/d5ecf85f7d1846fb. Report an issue: GitHub.