DapperLib/Dapper · error · InvalidOperationException
This operation requires an identity or a connected command
Error message
This operation requires an identity or a connected command
What it means
GridReader builds an Identity lazily (SqlMapper.GridReader.cs:48) to key the per-shape deserializer cache. CreateIdentity() (line 50) can only build one from a non-null Command whose Connection is non-null; otherwise it throws InvalidOperationException. This fires when no Identity was supplied at construction and the command is detached/disposed before any Read.
Source
Thrown at Dapper/SqlMapper.GridReader.cs:57
cancel = cancellationToken;
}
internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, IParameterCallbacks? callbacks, bool addToCache,
CancellationToken cancellationToken = default)
: this(command, reader, identity, callbacks is null ? null : static state => ((IParameterCallbacks)state!).OnCompleted(),
callbacks, addToCache, cancellationToken)
{ }
private Identity Identity => _identity ??= CreateIdentity();
private Identity CreateIdentity()
{
var cmd = Command;
if (cmd is not null && cmd.Connection is not null)
{
return new Identity(cmd.CommandText, cmd.CommandType, cmd.Connection, null, null);
}
throw new InvalidOperationException("This operation requires an identity or a connected command");
}
/// <summary>
/// Read the next grid of results, returned as a dynamic object.
/// </summary>
/// <param name="buffered">Whether the results should be buffered in memory.</param>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
public IEnumerable<dynamic> Read(bool buffered = true) => ReadImpl<dynamic>(typeof(DapperRow), buffered);
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object.
/// </summary>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
public dynamic ReadFirst() => ReadRow<dynamic>(typeof(DapperRow), Row.First);
/// <summary>
/// Read an individual row of the next grid of results, returned as a dynamic object.
/// </summary>
View on GitHub (pinned to 72a54c475f)
Solutions
- Keep the IDbConnection open and the command alive until every grid is consumed; wrap usage in using var grid = cnn.QueryMultiple(...).
- When constructing a GridReader manually, pass a valid Identity built from the command text, type, and connection.
- Prefer the provided QueryMultiple/QueryMultipleAsync factory over manual construction so Identity is always supplied.
Example fix
// before var grid = new MyGridReader(cmd, reader, identity: null); // cmd.Connection later set null var data = grid.Read<Foo>(); // throws // after var identity = new Identity(cmd.CommandText, cmd.CommandType, cmd.Connection, null, null); var grid = new MyGridReader(cmd, reader, identity); var data = grid.Read<Foo>();
Defensive patterns
Strategy: validation
Validate before calling
// Before reading, ensure identity can be built
var cmd = grid.Command;
if (cmd is null || cmd.Connection is null) throw new InvalidOperationException("GridReader needs a connected command before reading"); Try / catch
try { var data = grid.Read<Foo>(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("identity or a connected command"))
{ /* re-open connection / re-run QueryMultiple */ } Prevention
- Use QueryMultiple/QueryMultipleAsync factories so Identity is always supplied.
- Keep the connection open inside a using until all grids are read.
- If subclassing GridReader, always pass a valid Identity to the constructor.
When it happens
Trigger: Construct a GridReader via the protected/internal constructor with identity=null and either Command=null or Command.Connection=null, then call Read/ReadFirst/ReadAsync which dereferences Identity. Custom subclasses or test doubles that bypass the normal QueryMultiple path hit this.
Common situations: Subclassing GridReader or writing a fake/wrapper without supplying an Identity; disposing/closing the connection before consuming the grids; capturing the command, detaching it, then reading.
Related errors
- type
- type
- The reader has been disposed; this can happen after all data
- An item with the same key has already been added
- The member {name} of type {type.FullName} cannot be used as
AI-assisted analysis of DapperLib/Dapper@72a54c475f (2026-08-13).
Data as JSON: /api/errors/ee715bb8c5d5a10a.
Report an issue: GitHub.