You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
381 lines
17 KiB
381 lines
17 KiB
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data;
|
|
using System.Linq;
|
|
using System.Globalization;
|
|
namespace Dapper
|
|
{
|
|
partial class SqlMapper
|
|
{
|
|
/// <summary>
|
|
/// The grid reader provides interfaces for reading multiple result sets from a Dapper query
|
|
/// </summary>
|
|
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;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the next grid of results, returned as a dynamic object
|
|
/// </summary>
|
|
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
|
|
public IEnumerable<dynamic> Read(bool buffered = true)
|
|
{
|
|
return ReadImpl<dynamic>(typeof(DapperRow), buffered);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results, returned as a dynamic object
|
|
/// </summary>
|
|
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
|
|
public dynamic ReadFirst()
|
|
{
|
|
return ReadRow<dynamic>(typeof(DapperRow), Row.First);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results, returned as a dynamic object
|
|
/// </summary>
|
|
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
|
|
public dynamic ReadFirstOrDefault()
|
|
{
|
|
return ReadRow<dynamic>(typeof(DapperRow), Row.FirstOrDefault);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results, returned as a dynamic object
|
|
/// </summary>
|
|
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
|
|
public dynamic ReadSingle()
|
|
{
|
|
return ReadRow<dynamic>(typeof(DapperRow), Row.Single);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results, returned as a dynamic object
|
|
/// </summary>
|
|
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object></remarks>
|
|
public dynamic ReadSingleOrDefault()
|
|
{
|
|
return ReadRow<dynamic>(typeof(DapperRow), Row.SingleOrDefault);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the next grid of results
|
|
/// </summary>
|
|
public IEnumerable<T> Read<T>(bool buffered = true)
|
|
{
|
|
return ReadImpl<T>(typeof(T), buffered);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results
|
|
/// </summary>
|
|
public T ReadFirst<T>()
|
|
{
|
|
return ReadRow<T>(typeof(T), Row.First);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results
|
|
/// </summary>
|
|
public T ReadFirstOrDefault<T>()
|
|
{
|
|
return ReadRow<T>(typeof(T), Row.FirstOrDefault);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results
|
|
/// </summary>
|
|
public T ReadSingle<T>()
|
|
{
|
|
return ReadRow<T>(typeof(T), Row.Single);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results
|
|
/// </summary>
|
|
public T ReadSingleOrDefault<T>()
|
|
{
|
|
return ReadRow<T>(typeof(T), Row.SingleOrDefault);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read the next grid of results
|
|
/// </summary>
|
|
public IEnumerable<object> Read(Type type, bool buffered = true)
|
|
{
|
|
if (type == null) throw new ArgumentNullException(nameof(type));
|
|
return ReadImpl<object>(type, buffered);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results
|
|
/// </summary>
|
|
public object ReadFirst(Type type)
|
|
{
|
|
if (type == null) throw new ArgumentNullException(nameof(type));
|
|
return ReadRow<object>(type, Row.First);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results
|
|
/// </summary>
|
|
public object ReadFirstOrDefault(Type type)
|
|
{
|
|
if (type == null) throw new ArgumentNullException(nameof(type));
|
|
return ReadRow<object>(type, Row.FirstOrDefault);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results
|
|
/// </summary>
|
|
public object ReadSingle(Type type)
|
|
{
|
|
if (type == null) throw new ArgumentNullException(nameof(type));
|
|
return ReadRow<object>(type, Row.Single);
|
|
}
|
|
/// <summary>
|
|
/// Read an individual row of the next grid of results
|
|
/// </summary>
|
|
public object ReadSingleOrDefault(Type type)
|
|
{
|
|
if (type == null) throw new ArgumentNullException(nameof(type));
|
|
return ReadRow<object>(type, Row.SingleOrDefault);
|
|
}
|
|
|
|
private IEnumerable<T> ReadImpl<T>(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<T>(gridIndex, deserializer.Func, typedIdentity, type);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
private T ReadRow<T>(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<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(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<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, default(CommandDefinition), func, splitOn, reader, identity, false))
|
|
{
|
|
yield return r;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
NextResult();
|
|
}
|
|
}
|
|
|
|
private IEnumerable<TReturn> MultiReadInternal<TReturn>(Type[] types, Func<object[], TReturn> map, string splitOn)
|
|
{
|
|
var identity = this.identity.ForGrid(typeof(TReturn), types, gridIndex);
|
|
try
|
|
{
|
|
foreach (var r in MultiMapImpl<TReturn>(null, default(CommandDefinition), types, map, splitOn, reader, identity, false))
|
|
{
|
|
yield return r;
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
NextResult();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> func, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(func, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Read multiple objects from a single record set on the grid
|
|
/// </summary>
|
|
public IEnumerable<TReturn> Read<TReturn>(Type[] types, Func<object[], TReturn> map, string splitOn = "id", bool buffered = true)
|
|
{
|
|
var result = MultiReadInternal<TReturn>(types, map, splitOn);
|
|
return buffered ? result.ToList() : result;
|
|
}
|
|
|
|
private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> 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;
|
|
|
|
/// <summary>
|
|
/// Has the underlying reader been consumed?
|
|
/// </summary>
|
|
public bool IsConsumed { get; private set; }
|
|
|
|
/// <summary>
|
|
/// The command associated with the reader
|
|
/// </summary>
|
|
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();
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Dispose the grid, closing and disposing both the underlying reader and command.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
if (reader != null)
|
|
{
|
|
if (!reader.IsClosed) Command?.Cancel();
|
|
reader.Dispose();
|
|
reader = null;
|
|
}
|
|
if (Command != null)
|
|
{
|
|
Command.Dispose();
|
|
Command = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|