#if ASYNC using System; using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; using System.Threading; using System.Threading.Tasks; namespace Dapper { partial class SqlMapper { partial class GridReader { CancellationToken cancel; internal GridReader(IDbCommand command, IDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel) : this(command, reader, identity, dynamicParams, addToCache) { this.cancel = cancel; } /// /// Read the next grid of results, returned as a dynamic object /// /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task> ReadAsync(bool buffered = true) { return ReadAsyncImpl(typeof(DapperRow), buffered); } /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task ReadFirstAsync() { return ReadRowAsyncImpl(typeof(DapperRow), Row.First); } /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task ReadFirstOrDefaultAsync() { return ReadRowAsyncImpl(typeof(DapperRow), Row.FirstOrDefault); } /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task ReadSingleAsync() { return ReadRowAsyncImpl(typeof(DapperRow), Row.Single); } /// /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task ReadSingleOrDefaultAsync() { return ReadRowAsyncImpl(typeof(DapperRow), Row.SingleOrDefault); } /// /// Read the next grid of results /// public Task> ReadAsync(Type type, bool buffered = true) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadAsyncImpl(type, buffered); } /// /// Read an individual row of the next grid of results /// public Task ReadFirstAsync(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.First); } /// /// Read an individual row of the next grid of results /// public Task ReadFirstOrDefaultAsync(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.FirstOrDefault); } /// /// Read an individual row of the next grid of results /// public Task ReadSingleAsync(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.Single); } /// /// Read an individual row of the next grid of results /// public Task ReadSingleOrDefaultAsync(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.SingleOrDefault); } /// /// Read the next grid of results /// public Task> ReadAsync(bool buffered = true) { return ReadAsyncImpl(typeof(T), buffered); } /// /// Read an individual row of the next grid of results /// public Task ReadFirstAsync() { return ReadRowAsyncImpl(typeof(T), Row.First); } /// /// Read an individual row of the next grid of results /// public Task ReadFirstOrDefaultAsync() { return ReadRowAsyncImpl(typeof(T), Row.FirstOrDefault); } /// /// Read an individual row of the next grid of results /// public Task ReadSingleAsync() { return ReadRowAsyncImpl(typeof(T), Row.Single); } /// /// Read an individual row of the next grid of results /// public Task ReadSingleOrDefaultAsync() { return ReadRowAsyncImpl(typeof(T), Row.SingleOrDefault); } private async Task NextResultAsync() { if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false)) { readCount++; gridIndex++; IsConsumed = false; } else { // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); reader = null; callbacks?.OnCompleted(); Dispose(); } } private Task> ReadAsyncImpl(Type type, bool buffered) { if (reader == 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"); var typedIdentity = identity.ForGrid(type, gridIndex); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); if (deserializer.Func == null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } IsConsumed = true; if (buffered && reader is DbDataReader) { return ReadBufferedAsync(gridIndex, deserializer.Func, typedIdentity); } else { var result = ReadDeferred(gridIndex, deserializer.Func, typedIdentity, type); if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario return Task.FromResult(result); } } private Task ReadRowAsyncImpl(Type type, Row row) { var dbReader = reader as DbDataReader; if (dbReader != null) return ReadRowAsyncImplViaDbReader(dbReader, type, row); // no async API available; use non-async and fake it return Task.FromResult(ReadRow(type, row)); } private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type type, Row row) { if (reader == 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"); IsConsumed = true; T result = default(T); if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) { var typedIdentity = identity.ForGrid(type, gridIndex); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); if (deserializer.Func == null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } result = (T)deserializer.Func(reader); if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { } } else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } await NextResultAsync().ConfigureAwait(false); return result; } private async Task> ReadBufferedAsync(int index, Func deserializer, Identity typedIdentity) { try { var reader = (DbDataReader)this.reader; List buffer = new List(); while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) { buffer.Add((T)deserializer(reader)); } return buffer; } finally // finally so that First etc progresses things even when multiple rows { if (index == gridIndex) { await NextResultAsync().ConfigureAwait(false); } } } } } } #endif