Skip to content

Commit

Permalink
DapperLib#259: Order of processing value was changed.
Browse files Browse the repository at this point in the history
  • Loading branch information
Maksim Golev authored and Maksim Golev committed Jun 28, 2018
1 parent 4e62055 commit b9b0bd2
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 48 deletions.
15 changes: 14 additions & 1 deletion Dapper.Tests/EnumTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System.Data;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using Dapper.Tests.TestCases;
using Dapper.Tests.TestEntities;
using Xunit;

namespace Dapper.Tests
Expand Down Expand Up @@ -125,5 +128,15 @@ public void SO27024806_TestVarcharEnumMemberWithExplicitConstructor()
var foo = connection.Query<SO27024806Class>("SELECT 'Foo' AS myField").Single();
Assert.Equal(SO27024806Enum.Foo, foo.MyField);
}

[Theory]
[MemberData(nameof(EnumTestCases.TestEnumParsingWithHandlerCases), MemberType = typeof(EnumTestCases))]
public void TestEnumParsingWithHandler(string query, IEnumerable<Item> expectedResult)
{
SqlMapper.AddTypeHandler(new ItemTypeHandler());
var actualResult = connection.Query<Item>(query);

Assert.Equal(expectedResult, actualResult);
}
}
}
17 changes: 17 additions & 0 deletions Dapper.Tests/TestCases/EnumTestCases.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
using System.Collections.Generic;
using Dapper.Tests.TestEntities;

namespace Dapper.Tests.TestCases
{
public class EnumTestCases
{
public static IEnumerable<object[]> TestEnumParsingWithHandlerCases
{
get
{
yield return new object[] { "SELECT 1 AS Id, 'F' AS Type", new List<Item>() { new Item(1, ItemType.Foo) } };
yield return new object[] { "SELECT 22 AS Id, 'B' AS Type", new List<Item>() { new Item(22, ItemType.Bar) } };
}
}
}
}
81 changes: 81 additions & 0 deletions Dapper.Tests/TestEntities/EnumTestEntities.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using System;
using System.Data;

namespace Dapper.Tests.TestEntities
{
public enum ItemType
{
Foo = 1,
Bar = 2,
}

public class ItemTypeHandler : SqlMapper.TypeHandler<ItemType>
{
public override ItemType Parse(object value)
{
var c = ((string)value)[0];
switch (c)
{
case 'F': return ItemType.Foo;
case 'B': return ItemType.Bar;
default: throw new ArgumentOutOfRangeException();
}
}

public override void SetValue(IDbDataParameter p, ItemType value)
{
p.DbType = DbType.AnsiStringFixedLength;
p.Size = 1;

switch (value)
{
case ItemType.Foo:
{
p.Value = "F";
break;
}
case ItemType.Bar:
{
p.Value = "B";
break;
}
default:
{
throw new ArgumentOutOfRangeException();
}
}
}
}

public class Item : IEquatable<Item>
{
public long Id { get; set; }
public ItemType Type { get; set; }

public Item(long id, ItemType type)
{
Id = id;
Type = type;
}

public Item()
: this(default(long), default(ItemType))
{

}

public bool Equals(Item other)
{
bool returnValue;
if ((Id == other.Id) && (Type == other.Type))
{
returnValue = true;
}
else
{
returnValue = false;
}
return returnValue;
}
}
}
92 changes: 45 additions & 47 deletions Dapper/SqlMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3195,7 +3195,8 @@ private static Func<IDataReader, object> GetTypeDeserializerImpl(

bool first = true;
var allDone = il.DefineLabel();
int enumDeclareLocal = -1, valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex;
int enumDeclareLocal = -1;
int valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex;
bool applyNullSetting = Settings.ApplyNullValues;
foreach (var item in members)
{
Expand Down Expand Up @@ -3227,72 +3228,69 @@ private static Func<IDataReader, object> 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?.IsEnum() == true ? nullUnderlyingType : memberType;

if (unboxType.IsEnum())
{
Type numericType = Enum.GetUnderlyingType(unboxType);
if (colType == typeof(string))
{
if (enumDeclareLocal == -1)
{
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
}
il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string]
StoreLocal(il, enumDeclareLocal); // 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(nameof(Type.GetTypeFromHandle)), null);// stack is now [target][target][enum-type]
LoadLocal(il, enumDeclareLocal); // 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.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
else
{
FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType);
}
var unboxType = nullUnderlyingType?.IsEnum() == true ? nullUnderlyingType : memberType; TypeCode dataTypeCode = TypeExtensions.GetTypeCode(colType), unboxTypeCode = TypeExtensions.GetTypeCode(unboxType);

if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
}
}
else if (memberType.FullName == LinqBinary)
if (typeHandlers.ContainsKey(unboxType))
{
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]
#pragma warning disable 618
il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache<int>.Parse)), null); // stack is now [target][target][typed-value]
#pragma warning restore 618
}
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 (unboxType.IsEnum())
{
if (hasTypeHandler)
Type numericType = Enum.GetUnderlyingType(unboxType);
if (colType == typeof(string))
{
#pragma warning disable 618
il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache<int>.Parse)), null); // stack is now [target][target][typed-value]
#pragma warning restore 618
if (enumDeclareLocal == -1)
{
enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
}
il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string]
StoreLocal(il, enumDeclareLocal); // 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(nameof(Type.GetTypeFromHandle)), null);// stack is now [target][target][enum-type]
LoadLocal(il, enumDeclareLocal); // 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.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
else
{
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType);
}

if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-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
{
// not a direct match; need to tweak the unbox
FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null);
if (nullUnderlyingType != null)
if (colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType))
{
il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
}
else
{
il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
// not a direct match; need to tweak the unbox
FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null);
if (nullUnderlyingType != null)
{
il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
}
}
}
}
}

if (specializedConstructor == null)
{
// Store the value in the property/field
Expand Down

0 comments on commit b9b0bd2

Please sign in to comment.