{"record":{"id":"1b452c4483ce461c","repo":"DapperLib/Dapper","slug":"the-reader-has-been-disposed-this-can-happen-afte","errorCode":null,"errorMessage":"The reader has been disposed; this can happen after all data has been consumed","messagePattern":"The reader has been disposed; this can happen after all data has been consumed","errorType":"exception","errorClass":"ObjectDisposedException","httpStatus":null,"severity":"error","filePath":"Dapper/SqlMapper.GridReader.cs","lineNumber":185,"sourceCode":"            /// <summary>\r\n            /// Read an individual row of the next grid of results.\r\n            /// </summary>\r\n            /// <param name=\"type\">The type to read.</param>\r\n            /// <exception cref=\"ArgumentNullException\"><paramref name=\"type\"/> is <c>null</c>.</exception>\r\n            public object? ReadSingleOrDefault(Type type)\r\n            {\r\n                if (type is null) throw new ArgumentNullException(nameof(type));\r\n                return ReadRow<object>(type, Row.SingleOrDefault);\r\n            }\r\n\r\n\r\n            /// <summary>\r\n            /// Validates that data is available, returning the <see cref=\"ResultIndex\"/> that corresponds to the current grid - and marks the current grid as consumed;\r\n            /// this call <em>must</em> be paired with a call to <see cref=\"OnAfterGrid(int)\"/> or <see cref=\"OnAfterGridAsync(int)\"/>\r\n            /// </summary>\r\n            protected int OnBeforeGrid()\r\n            {\r\n                if (reader is null) throw new ObjectDisposedException(GetType().FullName, \"The reader has been disposed; this can happen after all data has been consumed\");\r\n                if (IsConsumed) throw new InvalidOperationException(\"Query results must be consumed in the correct order, and each result can only be consumed once\");\r\n                _resultIndexAndConsumedFlag |= CONSUMED_FLAG;\r\n                return ResultIndex;\r\n            }\r\n\r\n            private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)\r\n            {\r\n                var index = OnBeforeGrid();\r\n                var typedIdentity = Identity.ForGrid(type, index);\r\n                CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache);\r\n                var deserializer = cache.Deserializer;\r\n\r\n                int hash = GetColumnHash(reader);\r\n                if (deserializer.Func is null || deserializer.Hash != hash)\r\n                {\r\n                    deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false));\r\n                    cache.Deserializer = deserializer;\r\n                }\r","sourceCodeStart":167,"sourceCodeEnd":203,"githubUrl":"https://github.com/DapperLib/Dapper/blob/72a54c475f75e18cb93cba0809d00a5e6e49efd9/Dapper/SqlMapper.GridReader.cs#L167-L203","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nvar grid = cnn.QueryMultiple(sql);\nvar a = grid.Read<A>();\n// grid falls out of scope, disposed -> later read throws\n\n// after\nusing var grid = cnn.QueryMultiple(sql);\nvar a = grid.Read<A>();\nvar b = grid.Read<B>(); // both reads inside the scope","handlingStrategy":"validation","validationCode":"// Structural guard: read only while the grid is alive\nusing var grid = cnn.QueryMultiple(sql);\nif (grid.IsConsumed) throw new InvalidOperationException(\"GridReader already consumed\");\nvar rows = grid.Read<Foo>();","typeGuard":null,"tryCatchPattern":"try { var rows = grid.Read<Foo>(); }\ncatch (ObjectDisposedException) { /* grid was disposed; re-run QueryMultiple */ }","preventionTips":["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."],"tags":["dapper","gridreader","objectdisposedexception","lifecycle","using-scope"],"backgroundTag":null,"analyzedSha":"72a54c475f75e18cb93cba0809d00a5e6e49efd9","analyzedAt":"2026-08-13T14:18:18.115Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}