Commit 41f276d4 authored by Sam Saffron's avatar Sam Saffron

completed multi map implementation

parent 41305935
...@@ -21,7 +21,7 @@ public static class SqlMapper ...@@ -21,7 +21,7 @@ public static class SqlMapper
class CacheInfo class CacheInfo
{ {
public object Deserializer { get; set; } public object Deserializer { get; set; }
public object Deserializer2 { get; set; } public object[] OtherDeserializers { get; set; }
public Action<IDbCommand, object> ParamReader { get; set; } public Action<IDbCommand, object> ParamReader { get; set; }
} }
...@@ -94,19 +94,25 @@ private class Identity : IEquatable<Identity> ...@@ -94,19 +94,25 @@ private class Identity : IEquatable<Identity>
public Type Type2 { get { return Type2; } } public Type Type2 { get { return Type2; } }
public string Sql { get { return sql; } } public string Sql { get { return sql; } }
public Type ParametersType { get { return ParametersType; } } public Type ParametersType { get { return ParametersType; } }
internal Identity(string sql, IDbConnection cnn, Type type, Type parametersType, Type type2 = null) internal Identity(string sql, IDbConnection cnn, Type type, Type parametersType, Type[] otherTypes = null)
{ {
this.sql = sql; this.sql = sql;
this.connectionString = cnn.ConnectionString; this.connectionString = cnn.ConnectionString;
this.type = type; this.type = type;
this.parametersType = parametersType; this.parametersType = parametersType;
this.type2 = type2; this.otherTypes = otherTypes;
unchecked unchecked
{ {
hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this
hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode()); hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode());
hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode()); hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode());
hashCode = hashCode * 23 + (type2 == null ? 0 : type2.GetHashCode()); if (otherTypes != null)
{
for (int i = 0; i < otherTypes.Length; i++)
{
hashCode = hashCode * 23 + (otherTypes[i] == null ? 0 : otherTypes[i].GetHashCode());
}
}
hashCode = hashCode * 23 + (connectionString == null ? 0 : connectionString.GetHashCode()); hashCode = hashCode * 23 + (connectionString == null ? 0 : connectionString.GetHashCode());
hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode()); hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode());
} }
...@@ -118,7 +124,7 @@ public override bool Equals(object obj) ...@@ -118,7 +124,7 @@ public override bool Equals(object obj)
private readonly string sql; private readonly string sql;
private readonly int hashCode; private readonly int hashCode;
private readonly Type type; private readonly Type type;
private readonly Type type2; private readonly Type[] otherTypes;
private readonly string connectionString; private readonly string connectionString;
private readonly Type parametersType; private readonly Type parametersType;
public override int GetHashCode() public override int GetHashCode()
...@@ -169,7 +175,6 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object ...@@ -169,7 +175,6 @@ public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object
} }
} }
/// <summary> /// <summary>
/// Return a typed list of objects, reader is closed after the call /// Return a typed list of objects, reader is closed after the call
/// </summary> /// </summary>
...@@ -199,9 +204,9 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -199,9 +204,9 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
} }
/// <summary> /// <summary>
/// /// Maps a query to objects
/// </summary> /// </summary>
/// <typeparam name="T"></typeparam> /// <typeparam name="T">The return type</typeparam>
/// <typeparam name="U"></typeparam> /// <typeparam name="U"></typeparam>
/// <param name="cnn"></param> /// <param name="cnn"></param>
/// <param name="sql"></param> /// <param name="sql"></param>
...@@ -212,7 +217,28 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -212,7 +217,28 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
/// <returns></returns> /// <returns></returns>
public static IEnumerable<T> Query<T, U>(this IDbConnection cnn, string sql, Action<T, U> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id") public static IEnumerable<T> Query<T, U>(this IDbConnection cnn, string sql, Action<T, U> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id")
{ {
var identity = new Identity(sql, cnn, typeof(T), param == null ? null : param.GetType()); return MultiMap<T,U,DontMap, DontMap, DontMap>(cnn, sql, map, param, transaction, buffered, splitOn);
}
public static IEnumerable<T> Query<T, U, V>(this IDbConnection cnn, string sql, Action<T, U, V> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id")
{
return MultiMap<T,U,V, DontMap, DontMap>(cnn, sql, map, param, transaction, buffered, splitOn);
}
public static IEnumerable<T> Query<T, U, V, Z>(this IDbConnection cnn, string sql, Action<T, U, V, Z> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id")
{
return MultiMap<T,U, V, Z, DontMap>(cnn, sql, map, param, transaction, buffered, splitOn);
}
public static IEnumerable<T> Query<T, U, V, Z, X>(this IDbConnection cnn, string sql, Action<T, U, V, Z, X> map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id")
{
return MultiMap<T,U,V,Z,X>(cnn, sql, map, param, transaction, buffered, splitOn);
}
class DontMap {}
static IEnumerable<T> MultiMap<T, U, V, Z, X>(this IDbConnection cnn, string sql, object map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id")
{
var identity = new Identity(sql, cnn, typeof(T), param == null ? null : param.GetType(), otherTypes: new Type[] {typeof(T), typeof(U), typeof(V), typeof(Z), typeof(X) });
var info = GetCacheInfo(param, identity); var info = GetCacheInfo(param, identity);
using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param)) using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param))
...@@ -221,47 +247,115 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq ...@@ -221,47 +247,115 @@ private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sq
{ {
if (info.Deserializer == null) if (info.Deserializer == null)
{ {
int start = 0; var split = 0;
int length = -1; int current = 0;
for (length = 1; length < reader.FieldCount; length++) Func<int> nextSplit = () =>
{ {
if (reader.GetName(length) == splitOn) int pos;
for (pos = current + 1; pos < reader.FieldCount; pos++)
{ {
break; if (reader.GetName(pos) == splitOn)
{
break;
}
} }
current = pos;
return pos;
};
List<object> otherDeserializer = new List<object>();
split = nextSplit();
info.Deserializer = GetDeserializer<T>(identity, reader, 0, split);
if (typeof(U) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<U>(identity, reader, split, next - split, returnNullIfFirstMissing: true));
split = next;
}
if (typeof(V) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<V>(identity, reader, split, next - split, returnNullIfFirstMissing: true));
split = next;
}
if (typeof(Z) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<Z>(identity, reader, split, next - split, returnNullIfFirstMissing: true));
split = next;
}
if (typeof(X) != typeof(DontMap))
{
var next = nextSplit();
otherDeserializer.Add(GetDeserializer<X>(identity, reader, split, next - split, returnNullIfFirstMissing: true));
} }
// dynamic comes back as object ... info.OtherDeserializers = otherDeserializer.ToArray();
if (typeof(T) == typeof(object))
{ queryCache[identity] = info;
info.Deserializer = GetDeserializer<ExpandoObject>(identity, reader, start, length);
}
else
{
info.Deserializer = GetDeserializer<T>(identity, reader, start, length);
} }
if (typeof(U) == typeof(object)) var deserializer = (Func<IDataReader, T>)info.Deserializer;
var deserializer2 = (Func<IDataReader, U>)info.OtherDeserializers[0];
Func<IDataReader,T> mapIt = null;
if (info.OtherDeserializers.Length == 1)
{ {
info.Deserializer2 = GetDeserializer<ExpandoObject>(identity, reader, start + length,returnNullIfFirstMissing:true); mapIt = r =>
{
var tmp = deserializer(r);
((Action<T, U>)map)(tmp, deserializer2(r));
return tmp;
};
} }
else
if (info.OtherDeserializers.Length > 1)
{ {
info.Deserializer2 = GetDeserializer<U>(identity, reader, start + length, returnNullIfFirstMissing: true); var deserializer3 = (Func<IDataReader, V>)info.OtherDeserializers[1];
}
queryCache[identity] = info; if (info.OtherDeserializers.Length == 2)
} {
mapIt = r =>
{
var tmp = deserializer(r);
((Action<T, U, V>)map)(tmp, deserializer2(r), deserializer3(r));
return tmp;
};
}
if (info.OtherDeserializers.Length > 2)
{
var deserializer4 = (Func<IDataReader, Z>)info.OtherDeserializers[2];
if (info.OtherDeserializers.Length == 3)
{
mapIt = r =>
{
var tmp = deserializer(r);
((Action<T, U, V, Z>)map)(tmp, deserializer2(r), deserializer3(r), deserializer4(r));
return tmp;
};
}
var deserializer = (Func<IDataReader, T>) info.Deserializer; if (info.OtherDeserializers.Length > 3)
var deserializer2 = (Func<IDataReader, U>) info.Deserializer2; {
var deserializer5 = (Func<IDataReader, X>)info.OtherDeserializers[3];
mapIt = r =>
{
var tmp = deserializer(r);
((Action<T, U, V, Z, X>)map)(tmp, deserializer2(r), deserializer3(r), deserializer4(r), deserializer5(r));
return tmp;
};
}
}
}
while (reader.Read()) while (reader.Read())
{ {
var tmp = deserializer(reader); yield return mapIt(reader);
map(tmp, deserializer2(reader));
yield return tmp;
} }
} }
} }
...@@ -281,17 +375,12 @@ private static CacheInfo GetCacheInfo(object param, Identity identity) ...@@ -281,17 +375,12 @@ private static CacheInfo GetCacheInfo(object param, Identity identity)
return info; return info;
} }
class DynamicStub
{
public static Type Type = typeof(DynamicStub);
}
static Func<IDataReader, T> GetDeserializer<T>(Identity identity, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) static Func<IDataReader, T> GetDeserializer<T>(Identity identity, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false)
{ {
object oDeserializer; object oDeserializer;
if (typeof(T) == DynamicStub.Type || typeof(T) == typeof(ExpandoObject)) // dynamic is passed in as Object ... by c# design
if (typeof(T) == typeof(object) || typeof(T) == typeof(ExpandoObject))
{ {
oDeserializer = GetDynamicDeserializer(reader,startBound, length, returnNullIfFirstMissing); oDeserializer = GetDynamicDeserializer(reader,startBound, length, returnNullIfFirstMissing);
} }
......
...@@ -6,7 +6,7 @@ ...@@ -6,7 +6,7 @@
namespace SqlMapper namespace SqlMapper
{ {
static class TestAssertions static class Assert
{ {
public static void IsEqualTo<T>(this T obj, T other) public static void IsEqualTo<T>(this T obj, T other)
...@@ -320,6 +320,44 @@ public void TestMultiMapDynamic() ...@@ -320,6 +320,44 @@ public void TestMultiMapDynamic()
connection.Execute("drop table #Users drop table #Posts"); connection.Execute("drop table #Users drop table #Posts");
} }
public void TestMultiMappingVariations()
{
var sql = "select 1 as Id, 'a' as Content, 2 as Id, 'b' as Content, 3 as Id, 'c' as Content, 4 as Id, 'd' as Content, 5 as Id, 'e' as Content";
var mapped = connection.Query<dynamic, dynamic, dynamic>(sql, (a, b, c) => { a.B = b; a.C = c; }).First();
Assert.IsEqualTo(mapped.Id, 1);
Assert.IsEqualTo(mapped.Content, "a");
Assert.IsEqualTo(mapped.B.Id, 2);
Assert.IsEqualTo(mapped.B.Content, "b");
Assert.IsEqualTo(mapped.C.Id, 3);
Assert.IsEqualTo(mapped.C.Content, "c");
mapped = connection.Query<dynamic, dynamic, dynamic, dynamic>(sql, (a, b, c, d) => { a.B = b; a.C = c; a.C.D = d; }).First();
Assert.IsEqualTo(mapped.Id, 1);
Assert.IsEqualTo(mapped.Content, "a");
Assert.IsEqualTo(mapped.B.Id, 2);
Assert.IsEqualTo(mapped.B.Content, "b");
Assert.IsEqualTo(mapped.C.Id, 3);
Assert.IsEqualTo(mapped.C.Content, "c");
Assert.IsEqualTo(mapped.C.D.Id, 4);
Assert.IsEqualTo(mapped.C.D.Content, "d");
mapped = connection.Query<dynamic, dynamic, dynamic, dynamic, dynamic>(sql, (a, b, c, d, e) => { a.B = b; a.C = c; a.C.D = d; a.E = e; }).First();
Assert.IsEqualTo(mapped.Id, 1);
Assert.IsEqualTo(mapped.Content, "a");
Assert.IsEqualTo(mapped.B.Id, 2);
Assert.IsEqualTo(mapped.B.Content, "b");
Assert.IsEqualTo(mapped.C.Id, 3);
Assert.IsEqualTo(mapped.C.Content, "c");
Assert.IsEqualTo(mapped.C.D.Id, 4);
Assert.IsEqualTo(mapped.C.D.Content, "d");
Assert.IsEqualTo(mapped.E.Id, 5);
Assert.IsEqualTo(mapped.E.Content, "e");
}
class InheritanceTest1 class InheritanceTest1
{ {
public string Base1 { get; set; } public string Base1 { get; set; }
......
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