From 684a0069c61398ced21386a4b9aeb22ebe776b3a Mon Sep 17 00:00:00 2001 From: Chris Chilvers Date: Thu, 11 Feb 2016 19:00:51 +0000 Subject: [PATCH 1/2] tests to verify enums will use type handlers --- Dapper.Tests/Tests.Enums.cs | 57 +++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/Dapper.Tests/Tests.Enums.cs b/Dapper.Tests/Tests.Enums.cs index 80f27bc38..63a6f29c0 100644 --- a/Dapper.Tests/Tests.Enums.cs +++ b/Dapper.Tests/Tests.Enums.cs @@ -56,6 +56,23 @@ public void TestEnumParamsWithoutNullable() obj.B.IsEqualTo(EnumParam.B); obj.C.IsEqualTo((EnumParam)0); } + + [Fact] + public void TestEnumParameterUsesTypeMapper() + { + SqlMapper.AddTypeHandler(ItemTypeHandler.Default); + var result = connection.QuerySingle("SELECT @Foo", new {Item.Foo}); + "F".IsEqualTo(result); + } + + [Fact] + public void TestEnumResultUsesTypeMapper() + { + SqlMapper.AddTypeHandler(ItemTypeHandler.Default); + var result = connection.QuerySingle("SELECT 'F'"); + Item.Foo.IsEqualTo(result); + } + enum EnumParam : short { None, A, B @@ -88,5 +105,45 @@ class TestEnumClassNoNull { public TestEnum EnumEnum { get; set; } } + + enum Item + { + None, + Foo, + Bar + } + + class ItemTypeHandler : SqlMapper.TypeHandler + { + public static readonly ItemTypeHandler Default = new ItemTypeHandler(); + + public override Item Parse(object value) + { + var c = ((string) value)[0]; + switch (c) + { + case 'F': return Item.Foo; + case 'B': return Item.Bar; + default: throw new ArgumentOutOfRangeException(); + } + } + + public override void SetValue(IDbDataParameter parameter, Item value) + { + parameter.DbType = DbType.AnsiStringFixedLength; + parameter.Size = 1; + parameter.Value = Format(value); + } + + private string Format(Item value) + { + switch (value) + { + case Item.Foo: return "F"; + case Item.Bar: return "B"; + default: throw new ArgumentOutOfRangeException(); + } + } + } } } From ae0c5193d32e9a5641f56c54e4de03f82adc2a7a Mon Sep 17 00:00:00 2001 From: Chris Chilvers Date: Thu, 11 Feb 2016 23:14:02 +0000 Subject: [PATCH 2/2] Check for custom type handler before using built-in logic --- Dapper/SqlMapper.cs | 82 ++++++++++++++++++++++----------------------- 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 46a993f37..e3f09820b 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -360,6 +360,10 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType handler = null; var nullUnderlyingType = Nullable.GetUnderlyingType(type); if (nullUnderlyingType != null) type = nullUnderlyingType; + if (typeHandlers.TryGetValue(type, out handler)) + { + return DbType.Object; + } if (type.IsEnum() && !typeMap.ContainsKey(type)) { type = Enum.GetUnderlyingType(type); @@ -372,10 +376,6 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType { return DbType.Binary; } - if (typeHandlers.TryGetValue(type, out handler)) - { - return DbType.Object; - } if (typeof(IEnumerable).IsAssignableFrom(type)) { return DynamicParameters.EnumerableMultiParameter; @@ -1613,15 +1613,15 @@ private static Func GetDeserializer(Type type, IDataReader { return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); } + ITypeHandler handler; + if (typeHandlers.TryGetValue(type, out handler)) + { + return GetHandlerDeserializer(handler, type, startBound); + } Type underlyingType = null; if (!(typeMap.ContainsKey(type) || type.IsEnum() || type.FullName == LinqBinary || (type.IsValueType() && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum()))) { - ITypeHandler handler; - if (typeHandlers.TryGetValue(type, out handler)) - { - return GetHandlerDeserializer(handler, type, startBound); - } return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); } return GetStructDeserializer(type, underlyingType ?? type, startBound); @@ -2508,6 +2508,15 @@ private static Action GetParameterReader(IDbConnection cnn, private static Func GetStructDeserializer(Type type, Type effectiveType, int index) { // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!) + ITypeHandler handler; + if (typeHandlers.TryGetValue(type, out handler)) + { + return r => + { + var val = r.GetValue(index); + return val is DBNull ? null : handler.Parse(type, val); + }; + } #pragma warning disable 618 if (type == typeof(char)) { // this *does* need special handling, though @@ -2535,15 +2544,6 @@ private static Func GetStructDeserializer(Type type, Type e return val is DBNull ? null : Enum.ToObject(effectiveType, val); }; } - ITypeHandler handler; - if(typeHandlers.TryGetValue(type, out handler)) - { - return r => - { - var val = r.GetValue(index); - return val is DBNull ? null : handler.Parse(type, val); - }; - } return r => { var val = r.GetValue(index); @@ -2557,6 +2557,11 @@ private static T Parse(object value) if (value is T) return (T)value; var type = typeof(T); type = Nullable.GetUnderlyingType(type) ?? type; + ITypeHandler handler; + if (typeHandlers.TryGetValue(type, out handler)) + { + return (T)handler.Parse(type, value); + } if (type.IsEnum()) { if (value is float || value is double || value is decimal) @@ -2565,11 +2570,6 @@ private static T Parse(object value) } return (T)Enum.ToObject(type, value); } - ITypeHandler handler; - if (typeHandlers.TryGetValue(type, out handler)) - { - return (T)handler.Parse(type, value); - } return (T)Convert.ChangeType(value, type, CultureInfo.InvariantCulture); } @@ -2817,7 +2817,22 @@ private static Func GetTypeDeserializerImpl( Type colType = reader.GetFieldType(index); Type memberType = item.MemberType; - if (memberType == typeof(char) || memberType == typeof(char?)) + // unbox nullable enums as the primitive, i.e. byte etc + var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); + var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum() ? nullUnderlyingType : memberType; + + bool hasTypeHandler = typeHandlers.ContainsKey(unboxType); + + if (hasTypeHandler) + { + 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] +#pragma warning disable 618 + il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse)), null); // stack is now [target][target][typed-value] +#pragma warning restore 618 + } + else if (memberType == typeof(char) || memberType == typeof(char?)) { il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( memberType == typeof(char) ? nameof(SqlMapper.ReadChar) : nameof(SqlMapper.ReadNullableChar), BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value] @@ -2828,11 +2843,6 @@ private static Func GetTypeDeserializerImpl( 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()) { Type numericType = Enum.GetUnderlyingType(unboxType); @@ -2869,19 +2879,9 @@ private static Func GetTypeDeserializerImpl( else { TypeCode dataTypeCode = TypeExtensions.GetTypeCode(colType), unboxTypeCode = TypeExtensions.GetTypeCode(unboxType); - bool hasTypeHandler; - if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType)) + if (colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType)) { - if (hasTypeHandler) - { -#pragma warning disable 618 - il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse)), null); // stack is now [target][target][typed-value] -#pragma warning restore 618 - } - else - { - il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] - } + il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] } else {