Skip to content

Commit

Permalink
New IDataReader APIs: GetRowParser, Parse; add cache around GetTypeDe…
Browse files Browse the repository at this point in the history
…serializer that is unrelated to the query itself (just dependent on the shape of the results)
  • Loading branch information
mgravell committed Jan 2, 2016
1 parent c80e9d1 commit 8747316
Show file tree
Hide file tree
Showing 4 changed files with 274 additions and 4 deletions.
44 changes: 44 additions & 0 deletions Dapper.Tests/Tests.IDataReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Xunit;

namespace Dapper.Tests
{
public partial class TestSuite
{
[Fact]
public void GetSameReaderForSameShape()
{
var origReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id");
var origParser = origReader.GetRowParser(typeof(HazNameId));

var list = origReader.Parse<HazNameId>().ToList();
list.Count.IsEqualTo(1);
list[0].Name.IsEqualTo("abc");
list[0].Id.IsEqualTo(123);
origReader.Dispose();

var secondReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id");
var secondParser = secondReader.GetRowParser(typeof(HazNameId));
var thirdParser = secondReader.GetRowParser(typeof(HazNameId), 1);

list = secondReader.Parse<HazNameId>().ToList();
list.Count.IsEqualTo(1);
list[0].Name.IsEqualTo("abc");
list[0].Id.IsEqualTo(123);
secondReader.Dispose();

// now: should be different readers, but same parser
ReferenceEquals(origReader, secondReader).IsEqualTo(false);
ReferenceEquals(origParser, secondParser).IsEqualTo(true);
ReferenceEquals(secondParser, thirdParser).IsEqualTo(false);
}
public class HazNameId
{
public string Name { get; set; }
public int Id { get; set; }
}
}
}
53 changes: 53 additions & 0 deletions Dapper/SqlMapper.IDataReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Data;

#if COREFX
using IDataReader = System.Data.Common.DbDataReader;
#endif

namespace Dapper
{
partial class SqlMapper
{
public static IEnumerable<T> Parse<T>(this IDataReader reader)
{
if(reader.Read())
{
var deser = GetDeserializer(typeof(T), reader, 0, -1, false);
do
{
yield return (T)deser(reader);
} while (reader.Read());
}
}
public static IEnumerable<object> Parse(this IDataReader reader, Type type)
{
if (reader.Read())
{
var deser = GetDeserializer(type, reader, 0, -1, false);
do
{
yield return deser(reader);
} while (reader.Read());
}
}
public static IEnumerable<dynamic> Parse(this IDataReader reader)
{
if (reader.Read())
{
var deser = GetDapperRowDeserializer(reader, 0, -1, false);
do
{
yield return deser(reader);
} while (reader.Read());
}
}

public static Func<IDataReader, object> GetRowParser(this IDataReader reader, Type type,
int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false)
{
return GetDeserializer(type, reader, startIndex, length, returnNullIfFirstMissing);
}
}
}
164 changes: 164 additions & 0 deletions Dapper/SqlMapper.TypeDeserializerCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
#if COREFX
using IDataReader = System.Data.Common.DbDataReader;
#endif

using System;
using System.Data;
using System.Collections;
using System.Collections.Generic;
using System.Text;

