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
- Use `Execute`/`ExecuteAsync` for batch (multi-exec) operations — that is the only method that iterates a sequence.
- If you need a reader, call ExecuteReader once per item in your own loop, or unwrap the sequence into a single parameter object.
- For bulk inserts, use `Execute` with the list, or a bulk-insert facility of your provider.
- 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
- Use Execute/ExecuteAsync for per-item batch operations.
- Pass a single parameter object (not a list) to ExecuteReader.
- Document which Dapper methods support multi-exec.
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
- An enumerable sequence of parameters (arrays, lists, etc) is
- The member {name} of type {type.FullName} cannot be used as
- When passing parameters by position, each parameter can only
- The first item in a list-expansion cannot be null
- ValueTuple should not be used for parameters - the language-
AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13).
Data as JSON: /api/errors/d5ecf85f7d1846fb.
Report an issue: GitHub.