Skip to content

Commit 1ddab8f

Browse files
authored
Merge pull request #63 from seesharper/fix-convert-delegates
Ensure convert-functions are invoked.
2 parents ae39adf + c1a6532 commit 1ddab8f

File tree

9 files changed

+267
-17
lines changed

9 files changed

+267
-17
lines changed

src/DbReader.Tests/ArgumentParserMethodBuilderTests.cs

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using System;
22
using System.Data;
3+
using System.Data.Common;
34
using DbReader.Construction;
45
using Moq;
56
using Shouldly;
@@ -21,6 +22,90 @@ public void ShouldThrowMeaningfulExceptionWhenParameterValueIsNotAccepted()
2122
}
2223

2324

25+
[Fact]
26+
public void ShouldHandleEnumWithoutConverterFunction()
27+
{
28+
var args = new { Status = EnumParameterWithoutConverterFunction.Value2 };
29+
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
30+
var result = method("@Status", args, () => new TestDataParameter());
31+
result.Parameters[0].Value.ShouldBe(2);
32+
}
33+
34+
[Fact]
35+
public void ShouldHandleEnumWithConverterFunction()
36+
{
37+
DbReaderOptions.WhenPassing<EnumParameterWithConverterFunction>().Use((parameter, value) => parameter.Value = (int)EnumParameterWithConverterFunction.Value3);
38+
var args = new { Status = EnumParameterWithConverterFunction.Value2 };
39+
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
40+
var result = method("@Status", args, () => new TestDataParameter());
41+
result.Parameters[0].Value.ShouldBe(3);
42+
}
43+
44+
[Fact]
45+
public void ShouldHandleNullableEnumWithoutConverterFunctionPassingNull()
46+
{
47+
var args = new { Status = new EnumParameterWithoutConverterFunction?() };
48+
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
49+
var result = method("@Status", args, () => new TestDataParameter());
50+
result.Parameters[0].Value.ShouldBe(DBNull.Value);
51+
}
52+
53+
[Fact]
54+
public void ShouldHandleNullableEnumWithoutConverterFunction()
55+
{
56+
var args = new { Status = new EnumParameterWithoutConverterFunction?(EnumParameterWithoutConverterFunction.Value2) };
57+
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
58+
var result = method("@Status", args, () => new TestDataParameter());
59+
result.Parameters[0].Value.ShouldBe(2);
60+
}
61+
62+
63+
[Fact]
64+
public void ShouldHandleNullableEnum()
65+
{
66+
// Nullable<int> nullableInt = 10;
67+
68+
// var r = nullableInt.GetType();
69+
70+
// TestMethod(nullableInt);
71+
72+
var args = new { Status = new Nullable<DbType>(DbType.Currency) };
73+
//DbReaderOptions.WhenPassing<DbType>().Use((p, v) => p.Value = 1);
74+
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
75+
var result = method("@Status", args, () => new TestDataParameter());
76+
}
77+
78+
[Fact]
79+
public void ShouldHandleNullableInt()
80+
{
81+
var args = new { Status = new Nullable<int>(42) };
82+
//DbReaderOptions.WhenPassing<DbType?>().Use((p, v) => p.Value = 1);
83+
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
84+
var result = method("@Status", args, () => new TestDataParameter());
85+
}
86+
87+
public class TestDataParameter : IDataParameter
88+
{
89+
private object _value;
90+
91+
public DbType DbType { get; set; }
92+
public ParameterDirection Direction { get; set; }
93+
94+
public bool IsNullable => true;
95+
96+
public string ParameterName { get; set; }
97+
public string SourceColumn { get; set; }
98+
public DataRowVersion SourceVersion { get; set; }
99+
public object Value
100+
{
101+
get => _value; set
102+
{
103+
_value = value;
104+
}
105+
}
106+
}
107+
108+
24109
public class ThrowingDataParameter : IDataParameter
25110
{
26111
public DbType DbType { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
@@ -33,5 +118,20 @@ public class ThrowingDataParameter : IDataParameter
33118
public DataRowVersion SourceVersion { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
34119
public object Value { get => throw new NotImplementedException(); set => throw new ArgumentException(); }
35120
}
121+
public enum EnumParameterWithoutConverterFunction
122+
{
123+
Value1 = 1,
124+
Value2 = 2,
125+
Value3 = 3
126+
}
127+
128+
public enum EnumParameterWithConverterFunction
129+
{
130+
Value1 = 1,
131+
Value2 = 2,
132+
Value3 = 3
133+
}
36134
}
135+
136+
37137
}

