-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New IDataReader APIs: GetRowParser, Parse; add cache around GetTypeDe…
…serializer that is unrelated to the query itself (just dependent on the shape of the results)
- Loading branch information
Showing
4 changed files
with
274 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; } | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} | ||
|
||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters