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&lt;string,object&gt;</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&lt;string,object&gt;</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

  1. Keep the IDbConnection open and the command alive until every grid is consumed; wrap usage in using var grid = cnn.QueryMultiple(...).
  2. When constructing a GridReader manually, pass a valid Identity built from the command text, type, and connection.
  3. 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

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


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