22using System . Collections . Generic ;
33using System . Data ;
44using System . Data . Common ;
5- using System . Globalization ;
65using System . Linq ;
6+ using System . Runtime . CompilerServices ;
77using System . Threading ;
88using System . Threading . Tasks ;
99
@@ -12,6 +12,9 @@ namespace Dapper
1212 public static partial class SqlMapper
1313 {
1414 public partial class GridReader
15+ #if NET5_0_OR_GREATER
16+ : IAsyncDisposable
17+ #endif
1518 {
1619 private readonly CancellationToken cancel ;
1720 internal GridReader ( IDbCommand command , DbDataReader reader , Identity identity , DynamicParameters dynamicParams , bool addToCache , CancellationToken cancel )
@@ -140,7 +143,7 @@ public Task<object> ReadSingleOrDefaultAsync(Type type)
140143
141144 private async Task NextResultAsync ( )
142145 {
143- if ( await ( ( DbDataReader ) reader ) . NextResultAsync ( cancel ) . ConfigureAwait ( false ) )
146+ if ( await reader . NextResultAsync ( cancel ) . ConfigureAwait ( false ) )
144147 {
145148 // readCount++;
146149 gridIndex ++ ;
@@ -150,14 +153,37 @@ private async Task NextResultAsync()
150153 {
151154 // happy path; close the reader cleanly - no
152155 // need for "Cancel" etc
156+ #if NET5_0_OR_GREATER
157+ await reader . DisposeAsync ( ) ;
158+ #else
153159 reader . Dispose ( ) ;
160+ #endif
154161 reader = null ;
155162 callbacks ? . OnCompleted ( ) ;
163+ #if NET5_0_OR_GREATER
164+ await DisposeAsync ( ) ;
165+ #else
156166 Dispose ( ) ;
167+ #endif
157168 }
158169 }
159170
160171 private Task < IEnumerable < T > > ReadAsyncImpl < T > ( Type type , bool buffered )
172+ {
173+ var deserializer = ValidateAndMarkConsumed ( type ) ;
174+ if ( buffered )
175+ {
176+ return ReadBufferedAsync < T > ( gridIndex , deserializer ) ;
177+ }
178+ else
179+ {
180+ var result = ReadDeferred < T > ( gridIndex , deserializer , type ) ;
181+ if ( buffered ) result = result ? . ToList ( ) ; // for the "not a DbDataReader" scenario
182+ return Task . FromResult ( result ) ;
183+ }
184+ }
185+
186+ private Func < DbDataReader , object > ValidateAndMarkConsumed ( Type type )
161187 {
162188 if ( reader == null ) throw new ObjectDisposedException ( GetType ( ) . FullName , "The reader has been disposed; this can happen after all data has been consumed" ) ;
163189 if ( IsConsumed ) throw new InvalidOperationException ( "Query results must be consumed in the correct order, and each result can only be consumed once" ) ;
@@ -172,27 +198,10 @@ private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered)
172198 cache . Deserializer = deserializer ;
173199 }
174200 IsConsumed = true ;
175- if ( buffered && reader is DbDataReader )
176- {
177- return ReadBufferedAsync < T > ( gridIndex , deserializer . Func ) ;
178- }
179- else
180- {
181- var result = ReadDeferred < T > ( gridIndex , deserializer . Func , type ) ;
182- if ( buffered ) result = result ? . ToList ( ) ; // for the "not a DbDataReader" scenario
183- return Task . FromResult ( result ) ;
184- }
185- }
186-
187- private Task < T > ReadRowAsyncImpl < T > ( Type type , Row row )
188- {
189- if ( reader is DbDataReader dbReader ) return ReadRowAsyncImplViaDbReader < T > ( dbReader , type , row ) ;
190-
191- // no async API available; use non-async and fake it
192- return Task . FromResult ( ReadRow < T > ( type , row ) ) ;
201+ return deserializer . Func ;
193202 }
194203
195- private async Task < T > ReadRowAsyncImplViaDbReader < T > ( DbDataReader reader , Type type , Row row )
204+ private async Task < T > ReadRowAsyncImpl < T > ( Type type , Row row )
196205 {
197206 if ( reader == null ) throw new ObjectDisposedException ( GetType ( ) . FullName , "The reader has been disposed; this can happen after all data has been consumed" ) ;
198207 if ( IsConsumed ) throw new InvalidOperationException ( "Query results must be consumed in the correct order, and each result can only be consumed once" ) ;
@@ -229,7 +238,6 @@ private async Task<IEnumerable<T>> ReadBufferedAsync<T>(int index, Func<DbDataRe
229238 {
230239 try
231240 {
232- var reader = ( DbDataReader ) this . reader ;
233241 var buffer = new List < T > ( ) ;
234242 while ( index == gridIndex && await reader . ReadAsync ( cancel ) . ConfigureAwait ( false ) )
235243 {
@@ -245,6 +253,64 @@ private async Task<IEnumerable<T>> ReadBufferedAsync<T>(int index, Func<DbDataRe
245253 }
246254 }
247255 }
256+
257+ #if NET5_0_OR_GREATER
258+ /// <summary>
259+ /// Read the next grid of results.
260+ /// </summary>
261+ /// <typeparam name="T">The type to read.</typeparam>
262+ public IAsyncEnumerable < T > ReadUnbufferedAsync < T > ( ) => ReadAsyncUnbufferedImpl < T > ( typeof ( T ) ) ;
263+
264+ private IAsyncEnumerable < T > ReadAsyncUnbufferedImpl < T > ( Type type )
265+ {
266+ var deserializer = ValidateAndMarkConsumed ( type ) ;
267+ return ReadUnbufferedAsync < T > ( gridIndex , deserializer , cancel ) ;
268+ }
269+
270+ private async IAsyncEnumerable < T > ReadUnbufferedAsync < T > ( int index , Func < DbDataReader , object > deserializer , [ EnumeratorCancellation ] CancellationToken cancel )
271+ {
272+ try
273+ {
274+ while ( index == gridIndex && await reader . ReadAsync ( cancel ) . ConfigureAwait ( false ) )
275+ {
276+ yield return ConvertTo < T > ( deserializer ( reader ) ) ;
277+ }
278+ }
279+ finally // finally so that First etc progresses things even when multiple rows
280+ {
281+ if ( index == gridIndex )
282+ {
283+ await NextResultAsync ( ) . ConfigureAwait ( false ) ;
284+ }
285+ }
286+ }
287+
288+ /// <summary>
289+ /// Dispose the grid, closing and disposing both the underlying reader and command.
290+ /// </summary>
291+ public async ValueTask DisposeAsync ( )
292+ {
293+ if ( reader != null )
294+ {
295+ if ( ! reader . IsClosed ) Command ? . Cancel ( ) ;
296+ await reader . DisposeAsync ( ) ;
297+ reader = null ;
298+ }
299+ if ( Command != null )
300+ {
301+ if ( Command is DbCommand typed )
302+ {
303+ await typed . DisposeAsync ( ) ;
304+ }
305+ else
306+ {
307+ Command . Dispose ( ) ;
308+ }
309+ Command = null ;
310+ }
311+ GC . SuppressFinalize ( this ) ;
312+ }
313+ #endif
248314 }
249315 }
250316}
0 commit comments