namespace Dapper
{
partial class SqlMapper
{

private class TypeDeserializerCache
{
private TypeDeserializerCache(Type type)
{
this.type = type;
}
static readonly Hashtable byType = new Hashtable();
private readonly Type type;
internal static void Purge(Type type)
{
lock (byType)
{
byType.Remove(type);
}
}
internal static void Purge()
{
lock (byType)
{
byType.Clear();
}
}

internal static Func<IDataReader, object> GetReader(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
var found = (TypeDeserializerCache)byType[type];
if (found == null)
{
lock (byType)
{
found = (TypeDeserializerCache)byType[type];
if (found == null)
{
byType[type] = found = new TypeDeserializerCache(type);
}
}
}
return found.GetReader(reader, startBound, length, returnNullIfFirstMissing);
}
private Dictionary<DeserializerKey, Func<IDataReader, object>> readers = new Dictionary<DeserializerKey, Func<IDataReader, object>>();
struct DeserializerKey : IEquatable<DeserializerKey>
{
private readonly int startBound, length;
private readonly bool returnNullIfFirstMissing;
private readonly IDataReader reader;
private readonly string[] names;
private readonly Type[] types;
private readonly int hashCode;

public DeserializerKey(int hashCode, int startBound, int length, bool returnNullIfFirstMissing, IDataReader reader, bool copyDown)
{
this.hashCode = hashCode;
this.startBound = startBound;
this.length = length;
this.returnNullIfFirstMissing = returnNullIfFirstMissing;

if (copyDown)
{
this.reader = null;
names = new string[length];
types = new Type[length];
int index = startBound;
for (int i = 0; i < length; i++)
{
names[i] = reader.GetName(index);
types[i] = reader.GetFieldType(index++);
}
}
else
{
this.reader = reader;
names = null;
types = null;
}
}

public override int GetHashCode()
{
return hashCode;
}
public override string ToString()
{ // only used in the debugger
if (names != null)
{
return string.Join(", ", names);
}
if (reader != null)
{
var sb = new StringBuilder();
int index = startBound;
for (int i = 0; i < length; i++)
{
if (i != 0) sb.Append(", ");
sb.Append(reader.GetName(index++));
}
return sb.ToString();
}
return base.ToString();
}
public override bool Equals(object obj)
{
return obj is DeserializerKey && Equals((DeserializerKey)obj);
}
public bool Equals(DeserializerKey other)
{
if (this.hashCode != other.hashCode
|| this.startBound != other.startBound
|| this.length != other.length
|| this.returnNullIfFirstMissing != other.returnNullIfFirstMissing)
{
return false; // clearly different
}
for (int i = 0; i < length; i++)
{
if ((this.names?[i] ?? this.reader?.GetName(startBound + i)) != (other.names?[i] ?? other.reader?.GetName(startBound + i))
||
(this.types?[i] ?? this.reader?.GetFieldType(startBound + i)) != (other.types?[i] ?? other.reader?.GetFieldType(startBound + i))
)
{
return false; // different column name or type
}
}
return true;
}
}
private Func<IDataReader, object> GetReader(IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
if (length < 0) length = reader.FieldCount - startBound;
int hash = GetColumnHash(reader, startBound, length);
if (returnNullIfFirstMissing) hash *= -27;
// get a cheap key first: false means don't copy the values down
var key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, false);
Func<IDataReader, object> deser;
lock (readers)
{
if (readers.TryGetValue(key, out deser)) return deser;
}
deser = GetTypeDeserializerImpl(type, reader, startBound, length, returnNullIfFirstMissing);
// get a more expensive key: true means copy the values down so it can be used as a key later
key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, true);
lock (readers)
{
return readers[key] = deser;
}
}
}

}
}
17 changes: 13 additions & 4 deletions Dapper/SqlMapper.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
License: http://www.apache.org/licenses/LICENSE-2.0
Home page: http://code.google.com/p/dapper-dot-net/
Home page: https://github.com/StackExchange/dapper-dot-net
*/

#if COREFX
Expand Down Expand Up @@ -37,12 +37,13 @@ namespace Dapper
/// </summary>
public static partial class SqlMapper
{
static int GetColumnHash(IDataReader reader)
static int GetColumnHash(IDataReader reader, int startBound = 0, int length = -1)
{
unchecked
{
int colCount = reader.FieldCount, hash = colCount;
for (int i = 0; i < colCount; i++)
int max = length < 0 ? reader.FieldCount : startBound + length;
int hash = (-37 * startBound) + max;
for (int i = startBound; i < max; i++)
{
object tmp = reader.GetName(i);
hash = -79 * ((hash * 31) + (tmp?.GetHashCode() ?? 0)) + (reader.GetFieldType(i)?.GetHashCode() ?? 0);
Expand Down Expand Up @@ -111,6 +112,7 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value)
public static void PurgeQueryCache()
{
_queryCache.Clear();
TypeDeserializerCache.Purge();
OnQueryCachePurged();
}

Expand All @@ -122,6 +124,7 @@ private static void PurgeQueryCacheByType(Type type)
if (entry.Key.type == type)
_queryCache.TryRemove(entry.Key, out cache);
}
TypeDeserializerCache.Purge(type);
}

/// <summary>
Expand Down Expand Up @@ -2641,6 +2644,12 @@ public static void SetTypeMap(Type type, ITypeMap map)
public static Func<IDataReader, object> GetTypeDeserializer(
Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
)
{
return TypeDeserializerCache.GetReader(type, reader, startBound, length, returnNullIfFirstMissing);
}
private static Func<IDataReader, object> GetTypeDeserializerImpl(
Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
)
{
var dm = new DynamicMethod($"Deserialize{Guid.NewGuid()}", typeof(object), new[] { typeof(IDataReader) }, true);
var il = dm.GetILGenerator();
Expand Down

0 comments on commit 8747316

Please sign in to comment.