src/DbReader.Tests/DbReader.Tests.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
</PackageReference>
2222
<PackageReference Include="moq" Version="4.18.2" />
2323
<PackageReference Include="shouldly" Version="4.1.0" />
24-
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.116" />
24+
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.115" />
2525
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
2626
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
2727
<PackageReference Include="System.Reflection.Emit.ILGeneration" Version="4.7.0" />

src/DbReader.Tests/InstanceReaderTests.cs

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,53 @@ public void ShouldReadEnum()
216216
instance.Property.ShouldBe(SampleEnum.One);
217217
}
218218

219+
[Fact]
220+
public void ShouldReadNullableEnum()
221+
{
222+
var dataRecord = new { Id = 1, Property = 1 }.ToDataRecord();
223+
var instance = GetReader<ClassWithProperty<SampleEnum?>>().Read(dataRecord, string.Empty);
224+
instance.Property.ShouldBe(SampleEnum.One);
225+
}
226+
227+
[Fact]
228+
public void ShouldReadNullableEnumWithNullValue()
229+
{
230+
var dataRecord = new { Id = 1, Property = DBNull.Value }.ToDataRecord();
231+
var instance = GetReader<ClassWithProperty<SampleEnum?>>().Read(dataRecord, string.Empty);
232+
instance.Property.ShouldBeNull();
233+
}
234+
235+
[Fact]
236+
public void ShouldReadNullableEnumWithConverterFunction()
237+
{
238+
var dataRecord = new { Id = 1, Property = 2 }.ToDataRecord();
239+
bool wasCalled = false;
240+
DbReaderOptions.WhenReading<EnumWithConverterFunction>().Use((dataRecord, ordinal) =>
241+
{
242+
wasCalled = true;
243+
return (EnumWithConverterFunction)dataRecord.GetInt32(ordinal);
244+
});
245+
var instance = GetReader<ClassWithProperty<EnumWithConverterFunction?>>().Read(dataRecord, string.Empty);
246+
instance.Property.ShouldBe(EnumWithConverterFunction.Value2);
247+
wasCalled.ShouldBe(true);
248+
}
249+
250+
251+
[Fact]
252+
public void ShouldReadNullableEnumWithConverterFunctionForIntegralType()
253+
{
254+
var dataRecord = new { Id = 1, Property = 2 }.ToDataRecord();
255+
bool wasCalled = false;
256+
DbReaderOptions.WhenReading<ushort>().Use((dataRecord, ordinal) =>
257+
{
258+
wasCalled = true;
259+
return (ushort)dataRecord.GetInt32(ordinal);
260+
});
261+
var instance = GetReader<ClassWithProperty<EnumWithoutConverterFunctionForIntegralType>>().Read(dataRecord, string.Empty);
262+
instance.Property.ShouldBe(EnumWithoutConverterFunctionForIntegralType.Value2);
263+
wasCalled.ShouldBe(true);
264+
}
265+
219266

220267
[Fact]
221268
public void ShouldReadCustomValueType()
@@ -310,7 +357,6 @@ public void ShouldUseConverterFunctionEvenWhenEnumHasIncompatibleUnderlyingType(
310357
instance.Property.ShouldBe(Int32Enum.One);
311358
}
312359

313-
314360
[Fact]
315361
public void ShouldHandleManyToOneWithoutAnyMatchingColumns()
316362
{
@@ -354,6 +400,21 @@ public void ShouldHandleNullValuesInNavigationChain()
354400
result.Children.ShouldBeEmpty();
355401
}
356402

