Commit c0ea75a5 authored by mgravell's avatar mgravell

spike to see what would be involved in capturing the shape of zero-row grids...

spike to see what would be involved in capturing the shape of zero-row grids by moving the list (buffer) earlier
parent c1d0fd87
...@@ -9,7 +9,7 @@ namespace Dapper ...@@ -9,7 +9,7 @@ namespace Dapper
/// <summary> /// <summary>
/// Represents the key aspects of a sql operation /// Represents the key aspects of a sql operation
/// </summary> /// </summary>
public struct CommandDefinition public readonly struct CommandDefinition
{ {
internal static CommandDefinition ForCallback(object parameters) internal static CommandDefinition ForCallback(object parameters)
{ {
......
...@@ -391,7 +391,7 @@ private static Task TryOpenAsync(this IDbConnection cnn, CancellationToken cance ...@@ -391,7 +391,7 @@ private static Task TryOpenAsync(this IDbConnection cnn, CancellationToken cance
/// <summary> /// <summary>
/// Attempts setup a <see cref="DbCommand"/> on a <see cref="DbConnection"/>, with a better error message for unsupported usages. /// Attempts setup a <see cref="DbCommand"/> on a <see cref="DbConnection"/>, with a better error message for unsupported usages.
/// </summary> /// </summary>
private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action<IDbCommand, object> paramReader) private static DbCommand TrySetupAsyncCommand(in this CommandDefinition command, IDbConnection cnn, Action<IDbCommand, object> paramReader)
{ {
if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand) if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand)
{ {
...@@ -420,11 +420,15 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, ...@@ -420,11 +420,15 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
var tuple = info.Deserializer; var tuple = info.Deserializer;
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
var buffer = command.Buffered ? CreateBufferList<T>() : null;
if (tuple.Func == null || tuple.Hash != hash) if (tuple.Func == null || tuple.Hash != hash)
{ {
if (reader.FieldCount == 0) if (reader.FieldCount == 0)
return Enumerable.Empty<T>(); {
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); return buffer ?? Enumerable.Empty<T>();
}
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false, buffer as IDynamicRowList));
if (command.AddToCache) SetQueryCache(identity, info); if (command.AddToCache) SetQueryCache(identity, info);
} }
...@@ -432,7 +436,6 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, ...@@ -432,7 +436,6 @@ private static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn,
if (command.Buffered) if (command.Buffered)
{ {
var buffer = new List<T>();
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
while (await reader.ReadAsync(cancel).ConfigureAwait(false)) while (await reader.ReadAsync(cancel).ConfigureAwait(false))
{ {
...@@ -491,7 +494,7 @@ private static async Task<T> QueryRowAsync<T>(this IDbConnection cnn, Row row, T ...@@ -491,7 +494,7 @@ private static async Task<T> QueryRowAsync<T>(this IDbConnection cnn, Row row, T
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash) if (tuple.Func == null || tuple.Hash != hash)
{ {
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false, null));
if (command.AddToCache) SetQueryCache(identity, info); if (command.AddToCache) SetQueryCache(identity, info);
} }
...@@ -558,7 +561,7 @@ public static Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefinition c ...@@ -558,7 +561,7 @@ public static Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefinition c
} }
} }
private struct AsyncExecState private readonly struct AsyncExecState
{ {
public readonly DbCommand Command; public readonly DbCommand Command;
public readonly Task<int> Task; public readonly Task<int> Task;
......
...@@ -18,21 +18,20 @@ public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object ...@@ -18,21 +18,20 @@ public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object
=> new DapperRowTypeDescriptor(instance); => new DapperRowTypeDescriptor(instance);
} }
//// in theory we could implement this for zero-length results to bind; would require // in theory we could implement this for zero-length results to bind; would require
//// additional changes, though, to capture a table even when no rows - so not currently provided // additional changes, though, to capture a table even when no rows - so not currently provided
//internal sealed class DapperRowList : List<DapperRow>, ITypedList internal sealed class DapperRowList : List<object>, ITypedList, IDynamicRowList
//{ {
// private readonly DapperTable _table; private DapperTable _table;
// public DapperRowList(DapperTable table) { _table = table; } PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
// PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors) {
// { if (listAccessors != null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty;
// if (listAccessors != null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty;
return DapperRowTypeDescriptor.GetProperties(_table);
// return DapperRowTypeDescriptor.GetProperties(_table); }
// } string ITypedList.GetListName(PropertyDescriptor[] listAccessors) => null;
void IDynamicRowList.SetTable(DapperTable table) => _table = table;
// string ITypedList.GetListName(PropertyDescriptor[] listAccessors) => null; }
//}
private sealed class DapperRowTypeDescriptor : ICustomTypeDescriptor private sealed class DapperRowTypeDescriptor : ICustomTypeDescriptor
{ {
...@@ -60,17 +59,16 @@ AttributeCollection ICustomTypeDescriptor.GetAttributes() ...@@ -60,17 +59,16 @@ AttributeCollection ICustomTypeDescriptor.GetAttributes()
EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty; EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty;
internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row); internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table);
internal static PropertyDescriptorCollection GetProperties(DapperTable table, IDictionary<string,object> row = null) internal static PropertyDescriptorCollection GetProperties(DapperTable table)
{ {
string[] names = table?.FieldNames; var columns = table?.Columns;
if (names == null || names.Length == 0) return PropertyDescriptorCollection.Empty; if (columns == null || columns.Length == 0) return PropertyDescriptorCollection.Empty;
var arr = new PropertyDescriptor[names.Length]; var arr = new PropertyDescriptor[columns.Length];
for (int i = 0; i < arr.Length; i++) for (int i = 0; i < arr.Length; i++)
{ {
var type = row != null && row.TryGetValue(names[i], out var value) && value != null ref readonly DapperTable.DapperColumn col = ref columns[i];
? value.GetType() : typeof(object); arr[i] = new RowBoundPropertyDescriptor(col.Type, col.Name, i);
arr[i] = new RowBoundPropertyDescriptor(type, names[i], i);
} }
return new PropertyDescriptorCollection(arr, true); return new PropertyDescriptorCollection(arr, true);
} }
......
...@@ -81,13 +81,13 @@ public override string ToString() ...@@ -81,13 +81,13 @@ public override string ToString()
public IEnumerator<KeyValuePair<string, object>> GetEnumerator() public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{ {
var names = table.FieldNames; var columns = table.Columns;
for (var i = 0; i < names.Length; i++) for (var i = 0; i < columns.Length; i++)
{ {
object value = i < values.Length ? values[i] : null; object value = i < values.Length ? values[i] : null;
if (!(value is DeadValue)) if (!(value is DeadValue))
{ {
yield return new KeyValuePair<string, object>(names[i], value); yield return new KeyValuePair<string, object>(columns[i].Name, value);
} }
} }
} }
...@@ -174,7 +174,7 @@ private object SetValue(string key, object value, bool isAdd) ...@@ -174,7 +174,7 @@ private object SetValue(string key, object value, bool isAdd)
int index = table.IndexOfName(key); int index = table.IndexOfName(key);
if (index < 0) if (index < 0)
{ {
index = table.AddField(key); index = table.AddField(key, typeof(object));
} }
else if (isAdd && index < values.Length && !(values[index] is DeadValue)) else if (isAdd && index < values.Length && !(values[index] is DeadValue))
{ {
......
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data;
namespace Dapper namespace Dapper
{ {
public static partial class SqlMapper public static partial class SqlMapper
{ {
private sealed class DapperTable internal sealed class DapperTable
{ {
private string[] fieldNames; internal readonly struct DapperColumn
private readonly Dictionary<string, int> fieldNameLookup; {
public readonly string Name;
public readonly Type Type;
public DapperColumn(string name, Type type)
{
Name = name;
Type = type;
}
}
private DapperColumn[] _columns;
private readonly Dictionary<string, int> _fieldNameLookup;
private readonly int _readerOffset, _readerCount;
internal string[] FieldNames => fieldNames; internal DapperColumn[] Columns => _columns;
public DapperTable(string[] fieldNames) private DapperTable(DapperColumn[] columns, int offset)
{ {
this.fieldNames = fieldNames ?? throw new ArgumentNullException(nameof(fieldNames)); _readerOffset = offset;
_readerCount = columns.Length;
fieldNameLookup = new Dictionary<string, int>(fieldNames.Length, StringComparer.Ordinal); _columns = columns ?? throw new ArgumentNullException(nameof(columns));
_fieldNameLookup = new Dictionary<string, int>(_columns.Length, StringComparer.Ordinal);
// if there are dups, we want the **first** key to be the "winner" - so iterate backwards // if there are dups, we want the **first** key to be the "winner" - so iterate backwards
for (int i = fieldNames.Length - 1; i >= 0; i--) for (int i = columns.Length - 1; i >= 0; i--)
{ {
string key = fieldNames[i]; string key = columns[i].Name;
if (key != null) fieldNameLookup[key] = i; if (key != null) _fieldNameLookup[key] = i;
} }
} }
internal int IndexOfName(string name) internal int IndexOfName(string name)
{ {
return (name != null && fieldNameLookup.TryGetValue(name, out int result)) ? result : -1; return (name != null && _fieldNameLookup.TryGetValue(name, out int result)) ? result : -1;
} }
internal int AddField(string name) internal int AddField(string name, Type type)
{ {
if (name == null) throw new ArgumentNullException(nameof(name)); if (name == null) throw new ArgumentNullException(nameof(name));
if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name); if (_fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name);
int oldLen = fieldNames.Length; int oldLen = _columns.Length;
Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case Array.Resize(ref _columns, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case
fieldNames[oldLen] = name; _columns[oldLen] = new DapperColumn(name, type);
fieldNameLookup[name] = oldLen; _fieldNameLookup[name] = oldLen;
return oldLen; return oldLen;
} }
internal bool FieldExists(string key) => key != null && fieldNameLookup.ContainsKey(key); public int FieldCount => _columns.Length;
private static readonly DapperColumn[] _nixColumns = new DapperColumn[0];
internal static DapperTable Create(IDataRecord reader, int offset, int count)
{
if (count == 0) return new DapperTable(_nixColumns, offset);
var columns = new DapperColumn[count];
var colIndex = offset;
for (int i = 0; i < count; i++)
{
columns[i] = new DapperColumn(
reader.GetName(colIndex),
reader.GetFieldType(colIndex));
colIndex++;
}
return new DapperTable(columns, offset);
}
internal object AddRow(IDataReader reader)
{
object[] values = new object[_readerCount];
int offset = _readerOffset;
for(int i = 0; i < values.Length; i++)
{
object val = reader.GetValue(offset++);
values[i] = val is DBNull ? null : val;
}
return new DapperRow(this, values);
}
internal object AddRowUnlessFirstMissing(IDataReader reader)
{
int offset = _readerOffset;
object value = reader.GetValue(offset++);
if (value is DBNull) return null;
object[] values = new object[_readerCount];
values[0] = value;
for (int i = 1; i < values.Length; i++)
{
object val = reader.GetValue(offset++);
values[i] = val is DBNull ? null : val;
}
return new DapperRow(this, values);
public int FieldCount => fieldNames.Length; }
} }
} }
} }
...@@ -5,7 +5,7 @@ namespace Dapper ...@@ -5,7 +5,7 @@ namespace Dapper
{ {
public static partial class SqlMapper public static partial class SqlMapper
{ {
private struct DeserializerState private readonly struct DeserializerState
{ {
public readonly int Hash; public readonly int Hash;
public readonly Func<IDataReader, object> Func; public readonly Func<IDataReader, object> Func;
......
...@@ -165,15 +165,16 @@ private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered) ...@@ -165,15 +165,16 @@ private Task<IEnumerable<T>> ReadAsyncImpl<T>(Type type, bool buffered)
var deserializer = cache.Deserializer; var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
var buffer = buffered ? CreateBufferList<T>() : null;
if (deserializer.Func == null || deserializer.Hash != hash) if (deserializer.Func == null || deserializer.Hash != hash)
{ {
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false, buffer as IDynamicRowList));
cache.Deserializer = deserializer; cache.Deserializer = deserializer;
} }
IsConsumed = true; IsConsumed = true;
if (buffered && reader is DbDataReader) if (buffered && reader is DbDataReader)
{ {
return ReadBufferedAsync<T>(gridIndex, deserializer.Func); return ReadBufferedAsync<T>(gridIndex, deserializer.Func, buffer);
} }
else else
{ {
...@@ -207,7 +208,7 @@ private async Task<T> ReadRowAsyncImplViaDbReader<T>(DbDataReader reader, Type t ...@@ -207,7 +208,7 @@ private async Task<T> ReadRowAsyncImplViaDbReader<T>(DbDataReader reader, Type t
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash) if (deserializer.Func == null || deserializer.Hash != hash)
{ {
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false, null));
cache.Deserializer = deserializer; cache.Deserializer = deserializer;
} }
result = (T)deserializer.Func(reader); result = (T)deserializer.Func(reader);
...@@ -222,12 +223,11 @@ private async Task<T> ReadRowAsyncImplViaDbReader<T>(DbDataReader reader, Type t ...@@ -222,12 +223,11 @@ private async Task<T> ReadRowAsyncImplViaDbReader<T>(DbDataReader reader, Type t
return result; return result;
} }
private async Task<IEnumerable<T>> ReadBufferedAsync<T>(int index, Func<IDataReader, object> deserializer) private async Task<IEnumerable<T>> ReadBufferedAsync<T>(int index, Func<IDataReader, object> deserializer, List<T> buffer)
{ {
try try
{ {
var reader = (DbDataReader)this.reader; var reader = (DbDataReader)this.reader;
var buffer = new List<T>();
while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false))
{ {
buffer.Add((T)deserializer(reader)); buffer.Add((T)deserializer(reader));
......
...@@ -152,14 +152,20 @@ private IEnumerable<T> ReadImpl<T>(Type type, bool buffered) ...@@ -152,14 +152,20 @@ private IEnumerable<T> ReadImpl<T>(Type type, bool buffered)
var deserializer = cache.Deserializer; var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
var buffer = buffered ? CreateBufferList<T>() : null;
if (deserializer.Func == null || deserializer.Hash != hash) if (deserializer.Func == null || deserializer.Hash != hash)
{ {
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false, buffer as IDynamicRowList));
cache.Deserializer = deserializer; cache.Deserializer = deserializer;
} }
IsConsumed = true; IsConsumed = true;
var result = ReadDeferred<T>(gridIndex, deserializer.Func, type); var result = ReadDeferred<T>(gridIndex, deserializer.Func, type);
return buffered ? result.ToList() : result; if (buffered)
{
buffer.AddRange(result);
return buffer;
}
return result;
} }
private T ReadRow<T>(Type type, Row row) private T ReadRow<T>(Type type, Row row)
...@@ -178,7 +184,7 @@ private T ReadRow<T>(Type type, Row row) ...@@ -178,7 +184,7 @@ private T ReadRow<T>(Type type, Row row)
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
if (deserializer.Func == null || deserializer.Hash != hash) if (deserializer.Func == null || deserializer.Hash != hash)
{ {
deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false, null));
cache.Deserializer = deserializer; cache.Deserializer = deserializer;
} }
object val = deserializer.Func(reader); object val = deserializer.Func(reader);
......
...@@ -16,7 +16,7 @@ public static IEnumerable<T> Parse<T>(this IDataReader reader) ...@@ -16,7 +16,7 @@ public static IEnumerable<T> Parse<T>(this IDataReader reader)
if (reader.Read()) if (reader.Read())
{ {
var effectiveType = typeof(T); var effectiveType = typeof(T);
var deser = GetDeserializer(effectiveType, reader, 0, -1, false); var deser = GetDeserializer(effectiveType, reader, 0, -1, false, null);
var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType;
do do
{ {
...@@ -42,7 +42,7 @@ public static IEnumerable<object> Parse(this IDataReader reader, Type type) ...@@ -42,7 +42,7 @@ public static IEnumerable<object> Parse(this IDataReader reader, Type type)
{ {
if (reader.Read()) if (reader.Read())
{ {
var deser = GetDeserializer(type, reader, 0, -1, false); var deser = GetDeserializer(type, reader, 0, -1, false, null);
do do
{ {
yield return deser(reader); yield return deser(reader);
...@@ -58,7 +58,7 @@ public static IEnumerable<dynamic> Parse(this IDataReader reader) ...@@ -58,7 +58,7 @@ public static IEnumerable<dynamic> Parse(this IDataReader reader)
{ {
if (reader.Read()) if (reader.Read())
{ {
var deser = GetDapperRowDeserializer(reader, 0, -1, false); var deser = GetDapperRowDeserializer(reader, 0, -1, false, null);
do do
{ {
yield return deser(reader); yield return deser(reader);
...@@ -79,7 +79,7 @@ public static IEnumerable<dynamic> Parse(this IDataReader reader) ...@@ -79,7 +79,7 @@ public static IEnumerable<dynamic> Parse(this IDataReader reader)
public static Func<IDataReader, object> GetRowParser(this IDataReader reader, Type type, public static Func<IDataReader, object> GetRowParser(this IDataReader reader, Type type,
int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false)
{ {
return GetDeserializer(type, reader, startIndex, length, returnNullIfFirstMissing); return GetDeserializer(type, reader, startIndex, length, returnNullIfFirstMissing, null);
} }
/// <summary> /// <summary>
...@@ -139,7 +139,7 @@ public static IEnumerable<dynamic> Parse(this IDataReader reader) ...@@ -139,7 +139,7 @@ public static IEnumerable<dynamic> Parse(this IDataReader reader)
int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false)
{ {
concreteType = concreteType ?? typeof(T); concreteType = concreteType ?? typeof(T);
var func = GetDeserializer(concreteType, reader, startIndex, length, returnNullIfFirstMissing); var func = GetDeserializer(concreteType, reader, startIndex, length, returnNullIfFirstMissing, null);
if (concreteType.IsValueType()) if (concreteType.IsValueType())
{ {
return _ => (T)func(_); return _ => (T)func(_);
......
...@@ -52,7 +52,7 @@ internal static void Purge() ...@@ -52,7 +52,7 @@ internal static void Purge()
private readonly Dictionary<DeserializerKey, Func<IDataReader, object>> readers = new Dictionary<DeserializerKey, Func<IDataReader, object>>(); private readonly Dictionary<DeserializerKey, Func<IDataReader, object>> readers = new Dictionary<DeserializerKey, Func<IDataReader, object>>();
private struct DeserializerKey : IEquatable<DeserializerKey> private readonly struct DeserializerKey : IEquatable<DeserializerKey>
{ {
private readonly int startBound, length; private readonly int startBound, length;
private readonly bool returnNullIfFirstMissing; private readonly bool returnNullIfFirstMissing;
......
...@@ -647,7 +647,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio ...@@ -647,7 +647,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
/// <param name="commandType">The type of command to execute.</param> /// <param name="commandType">The type of command to execute.</param>
/// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <remarks>Note: each row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) => public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) =>
Query<DapperRow>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType); Query<object>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType);
/// <summary> /// <summary>
/// Return a dynamic object with properties matching the columns. /// Return a dynamic object with properties matching the columns.
...@@ -660,7 +660,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio ...@@ -660,7 +660,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
/// <param name="commandType">The type of command to execute.</param> /// <param name="commandType">The type of command to execute.</param>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static dynamic QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => public static dynamic QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
QueryFirst<DapperRow>(cnn, sql, param as object, transaction, commandTimeout, commandType); QueryFirst<object>(cnn, sql, param as object, transaction, commandTimeout, commandType);
/// <summary> /// <summary>
/// Return a dynamic object with properties matching the columns. /// Return a dynamic object with properties matching the columns.
...@@ -673,7 +673,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio ...@@ -673,7 +673,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
/// <param name="commandType">The type of command to execute.</param> /// <param name="commandType">The type of command to execute.</param>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
QueryFirstOrDefault<DapperRow>(cnn, sql, param as object, transaction, commandTimeout, commandType); QueryFirstOrDefault<object>(cnn, sql, param as object, transaction, commandTimeout, commandType);
/// <summary> /// <summary>
/// Return a dynamic object with properties matching the columns. /// Return a dynamic object with properties matching the columns.
...@@ -686,7 +686,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio ...@@ -686,7 +686,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
/// <param name="commandType">The type of command to execute.</param> /// <param name="commandType">The type of command to execute.</param>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static dynamic QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => public static dynamic QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
QuerySingle<DapperRow>(cnn, sql, param as object, transaction, commandTimeout, commandType); QuerySingle<object>(cnn, sql, param as object, transaction, commandTimeout, commandType);
/// <summary> /// <summary>
/// Return a dynamic object with properties matching the columns. /// Return a dynamic object with properties matching the columns.
...@@ -699,7 +699,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio ...@@ -699,7 +699,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
/// <param name="commandType">The type of command to execute.</param> /// <param name="commandType">The type of command to execute.</param>
/// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks> /// <remarks>Note: the row can be accessed via "dynamic", or by casting to an IDictionary&lt;string,object&gt;</remarks>
public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) =>
QuerySingleOrDefault<DapperRow>(cnn, sql, param as object, transaction, commandTimeout, commandType); QuerySingleOrDefault<object>(cnn, sql, param as object, transaction, commandTimeout, commandType);
/// <summary> /// <summary>
/// Executes a query, returning the data typed as <typeparamref name="T"/>. /// Executes a query, returning the data typed as <typeparamref name="T"/>.
...@@ -719,8 +719,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio ...@@ -719,8 +719,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio
public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
{ {
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<T>(cnn, command, typeof(T)); return command.Buffered ? QueryBufferedImpl<T>(cnn, command, typeof(T)) : QueryImpl<T>(cnn, command, typeof(T), null);
return command.Buffered ? data.ToList() : data;
} }
/// <summary> /// <summary>
...@@ -823,8 +822,7 @@ public static IEnumerable<object> Query(this IDbConnection cnn, Type type, strin ...@@ -823,8 +822,7 @@ public static IEnumerable<object> Query(this IDbConnection cnn, Type type, strin
{ {
if (type == null) throw new ArgumentNullException(nameof(type)); if (type == null) throw new ArgumentNullException(nameof(type));
var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None);
var data = QueryImpl<object>(cnn, command, type); return command.Buffered ? QueryBufferedImpl<object>(cnn, command, type) : QueryImpl<object>(cnn, command, type, null);
return command.Buffered ? data.ToList() : data;
} }
/// <summary> /// <summary>
...@@ -926,10 +924,7 @@ public static object QuerySingleOrDefault(this IDbConnection cnn, Type type, str ...@@ -926,10 +924,7 @@ public static object QuerySingleOrDefault(this IDbConnection cnn, Type type, str
/// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
/// </returns> /// </returns>
public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition command) public static IEnumerable<T> Query<T>(this IDbConnection cnn, CommandDefinition command)
{ => command.Buffered ? QueryBufferedImpl<T>(cnn, command, typeof(T)) : QueryImpl<T>(cnn, command, typeof(T), null);
var data = QueryImpl<T>(cnn, command, typeof(T));
return command.Buffered ? data.ToList() : data;
}
/// <summary> /// <summary>
/// Executes a query, returning the data typed as <typeparamref name="T"/>. /// Executes a query, returning the data typed as <typeparamref name="T"/>.
...@@ -1063,7 +1058,30 @@ private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool w ...@@ -1063,7 +1058,30 @@ private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool w
} }
} }
private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType) internal interface IDynamicRowList
{
void SetTable(DapperTable table);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static List<T> CreateBufferList<T>()
{
#if !NETSTANDARD1_3 // needs the component-model API
if (typeof(T) == typeof(object))
{
return (List<T>)(object)new DapperRow.DapperRowList();
}
#endif
return new List<T>();
}
private static IList<T> QueryBufferedImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType)
{
List<T> list = CreateBufferList<T>();
list.AddRange(QueryImpl<T>(cnn, command, effectiveType, list));
return list;
}
private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefinition command, Type effectiveType, List<T> buffer)
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null);
...@@ -1088,8 +1106,10 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1088,8 +1106,10 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
if (tuple.Func == null || tuple.Hash != hash) if (tuple.Func == null || tuple.Hash != hash)
{ {
if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57 if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57
{
yield break; yield break;
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); }
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false, buffer as IDynamicRowList));
if (command.AddToCache) SetQueryCache(identity, info); if (command.AddToCache) SetQueryCache(identity, info);
} }
...@@ -1098,13 +1118,22 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1098,13 +1118,22 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
while (reader.Read()) while (reader.Read())
{ {
object val = func(reader); object val = func(reader);
T typed;
if (val == null || val is T) if (val == null || val is T)
{ {
yield return (T)val; typed = (T)val;
} }
else else
{ {
yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); typed = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture);
}
if (buffer != null)
{
buffer.Add(typed);
}
else
{
yield return typed;
} }
} }
while (reader.NextResult()) { /* ignore subsequent result sets */ } while (reader.NextResult()) { /* ignore subsequent result sets */ }
...@@ -1191,7 +1220,7 @@ private static T QueryRowImpl<T>(IDbConnection cnn, Row row, ref CommandDefiniti ...@@ -1191,7 +1220,7 @@ private static T QueryRowImpl<T>(IDbConnection cnn, Row row, ref CommandDefiniti
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
if (tuple.Func == null || tuple.Hash != hash) if (tuple.Func == null || tuple.Hash != hash)
{ {
tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false, null));
if (command.AddToCache) SetQueryCache(identity, info); if (command.AddToCache) SetQueryCache(identity, info);
} }
...@@ -1596,7 +1625,7 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn ...@@ -1596,7 +1625,7 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn
{ {
currentSplit = splits[++splitIdx]; currentSplit = splits[++splitIdx];
} }
deserializers.Add(GetDeserializer(type, reader, currentPos, splitPoint - currentPos, !first)); deserializers.Add(GetDeserializer(type, reader, currentPos, splitPoint - currentPos, !first, null));
currentPos = splitPoint; currentPos = splitPoint;
first = false; first = false;
} }
...@@ -1626,7 +1655,7 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn ...@@ -1626,7 +1655,7 @@ private static IEnumerable<TReturn> MultiMapImpl<TReturn>(this IDbConnection cnn
} }
} }
deserializers.Add(GetDeserializer(type, reader, splitPoint, currentPos - splitPoint, typeIdx > 0)); deserializers.Add(GetDeserializer(type, reader, splitPoint, currentPos - splitPoint, typeIdx > 0, null));
currentPos = splitPoint; currentPos = splitPoint;
} }
...@@ -1767,12 +1796,12 @@ private static void PassByPosition(IDbCommand cmd) ...@@ -1767,12 +1796,12 @@ private static void PassByPosition(IDbCommand cmd)
}); });
} }
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing, IDynamicRowList rowList)
{ {
// dynamic is passed in as Object ... by c# design // dynamic is passed in as Object ... by c# design
if (type == typeof(object) || type == typeof(DapperRow)) if (type == typeof(object) || type == typeof(DapperRow))
{ {
return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing, rowList);
} }
Type underlyingType = null; Type underlyingType = null;
if (!(typeMap.ContainsKey(type) || type.IsEnum() || type.FullName == LinqBinary if (!(typeMap.ContainsKey(type) || type.IsEnum() || type.FullName == LinqBinary
...@@ -1803,7 +1832,7 @@ private static Exception MultiMapException(IDataRecord reader) ...@@ -1803,7 +1832,7 @@ private static Exception MultiMapException(IDataRecord reader)
return new InvalidOperationException("No columns were selected"); return new InvalidOperationException("No columns were selected");
} }
internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing, IDynamicRowList rowList)
{ {
var fieldCount = reader.FieldCount; var fieldCount = reader.FieldCount;
if (length == -1) if (length == -1)
...@@ -1818,51 +1847,11 @@ private static Exception MultiMapException(IDataRecord reader) ...@@ -1818,51 +1847,11 @@ private static Exception MultiMapException(IDataRecord reader)
var effectiveFieldCount = Math.Min(fieldCount - startBound, length); var effectiveFieldCount = Math.Min(fieldCount - startBound, length);
DapperTable table = null; DapperTable table = DapperTable.Create(reader, startBound, effectiveFieldCount);
rowList?.SetTable(table);
return
r =>
{
if (table == null)
{
string[] names = new string[effectiveFieldCount];
for (int i = 0; i < effectiveFieldCount; i++)
{
names[i] = r.GetName(i + startBound);
}
table = new DapperTable(names);
}
var values = new object[effectiveFieldCount];
if (returnNullIfFirstMissing)
{
values[0] = r.GetValue(startBound);
if (values[0] is DBNull)
{
return null;
}
}
if (startBound == 0) if (returnNullIfFirstMissing) return table.AddRowUnlessFirstMissing;
{ return table.AddRow;
for (int i = 0; i < values.Length; i++)
{
object val = r.GetValue(i);
values[i] = val is DBNull ? null : val;
}
}
else
{
var begin = returnNullIfFirstMissing ? 1 : 0;
for (var iter = begin; iter < effectiveFieldCount; ++iter)
{
object obj = r.GetValue(iter + startBound);
values[iter] = obj is DBNull ? null : obj;
}
}
return new DapperRow(table, values);
};
} }
/// <summary> /// <summary>
/// Internal use only. /// Internal use only.
......
...@@ -13,7 +13,7 @@ public BindingForm() ...@@ -13,7 +13,7 @@ public BindingForm()
SuspendLayout(); SuspendLayout();
using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated Security=SSPI")) using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated Security=SSPI"))
{ {
mainGrid.DataSource = conn.Query("select * from sys.objects").AsList(); mainGrid.DataSource = conn.Query("select * from sys.objects where 1 = 0").AsList();
} }
ResumeLayout(); ResumeLayout();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment