DapperLib/Dapper · error · ObjectDisposedException
The reader has been disposed; this can happen after all data
Error message
The reader has been disposed; this can happen after all data has been consumed
What it means
OnBeforeGrid() is the guard every Read/ReadAsync path calls (SqlMapper.GridReader.cs:183). It throws ObjectDisposedException when the internal reader field is null — Dispose() nulls it. The message notes this can also happen after all data has been consumed, since the reader is torn down at that point.
Source
Thrown at Dapper/SqlMapper.GridReader.cs:185
/// <summary>
/// Read an individual row of the next grid of results.
/// </summary>
/// <param name="type">The type to read.</param>
/// <exception cref="ArgumentNullException"><paramref name="type"/> is <c>null</c>.</exception>
public object? ReadSingleOrDefault(Type type)
{
if (type is null) throw new ArgumentNullException(nameof(type));
return ReadRow<object>(type, Row.SingleOrDefault);
}
/// <summary>
/// Validates that data is available, returning the <see cref="ResultIndex"/> that corresponds to the current grid - and marks the current grid as consumed;
/// this call <em>must</em> be paired with a call to <see cref="OnAfterGrid(int)"/> or <see cref="OnAfterGridAsync(int)"/>
/// </summary>
protected int OnBeforeGrid()
{
if (reader is null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
_resultIndexAndConsumedFlag |= CONSUMED_FLAG;
return ResultIndex;
}
private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
{
var index = OnBeforeGrid();
var typedIdentity = Identity.ForGrid(type, index);
CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);
var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader);
if (deserializer.Func is null || deserializer.Hash != hash)
{
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));
cache.Deserializer = deserializer;
}
View on GitHub (pinned to 72a54c475f)
Solutions
- Always wrap the GridReader in using: using var grid = cnn.QueryMultiple(sql); then read every grid inside the scope.
- Read exactly the number of result sets the query produces; track count and stop.
- Do not Dispose or let the using exit before all reads (sync) / all awaited reads (async) complete.
- For async, await every ReadAsync inside the using scope before it disposes.
Example fix
// before var grid = cnn.QueryMultiple(sql); var a = grid.Read<A>(); // grid falls out of scope, disposed -> later read throws // after using var grid = cnn.QueryMultiple(sql); var a = grid.Read<A>(); var b = grid.Read<B>(); // both reads inside the scope
Defensive patterns
Strategy: validation
Validate before calling
// Structural guard: read only while the grid is alive
using var grid = cnn.QueryMultiple(sql);
if (grid.IsConsumed) throw new InvalidOperationException("GridReader already consumed");
var rows = grid.Read<Foo>(); Try / catch
try { var rows = grid.Read<Foo>(); }
catch (ObjectDisposedException) { /* grid was disposed; re-run QueryMultiple */ } Prevention
- Always wrap GridReader in using and read every grid inside the scope.
- Await all ReadAsync calls before the using exits.
- Track the number of result sets and never read past the last one.
- Never share a GridReader across threads without synchronization.
When it happens
Trigger: Call any Read/ReadFirst/ReadAsync after grid.Dispose(); or after the using block holding the GridReader has exited; or attempt to read more result sets than the query returned (the last grid's consumption disposes the reader).
Common situations: Missing `using` on the GridReader; reading grids asynchronously after the connection/command scope closed; sharing a GridReader across threads where one disposes; off-by-one in grid count when looping.
Related errors
- type
- This operation requires an identity or a connected command
- type
- 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/1b452c4483ce461c.
Report an issue: GitHub.