Created
February 13, 2013 16:38
-
-
Save aboutdev/4945917 to your computer and use it in GitHub Desktop.
Dapper change to validate mapping. DRAGONS!!!
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
public static Func<IDataReader, object> GetTypeDeserializer( | |
#if CSHARP30 | |
Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing | |
#else | |
Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false, bool validateAllFieldsHaveData = false | |
#endif | |
) | |
{ | |
var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true); | |
var il = dm.GetILGenerator(); | |
il.DeclareLocal(typeof(int)); | |
il.DeclareLocal(type); | |
bool haveEnumLocal = false; | |
il.Emit(OpCodes.Ldc_I4_0); | |
il.Emit(OpCodes.Stloc_0); | |
var properties = GetSettableProps(type); | |
var fields = GetSettableFields(type); | |
if (length == -1) | |
{ | |
length = reader.FieldCount - startBound; | |
} | |
if (reader.FieldCount <= startBound) | |
{ | |
throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); | |
} | |
var names = new List<string>(); | |
for (int i = startBound; i < startBound + length; i++) | |
{ | |
names.Add(reader.GetName(i)); | |
} | |
var setters = ( | |
from n in names | |
let prop = properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // property case sensitive first | |
?? properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase)) // property case insensitive second | |
let field = prop != null ? null : (fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // field case sensitive third | |
?? fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase))) // field case insensitive fourth | |
select new | |
{ | |
Name = n, | |
Property = prop, | |
Field = field | |
} | |
).ToList(); | |
if (validateAllFieldsHaveData) | |
{ | |
var item = setters.FirstOrDefault(x => x.Field == null && x.Property == null); | |
if (item != null) | |
{ | |
throw new MissingFieldException("Cannot map field " + item.Name); | |
} | |
} | |
int index = startBound; | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Ldloca_S, (byte)1); | |
il.Emit(OpCodes.Initobj, type); | |
} | |
else | |
{ | |
var ctor = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null); | |
if (ctor == null) | |
{ | |
throw new InvalidOperationException("A parameterless default constructor is required to allow for dapper materialization"); | |
} | |
il.Emit(OpCodes.Newobj, ctor); | |
il.Emit(OpCodes.Stloc_1); | |
} | |
il.BeginExceptionBlock(); | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Ldloca_S, (byte)1);// [target] | |
} | |
else | |
{ | |
il.Emit(OpCodes.Ldloc_1);// [target] | |
} | |
// stack is now [target] | |
bool first = true; | |
var allDone = il.DefineLabel(); | |
foreach (var item in setters) | |
{ | |
if (item.Property != null || item.Field != null) | |
{ | |
il.Emit(OpCodes.Dup); // stack is now [target][target] | |
Label isDbNullLabel = il.DefineLabel(); | |
Label finishLabel = il.DefineLabel(); | |
il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader] | |
EmitInt32(il, index); // stack is now [target][target][reader][index] | |
il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index] | |
il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] | |
il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] | |
Type memberType = item.Property != null ? item.Property.Type : item.Field.FieldType; | |
if (memberType == typeof(char) || memberType == typeof(char?)) | |
{ | |
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( | |
memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value] | |
} | |
else | |
{ | |
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] | |
il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null] | |
il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object] | |
// unbox nullable enums as the primitive, i.e. byte etc | |
var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); | |
var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType; | |
if (unboxType.IsEnum) | |
{ | |
if (!haveEnumLocal) | |
{ | |
il.DeclareLocal(typeof(string)); | |
haveEnumLocal = true; | |
} | |
Label isNotString = il.DefineLabel(); | |
il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] | |
il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null] | |
il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null] | |
il.Emit(OpCodes.Stloc_2); // stack is now [target][target][value-as-object][string or null] | |
il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object] | |
il.Emit(OpCodes.Pop); // stack is now [target][target] | |
il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token] | |
il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type] | |
il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string] | |
il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true] | |
il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object] | |
il.MarkLabel(isNotString); | |
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] | |
if (nullUnderlyingType != null) | |
{ | |
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-value] | |
} | |
} | |
else if (memberType.FullName == LinqBinary) | |
{ | |
il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array] | |
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary] | |
} | |
else | |
{ | |
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] | |
} | |
} | |
// Store the value in the property/field | |
if (item.Property != null) | |
{ | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Call, item.Property.Setter); // stack is now [target] | |
} | |
else | |
{ | |
il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target] | |
} | |
} | |
else | |
{ | |
il.Emit(OpCodes.Stfld, item.Field); // 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.Stloc_1); | |
il.Emit(OpCodes.Br, allDone); | |
} | |
il.MarkLabel(finishLabel); | |
} | |
first = false; | |
index += 1; | |
} | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Pop); | |
} | |
else | |
{ | |
il.Emit(OpCodes.Stloc_1); // stack is empty | |
} | |
il.MarkLabel(allDone); | |
il.BeginCatchBlock(typeof(Exception)); // stack is Exception | |
il.Emit(OpCodes.Ldloc_0); // stack is Exception, index | |
il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader | |
il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null); | |
il.EndExceptionBlock(); | |
il.Emit(OpCodes.Ldloc_1); // stack is [rval] | |
if (type.IsValueType) | |
{ | |
il.Emit(OpCodes.Box, type); | |
} | |
il.Emit(OpCodes.Ret); | |
return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>)); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@mgravell responded to this question on http://stackoverflow.com/a/14850205/64015
Things to consider.