Commit 87473160 authored by Marc Gravell's avatar Marc Gravell

New IDataReader APIs: GetRowParser, Parse; add cache around...

New IDataReader APIs: GetRowParser, Parse; add cache around GetTypeDeserializer that is unrelated to the query itself (just dependent on the shape of the results)
parent c80e9d17
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; }
}
}
}
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);
}
}
}
#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;
}
}
}
}
}
/* /*
License: http://www.apache.org/licenses/LICENSE-2.0 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 #if COREFX
...@@ -37,12 +37,13 @@ namespace Dapper ...@@ -37,12 +37,13 @@ namespace Dapper
/// </summary> /// </summary>
public static partial class SqlMapper public static partial class SqlMapper
{ {
static int GetColumnHash(IDataReader reader) static int GetColumnHash(IDataReader reader, int startBound = 0, int length = -1)
{ {
unchecked unchecked
{ {
int colCount = reader.FieldCount, hash = colCount; int max = length < 0 ? reader.FieldCount : startBound + length;
for (int i = 0; i < colCount; i++) int hash = (-37 * startBound) + max;
for (int i = startBound; i < max; i++)
{ {
object tmp = reader.GetName(i); object tmp = reader.GetName(i);
hash = -79 * ((hash * 31) + (tmp?.GetHashCode() ?? 0)) + (reader.GetFieldType(i)?.GetHashCode() ?? 0); hash = -79 * ((hash * 31) + (tmp?.GetHashCode() ?? 0)) + (reader.GetFieldType(i)?.GetHashCode() ?? 0);
...@@ -111,6 +112,7 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value) ...@@ -111,6 +112,7 @@ private static bool TryGetQueryCache(Identity key, out CacheInfo value)
public static void PurgeQueryCache() public static void PurgeQueryCache()
{ {
_queryCache.Clear(); _queryCache.Clear();
TypeDeserializerCache.Purge();
OnQueryCachePurged(); OnQueryCachePurged();
} }
...@@ -122,6 +124,7 @@ private static void PurgeQueryCacheByType(Type type) ...@@ -122,6 +124,7 @@ private static void PurgeQueryCacheByType(Type type)
if (entry.Key.type == type) if (entry.Key.type == type)
_queryCache.TryRemove(entry.Key, out cache); _queryCache.TryRemove(entry.Key, out cache);
} }
TypeDeserializerCache.Purge(type);
} }
/// <summary> /// <summary>
...@@ -2641,6 +2644,12 @@ public static void SetTypeMap(Type type, ITypeMap map) ...@@ -2641,6 +2644,12 @@ public static void SetTypeMap(Type type, ITypeMap map)
public static Func<IDataReader, object> GetTypeDeserializer( public static Func<IDataReader, object> GetTypeDeserializer(
Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false 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 dm = new DynamicMethod($"Deserialize{Guid.NewGuid()}", typeof(object), new[] { typeof(IDataReader) }, true);
var il = dm.GetILGenerator(); var il = dm.GetILGenerator();
......
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