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;
......
This diff is collapsed.
...@@ -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