using System; using System.Collections.Generic; using System.Data; using System.Linq; using System.Globalization; namespace Dapper { partial class SqlMapper { /// /// The grid reader provides interfaces for reading multiple result sets from a Dapper query /// public partial class GridReader : IDisposable { private IDataReader reader; private Identity identity; private bool addToCache; internal GridReader(IDbCommand command, IDataReader reader, Identity identity, IParameterCallbacks callbacks, bool addToCache) { Command = command; this.reader = reader; this.identity = identity; this.callbacks = callbacks; this.addToCache = addToCache; } /// /// 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 IEnumerable Read(bool buffered = true) { return ReadImpl(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 dynamic ReadFirst() { return ReadRow(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 dynamic ReadFirstOrDefault() { return ReadRow(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 dynamic ReadSingle() { return ReadRow(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 dynamic ReadSingleOrDefault() { return ReadRow(typeof(DapperRow), Row.SingleOrDefault); } /// /// Read the next grid of results /// public IEnumerable Read(bool buffered = true) { return ReadImpl(typeof(T), buffered); } /// /// Read an individual row of the next grid of results /// public T ReadFirst() { return ReadRow(typeof(T), Row.First); } /// /// Read an individual row of the next grid of results /// public T ReadFirstOrDefault() { return ReadRow(typeof(T), Row.FirstOrDefault); } /// /// Read an individual row of the next grid of results /// public T ReadSingle() { return ReadRow(typeof(T), Row.Single); } /// /// Read an individual row of the next grid of results /// public T ReadSingleOrDefault() { return ReadRow(typeof(T), Row.SingleOrDefault); } /// /// Read the next grid of results /// public IEnumerable Read(Type type, bool buffered = true) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadImpl(type, buffered); } /// /// Read an individual row of the next grid of results /// public object ReadFirst(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.First); } /// /// Read an individual row of the next grid of results /// public object ReadFirstOrDefault(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.FirstOrDefault); } /// /// Read an individual row of the next grid of results /// public object ReadSingle(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.Single); } /// /// Read an individual row of the next grid of results /// public object ReadSingleOrDefault(Type type) { if (type == null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.SingleOrDefault); } private IEnumerable ReadImpl(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; var result = ReadDeferred(gridIndex, deserializer.Func, typedIdentity, type); return buffered ? result.ToList() : result; } private T ReadRow(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(reader.Read() && 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; } object val = deserializer.Func(reader); if(val == null || val is T) { result = (T)val; } else { var convertToType = Nullable.GetUnderlyingType(type) ?? type; result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); } if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); while (reader.Read()) { } } else if((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { ThrowZeroRows(row); } NextResult(); return result; } private IEnumerable MultiReadInternal(Delegate func, string splitOn) { var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, gridIndex); try { foreach (var r in MultiMapImpl(null, default(CommandDefinition), func, splitOn, reader, identity, false)) { yield return r; } } finally { NextResult(); } } private IEnumerable MultiReadInternal(Type[] types, Func map, string splitOn) { var identity = this.identity.ForGrid(typeof(TReturn), types, gridIndex); try { foreach (var r in MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, false)) { yield return r; } } finally { NextResult(); } } /// /// Read multiple objects from a single record set on the grid /// public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// public IEnumerable Read(Func func, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(func, splitOn); return buffered ? result.ToList() : result; } /// /// Read multiple objects from a single record set on the grid /// public IEnumerable Read(Type[] types, Func map, string splitOn = "id", bool buffered = true) { var result = MultiReadInternal(types, map, splitOn); return buffered ? result.ToList() : result; } private IEnumerable ReadDeferred(int index, Func deserializer, Identity typedIdentity, Type effectiveType) { try { var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; while (index == gridIndex && reader.Read()) { object val = deserializer(reader); if (val == null || val is T) { yield return (T)val; } else { yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); } } } finally // finally so that First etc progresses things even when multiple rows { if (index == gridIndex) { NextResult(); } } } private int gridIndex, readCount; private IParameterCallbacks callbacks; /// /// Has the underlying reader been consumed? /// public bool IsConsumed { get; private set; } /// /// The command associated with the reader /// public IDbCommand Command { get; set; } private void NextResult() { if (reader.NextResult()) { readCount++; gridIndex++; IsConsumed = false; } else { // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); reader = null; callbacks?.OnCompleted(); Dispose(); } } /// /// Dispose the grid, closing and disposing both the underlying reader and command. /// public void Dispose() { if (reader != null) { if (!reader.IsClosed) Command?.Cancel(); reader.Dispose(); reader = null; } if (Command != null) { Command.Dispose(); Command = null; } } } } }