diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 2341676303ebeae996944db3dd22dfd0f397baf2..e10aaff0d6088143499fef0f96469488e9d548c3 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -227,8 +227,25 @@ private static IEnumerable QueryInternal(this IDbConnection cnn, string sq } } - info.Deserializer = GetDeserializer(identity, reader, start, length); - info.Deserializer2 = GetDeserializer(identity, reader, start + length); + // dynamic comes back as object ... + if (typeof(T) == typeof(object)) + { + info.Deserializer = GetDeserializer(identity, reader, start, length); + } + else + { + info.Deserializer = GetDeserializer(identity, reader, start, length); + } + + if (typeof(U) == typeof(object)) + { + info.Deserializer2 = GetDeserializer(identity, reader, start + length,returnNullIfFirstMissing:true); + } + else + { + info.Deserializer2 = GetDeserializer(identity, reader, start + length, returnNullIfFirstMissing: true); + } + queryCache[identity] = info; } @@ -259,22 +276,22 @@ private static CacheInfo GetCacheInfo(object param, Identity identity) } - static class DynamicStub + class DynamicStub { public static Type Type = typeof(DynamicStub); } - static Func GetDeserializer(Identity identity, IDataReader reader, int startBound = 0, int length = -1) + static Func GetDeserializer(Identity identity, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) { object oDeserializer; if (typeof(T) == DynamicStub.Type || typeof(T) == typeof(ExpandoObject)) { - oDeserializer = GetDynamicDeserializer(reader); + oDeserializer = GetDynamicDeserializer(reader,startBound, length, returnNullIfFirstMissing); } else if (typeof(T).IsClass && typeof(T) != typeof(string)) { - oDeserializer = GetClassDeserializer(reader, startBound, length); + oDeserializer = GetClassDeserializer(reader, startBound, length, returnNullIfFirstMissing: returnNullIfFirstMissing); } else { @@ -285,10 +302,16 @@ static class DynamicStub return deserializer; } - private static object GetDynamicDeserializer(IDataReader reader) + private static object GetDynamicDeserializer(IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) { List colNames = new List(); - for (int i = 0; i < reader.FieldCount; i++) + + if (length == -1) + { + length = reader.FieldCount - startBound; + } + + for (int i = startBound; i < startBound + length; i++) { colNames.Add(reader.GetName(i)); } @@ -297,12 +320,19 @@ private static object GetDynamicDeserializer(IDataReader reader) r => { IDictionary row = new ExpandoObject(); - int i = 0; + int i = startBound; + bool first = true; foreach (var colName in colNames) { var tmp = r.GetValue(i); - row[colName] = tmp == DBNull.Value ? null : tmp; + tmp = tmp == DBNull.Value ? null : tmp; + row[colName] = tmp; + if (returnNullIfFirstMissing && first && tmp == null) + { + return null; + } i++; + first = false; } return (ExpandoObject)row; }; @@ -521,7 +551,7 @@ private static object GetStructDeserializer(IDataReader reader) return deserializer; } - public static Func GetClassDeserializer(IDataReader reader, int startBound = 0, int length = -1) + public static Func GetClassDeserializer(IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) { DynamicMethod dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), typeof(T), new Type[] { typeof(IDataReader) }, true); @@ -560,6 +590,7 @@ private static object GetStructDeserializer(IDataReader reader) // stack is empty il.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes)); // stack is now [target] + bool first = true; foreach (var item in setters) { if (item.Info != null) @@ -581,13 +612,21 @@ private static object GetStructDeserializer(IDataReader reader) il.Emit(OpCodes.Callvirt, item.Info.Setter); // stack is now [target] il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] - il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value] + il.Emit(OpCodes.Pop); // stack is now [target][target] il.Emit(OpCodes.Pop); // stack is now [target] + if (first && returnNullIfFirstMissing) + { + il.Emit(OpCodes.Pop); + il.Emit(OpCodes.Ldnull); // stack is now [null] + il.Emit(OpCodes.Ret); + } + il.MarkLabel(finishLabel); } + first = false; index += 1; } il.Emit(OpCodes.Ret); // stack is empty diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 72c432955c84eba63bd7f269076504478b7ea4b5..87bcd9594406752e98f146c2420702ab8fb35804 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -269,13 +269,53 @@ public void TestMultiMap() left join #Users u on u.Id = p.OwnerId Order by p.Id"; - var data = connection.Query(sql, (post, user) => { post.Owner = user; }); + var data = connection.Query(sql, (post, user) => { post.Owner = user; }).ToList(); var p = data.First(); p.Content.IsEqualTo("Sams Post1"); p.Id.IsEqualTo(1); p.Owner.Name.IsEqualTo("Sam"); p.Owner.Id.IsEqualTo(99); + + data[2].Owner.IsNull(); + + connection.Execute("drop table #Users drop table #Posts"); + } + + + public void TestMultiMapDynamic() + { + var createSql = @" + create table #Users (Id int, Name varchar(20)) + create table #Posts (Id int, OwnerId int, Content varchar(20)) + + insert #Users values(99, 'Sam') + insert #Users values(2, 'I am') + + insert #Posts values(1, 99, 'Sams Post1') + insert #Posts values(2, 99, 'Sams Post2') + insert #Posts values(3, null, 'no ones post') +"; + connection.Execute(createSql); + + var sql = +@"select * from #Posts p +left join #Users u on u.Id = p.OwnerId +Order by p.Id"; + + var data = connection.Query(sql, (post, user) => { post.Owner = user; }).ToList(); + var p = data.First(); + + // hairy extension method support for dynamics + ((string)p.Content).IsEqualTo("Sams Post1"); + ((int)p.Id).IsEqualTo(1); + ((string)p.Owner.Name).IsEqualTo("Sam"); + ((int)p.Owner.Id).IsEqualTo(99); + + ((object)data[2].Owner).IsNull(); + + connection.Execute("drop table #Users drop table #Posts"); } + } }