403+
public enum EnumWithoutConverterFunctionForIntegralType : ushort
404+
{
405+
Value1 = 1,
406+
Value2 = 2,
407+
Value3 = 3
408+
}
409+
410+
public enum EnumWithConverterFunction
411+
{
412+
Value1 = 1,
413+
Value2 = 2,
414+
Value3 = 3
415+
}
416+
417+
357418
public enum Int32Enum
358419
{
359420
Zero,

src/DbReader/ArgumentProcessor.cs

Lines changed: 87 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,40 @@ static ArgumentProcessor()
2020
{
2121
ConvertMethod = typeof(ArgumentProcessor).GetTypeInfo().DeclaredMethods
2222
.Single(m => m.Name == "Process");
23+
ProcessDelegates.TryAdd(typeof(int), (parameter, value) => AssignParameterValue(parameter, (int)value));
24+
ProcessDelegates.TryAdd(typeof(uint), (parameter, value) => AssignParameterValue(parameter, (uint)value));
25+
ProcessDelegates.TryAdd(typeof(long), (parameter, value) => AssignParameterValue(parameter, (long)value));
26+
ProcessDelegates.TryAdd(typeof(ulong), (parameter, value) => AssignParameterValue(parameter, (ulong)value));
27+
ProcessDelegates.TryAdd(typeof(string), (parameter, value) => AssignParameterValue(parameter, (string)value));
28+
ProcessDelegates.TryAdd(typeof(DateTime), (parameter, value) => AssignParameterValue(parameter, (DateTime)value));
29+
ProcessDelegates.TryAdd(typeof(Guid), (parameter, value) => AssignParameterValue(parameter, (Guid)value));
30+
ProcessDelegates.TryAdd(typeof(decimal), (parameter, value) => AssignParameterValue(parameter, (decimal)value));
31+
ProcessDelegates.TryAdd(typeof(byte[]), (parameter, value) => AssignParameterValue(parameter, (byte[])value));
32+
ProcessDelegates.TryAdd(typeof(char[]), (parameter, value) => AssignParameterValue(parameter, (char[])value));
33+
ProcessDelegates.TryAdd(typeof(sbyte), (parameter, value) => AssignParameterValue(parameter, (sbyte)value));
34+
ProcessDelegates.TryAdd(typeof(byte), (parameter, value) => AssignParameterValue(parameter, (byte)value));
35+
ProcessDelegates.TryAdd(typeof(short), (parameter, value) => AssignParameterValue(parameter, (short)value));
36+
ProcessDelegates.TryAdd(typeof(ushort), (parameter, value) => AssignParameterValue(parameter, (ushort)value));
37+
ProcessDelegates.TryAdd(typeof(ushort), (parameter, value) => AssignParameterValue(parameter, (ushort)value));
38+
}
39+
2340

41+
//Extension method?
42+
private static void AssignParameterValue<T>(IDataParameter dataParameter, T value)
43+
{
44+
try
45+
{
46+
dataParameter.Value = ToDbNullIfNull(value);
47+
}
48+
catch
49+
{
50+
throw new ArgumentOutOfRangeException(dataParameter.ParameterName, value, ErrorMessages.InvalidParameterValue.FormatWith(dataParameter.ParameterName, value, value == null ? "null" : value.GetType().Name));
51+
}
52+
}
53+
54+
private static object ToDbNullIfNull(object value)
55+
{
56+
return value ?? DBNull.Value;
2457
}
2558

2659
public static void RegisterProcessDelegate<TArgument>(Action<IDataParameter, TArgument> convertFunction)
@@ -32,7 +65,7 @@ public static void RegisterProcessDelegate<TArgument>(Action<IDataParameter, TAr
3265
Type argumentType = typeof(TArgument);
3366
if (ProcessDelegates.ContainsKey(argumentType))
3467
{
35-
ProcessDelegates.TryUpdate(argumentType, processDelegate, processDelegate);
68+
ProcessDelegates[argumentType] = processDelegate;
3669
}
3770
else
3871
{
@@ -42,19 +75,68 @@ public static void RegisterProcessDelegate<TArgument>(Action<IDataParameter, TAr
4275

4376
public static void Process(Type argumentType, IDataParameter dataParameter, object argument)
4477
{
45-
if (argument != null)
78+
if (argument == null)
79+
{
80+
dataParameter.Value = DBNull.Value;
81+
return;
82+
}
83+
84+
if (ProcessDelegates.ContainsKey(argumentType))
4685
{
47-
ProcessDelegates[argumentType.GetUnderlyingType()](dataParameter, argument);
86+
ProcessDelegates[argumentType](dataParameter, argument);
4887
}
4988
else
5089
{
51-
dataParameter.Value = DBNull.Value;
90+
Type underlyingType = argumentType.GetUnderlyingType();
91+
while (underlyingType != argumentType)
92+
{
93+
if (ProcessDelegates.ContainsKey(underlyingType))
94+
{
95+
ProcessDelegates[underlyingType](dataParameter, argument);
96+
break;
97+
}
98+
else
99+
{
100+
underlyingType = underlyingType.GetUnderlyingType();
101+
}
102+
}
52103
}
53104
}
54105

55106
public static bool CanProcess(Type type)
56107
{
57-
return ProcessDelegates.ContainsKey(type.GetUnderlyingType());
108+
//Keep a map to that we know that Nullable<Guid> maps to ConvertFunction for Guid
109+
110+
if (ProcessDelegates.ContainsKey(type))
111+
{
112+
return true;
113+
}
114+
else
115+
{
116+
Type underlyingType = type.GetUnderlyingType();
117+
if (underlyingType != type)
118+
{
119+
return CanProcess(underlyingType);
120+
}
121+
122+
return false;
123+
}
124+
125+
126+
127+
// Type underlyingType = type.GetUnderlyingType();
128+
// while (type != underlyingType)
129+
// {
130+
// if (ProcessDelegates.ContainsKey(type))
131+
// {
132+
// return true;
133+
// }
134+
// else
135+
// {
136+
// return CanProcess(underlyingType);
137+
// }
138+
// }
139+
// return type.IsSimpleType();
58140
}
59141
}
60142
}

src/DbReader/Construction/ReaderMethodBuilder.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ namespace DbReader.Construction
1212
/// implementations.
1313
/// </summary>
1414
/// <typeparam name="T">The type of object to be created.</typeparam>
15-
public abstract class ReaderMethodBuilder<T> : IReaderMethodBuilder<T>
16-
{
15+
public abstract class ReaderMethodBuilder<T> : IReaderMethodBuilder<T>
16+
{
1717
private readonly IMethodSkeletonFactory methodSkeletonFactory;
1818

1919
/// <summary>
@@ -28,7 +28,7 @@ protected ReaderMethodBuilder(IMethodSkeletonFactory methodSkeletonFactory, IMet
2828
MethodSelector = methodSelector;
2929
this.methodSkeletonFactory = methodSkeletonFactory;
3030
}
31-
31+
3232
/// <summary>
3333
/// Gets the <see cref="IMethodSelector"/> that is responsible for selecting the <see cref="IDataRecord"/>
3434
/// get method that corresponds to the property type.
@@ -93,7 +93,7 @@ protected void EmitCheckForDbNull(ILGenerator il, int index, Label trueLabel)
9393
/// <param name="targetType">The <see cref="Type"/> of the target <see cref="PropertyInfo"/> or <see cref="ParameterInfo"/>.</param>
9494
protected void EmitGetValue(ILGenerator il, int index, MethodInfo getMethod, Type targetType)
9595
{
96-
if (targetType.IsNullable())
96+
if (targetType.IsNullable() && !getMethod.ReturnType.IsNullable())
9797
{
9898
EmitGetNullableValue(il, index, getMethod, targetType);
9999
}
@@ -177,7 +177,7 @@ private static Func<IDataRecord, int[], T> CreateDelegate(IMethodSkeleton method
177177
{
178178
return (Func<IDataRecord, int[], T>)methodSkeleton.CreateDelegate(typeof(Func<IDataRecord, int[], T>));
179179
}
180-
180+
181181
private static void LoadDataRecord(ILGenerator il)
182182
{
183183
il.Emit(OpCodes.Ldarg_0);

src/DbReader/DataReaderExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@
55
using System.Data;
66
using System.Runtime.CompilerServices;
77
using Construction;
8-
using LightInject;
98
using Extensions;
9+
using LightInject;
1010
using Readers;
1111
using Selectors;
1212

src/DbReader/Selectors/MethodSelector.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,13 @@ public MethodInfo Execute(Type type)
3131
return ValueConverter.GetConvertMethod(type);
3232
}
3333

34+
type = type.GetUnderlyingType();
35+
36+
if (ValueConverter.CanConvert(type))
37+
{
38+
return ValueConverter.GetConvertMethod(type);
39+
}
40+
3441
if (type == typeof(bool))
3542
{
3643
return typeof(IDataRecord).GetTypeInfo().GetMethod("GetBoolean");

0 commit comments

Comments
 (0)