Skip to content

Commit

Permalink
Merge pull request #63 from seesharper/fix-convert-delegates
Browse files Browse the repository at this point in the history
Ensure convert-functions are invoked.
  • Loading branch information
seesharper committed Sep 26, 2022
2 parents ae39adf + c1a6532 commit 1ddab8f
Show file tree
Hide file tree
Showing 9 changed files with 267 additions and 17 deletions.
100 changes: 100 additions & 0 deletions src/DbReader.Tests/ArgumentParserMethodBuilderTests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Data;
using System.Data.Common;
using DbReader.Construction;
using Moq;
using Shouldly;
Expand All @@ -21,6 +22,90 @@ public void ShouldThrowMeaningfulExceptionWhenParameterValueIsNotAccepted()
}


[Fact]
public void ShouldHandleEnumWithoutConverterFunction()
{
var args = new { Status = EnumParameterWithoutConverterFunction.Value2 };
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
var result = method("@Status", args, () => new TestDataParameter());
result.Parameters[0].Value.ShouldBe(2);
}

[Fact]
public void ShouldHandleEnumWithConverterFunction()
{
DbReaderOptions.WhenPassing<EnumParameterWithConverterFunction>().Use((parameter, value) => parameter.Value = (int)EnumParameterWithConverterFunction.Value3);
var args = new { Status = EnumParameterWithConverterFunction.Value2 };
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
var result = method("@Status", args, () => new TestDataParameter());
result.Parameters[0].Value.ShouldBe(3);
}

[Fact]
public void ShouldHandleNullableEnumWithoutConverterFunctionPassingNull()
{
var args = new { Status = new EnumParameterWithoutConverterFunction?() };
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
var result = method("@Status", args, () => new TestDataParameter());
result.Parameters[0].Value.ShouldBe(DBNull.Value);
}

[Fact]
public void ShouldHandleNullableEnumWithoutConverterFunction()
{
var args = new { Status = new EnumParameterWithoutConverterFunction?(EnumParameterWithoutConverterFunction.Value2) };
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
var result = method("@Status", args, () => new TestDataParameter());
result.Parameters[0].Value.ShouldBe(2);
}


[Fact]
public void ShouldHandleNullableEnum()
{
// Nullable<int> nullableInt = 10;

// var r = nullableInt.GetType();

// TestMethod(nullableInt);

var args = new { Status = new Nullable<DbType>(DbType.Currency) };
//DbReaderOptions.WhenPassing<DbType>().Use((p, v) => p.Value = 1);
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
var result = method("@Status", args, () => new TestDataParameter());
}

[Fact]
public void ShouldHandleNullableInt()
{
var args = new { Status = new Nullable<int>(42) };
//DbReaderOptions.WhenPassing<DbType?>().Use((p, v) => p.Value = 1);
var method = argumentParserMethodBuilder.CreateMethod("@Status", args.GetType(), Array.Empty<IDataParameter>());
var result = method("@Status", args, () => new TestDataParameter());
}

public class TestDataParameter : IDataParameter
{
private object _value;

public DbType DbType { get; set; }
public ParameterDirection Direction { get; set; }

public bool IsNullable => true;

public string ParameterName { get; set; }
public string SourceColumn { get; set; }
public DataRowVersion SourceVersion { get; set; }
public object Value
{
get => _value; set
{
_value = value;
}
}
}


public class ThrowingDataParameter : IDataParameter
{
public DbType DbType { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
Expand All @@ -33,5 +118,20 @@ public class ThrowingDataParameter : IDataParameter
public DataRowVersion SourceVersion { get => throw new NotImplementedException(); set => throw new NotImplementedException(); }
public object Value { get => throw new NotImplementedException(); set => throw new ArgumentException(); }
}
public enum EnumParameterWithoutConverterFunction
{
Value1 = 1,
Value2 = 2,
Value3 = 3
}

public enum EnumParameterWithConverterFunction
{
Value1 = 1,
Value2 = 2,
Value3 = 3
}
}


}
2 changes: 1 addition & 1 deletion src/DbReader.Tests/DbReader.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
</PackageReference>
<PackageReference Include="moq" Version="4.18.2" />
<PackageReference Include="shouldly" Version="4.1.0" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.116" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.115" />
<PackageReference Include="System.Reflection.Emit" Version="4.7.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageReference Include="System.Reflection.Emit.ILGeneration" Version="4.7.0" />
Expand Down
63 changes: 62 additions & 1 deletion src/DbReader.Tests/InstanceReaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,53 @@ public void ShouldReadEnum()
instance.Property.ShouldBe(SampleEnum.One);
}

[Fact]
public void ShouldReadNullableEnum()
{
var dataRecord = new { Id = 1, Property = 1 }.ToDataRecord();
var instance = GetReader<ClassWithProperty<SampleEnum?>>().Read(dataRecord, string.Empty);
instance.Property.ShouldBe(SampleEnum.One);
}

[Fact]
public void ShouldReadNullableEnumWithNullValue()
{
var dataRecord = new { Id = 1, Property = DBNull.Value }.ToDataRecord();
var instance = GetReader<ClassWithProperty<SampleEnum?>>().Read(dataRecord, string.Empty);
instance.Property.ShouldBeNull();
}

[Fact]
public void ShouldReadNullableEnumWithConverterFunction()
{
var dataRecord = new { Id = 1, Property = 2 }.ToDataRecord();
bool wasCalled = false;
DbReaderOptions.WhenReading<EnumWithConverterFunction>().Use((dataRecord, ordinal) =>
{
wasCalled = true;
return (EnumWithConverterFunction)dataRecord.GetInt32(ordinal);
});
var instance = GetReader<ClassWithProperty<EnumWithConverterFunction?>>().Read(dataRecord, string.Empty);
instance.Property.ShouldBe(EnumWithConverterFunction.Value2);
wasCalled.ShouldBe(true);
}


[Fact]
public void ShouldReadNullableEnumWithConverterFunctionForIntegralType()
{
var dataRecord = new { Id = 1, Property = 2 }.ToDataRecord();
bool wasCalled = false;
DbReaderOptions.WhenReading<ushort>().Use((dataRecord, ordinal) =>
{
wasCalled = true;
return (ushort)dataRecord.GetInt32(ordinal);
});
var instance = GetReader<ClassWithProperty<EnumWithoutConverterFunctionForIntegralType>>().Read(dataRecord, string.Empty);
instance.Property.ShouldBe(EnumWithoutConverterFunctionForIntegralType.Value2);
wasCalled.ShouldBe(true);
}


[Fact]
public void ShouldReadCustomValueType()
Expand Down Expand Up @@ -310,7 +357,6 @@ public void ShouldUseConverterFunctionEvenWhenEnumHasIncompatibleUnderlyingType(
instance.Property.ShouldBe(Int32Enum.One);
}


[Fact]
public void ShouldHandleManyToOneWithoutAnyMatchingColumns()
{
Expand Down Expand Up @@ -354,6 +400,21 @@ public void ShouldHandleNullValuesInNavigationChain()
result.Children.ShouldBeEmpty();
}

public enum EnumWithoutConverterFunctionForIntegralType : ushort
{
Value1 = 1,
Value2 = 2,
Value3 = 3
}

public enum EnumWithConverterFunction
{
Value1 = 1,
Value2 = 2,
Value3 = 3
}


public enum Int32Enum
{
Zero,
Expand Down
92 changes: 87 additions & 5 deletions src/DbReader/ArgumentProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,40 @@ static ArgumentProcessor()
{
ConvertMethod = typeof(ArgumentProcessor).GetTypeInfo().DeclaredMethods
.Single(m => m.Name == "Process");
ProcessDelegates.TryAdd(typeof(int), (parameter, value) => AssignParameterValue(parameter, (int)value));
ProcessDelegates.TryAdd(typeof(uint), (parameter, value) => AssignParameterValue(parameter, (uint)value));
ProcessDelegates.TryAdd(typeof(long), (parameter, value) => AssignParameterValue(parameter, (long)value));
ProcessDelegates.TryAdd(typeof(ulong), (parameter, value) => AssignParameterValue(parameter, (ulong)value));
ProcessDelegates.TryAdd(typeof(string), (parameter, value) => AssignParameterValue(parameter, (string)value));
ProcessDelegates.TryAdd(typeof(DateTime), (parameter, value) => AssignParameterValue(parameter, (DateTime)value));
ProcessDelegates.TryAdd(typeof(Guid), (parameter, value) => AssignParameterValue(parameter, (Guid)value));
ProcessDelegates.TryAdd(typeof(decimal), (parameter, value) => AssignParameterValue(parameter, (decimal)value));
ProcessDelegates.TryAdd(typeof(byte[]), (parameter, value) => AssignParameterValue(parameter, (byte[])value));
ProcessDelegates.TryAdd(typeof(char[]), (parameter, value) => AssignParameterValue(parameter, (char[])value));
ProcessDelegates.TryAdd(typeof(sbyte), (parameter, value) => AssignParameterValue(parameter, (sbyte)value));
ProcessDelegates.TryAdd(typeof(byte), (parameter, value) => AssignParameterValue(parameter, (byte)value));
ProcessDelegates.TryAdd(typeof(short), (parameter, value) => AssignParameterValue(parameter, (short)value));
ProcessDelegates.TryAdd(typeof(ushort), (parameter, value) => AssignParameterValue(parameter, (ushort)value));
ProcessDelegates.TryAdd(typeof(ushort), (parameter, value) => AssignParameterValue(parameter, (ushort)value));
}


//Extension method?
private static void AssignParameterValue<T>(IDataParameter dataParameter, T value)
{
try
{
dataParameter.Value = ToDbNullIfNull(value);
}
catch
{
throw new ArgumentOutOfRangeException(dataParameter.ParameterName, value, ErrorMessages.InvalidParameterValue.FormatWith(dataParameter.ParameterName, value, value == null ? "null" : value.GetType().Name));
}
}

private static object ToDbNullIfNull(object value)
{
return value ?? DBNull.Value;
}

public static void RegisterProcessDelegate<TArgument>(Action<IDataParameter, TArgument> convertFunction)
Expand All @@ -32,7 +65,7 @@ public static void RegisterProcessDelegate<TArgument>(Action<IDataParameter, TAr
Type argumentType = typeof(TArgument);
if (ProcessDelegates.ContainsKey(argumentType))
{
ProcessDelegates.TryUpdate(argumentType, processDelegate, processDelegate);
ProcessDelegates[argumentType] = processDelegate;
}
else
{
Expand All @@ -42,19 +75,68 @@ public static void RegisterProcessDelegate<TArgument>(Action<IDataParameter, TAr

public static void Process(Type argumentType, IDataParameter dataParameter, object argument)
{
if (argument != null)
if (argument == null)
{
dataParameter.Value = DBNull.Value;
return;
}

if (ProcessDelegates.ContainsKey(argumentType))
{
ProcessDelegates[argumentType.GetUnderlyingType()](dataParameter, argument);
ProcessDelegates[argumentType](dataParameter, argument);
}
else
{
dataParameter.Value = DBNull.Value;
Type underlyingType = argumentType.GetUnderlyingType();
while (underlyingType != argumentType)
{
if (ProcessDelegates.ContainsKey(underlyingType))
{
ProcessDelegates[underlyingType](dataParameter, argument);
break;
}
else
{
underlyingType = underlyingType.GetUnderlyingType();
}
}
}
}

public static bool CanProcess(Type type)
{
return ProcessDelegates.ContainsKey(type.GetUnderlyingType());
//Keep a map to that we know that Nullable<Guid> maps to ConvertFunction for Guid

if (ProcessDelegates.ContainsKey(type))
{
return true;
}
else
{
Type underlyingType = type.GetUnderlyingType();
if (underlyingType != type)
{
return CanProcess(underlyingType);
}

return false;
}



// Type underlyingType = type.GetUnderlyingType();
// while (type != underlyingType)
// {
// if (ProcessDelegates.ContainsKey(type))
// {
// return true;
// }
// else
// {
// return CanProcess(underlyingType);
// }
// }
// return type.IsSimpleType();
}
}
}
10 changes: 5 additions & 5 deletions src/DbReader/Construction/ReaderMethodBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ namespace DbReader.Construction
/// implementations.
/// </summary>
/// <typeparam name="T">The type of object to be created.</typeparam>
public abstract class ReaderMethodBuilder<T> : IReaderMethodBuilder<T>
{
public abstract class ReaderMethodBuilder<T> : IReaderMethodBuilder<T>
{
private readonly IMethodSkeletonFactory methodSkeletonFactory;

/// <summary>
Expand All @@ -28,7 +28,7 @@ protected ReaderMethodBuilder(IMethodSkeletonFactory methodSkeletonFactory, IMet
MethodSelector = methodSelector;
this.methodSkeletonFactory = methodSkeletonFactory;
}

/// <summary>
/// Gets the <see cref="IMethodSelector"/> that is responsible for selecting the <see cref="IDataRecord"/>
/// get method that corresponds to the property type.
Expand Down Expand Up @@ -93,7 +93,7 @@ protected void EmitCheckForDbNull(ILGenerator il, int index, Label trueLabel)
/// <param name="targetType">The <see cref="Type"/> of the target <see cref="PropertyInfo"/> or <see cref="ParameterInfo"/>.</param>
protected void EmitGetValue(ILGenerator il, int index, MethodInfo getMethod, Type targetType)
{
if (targetType.IsNullable())
if (targetType.IsNullable() && !getMethod.ReturnType.IsNullable())
{
EmitGetNullableValue(il, index, getMethod, targetType);
}
Expand Down Expand Up @@ -177,7 +177,7 @@ private static void EmitGotoEndLabelIfValueIsTrue(ILGenerator il, Label endLabel
{
return (Func<IDataRecord, int[], T>)methodSkeleton.CreateDelegate(typeof(Func<IDataRecord, int[], T>));
}

private static void LoadDataRecord(ILGenerator il)
{
il.Emit(OpCodes.Ldarg_0);
Expand Down
2 changes: 1 addition & 1 deletion src/DbReader/DataReaderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
using System.Data;
using System.Runtime.CompilerServices;
using Construction;
using LightInject;
using Extensions;
using LightInject;
using Readers;
using Selectors;

Expand Down
7 changes: 7 additions & 0 deletions src/DbReader/Selectors/MethodSelector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ public MethodInfo Execute(Type type)
return ValueConverter.GetConvertMethod(type);
}

type = type.GetUnderlyingType();

if (ValueConverter.CanConvert(type))
{
return ValueConverter.GetConvertMethod(type);
}

if (type == typeof(bool))
{
return typeof(IDataRecord).GetTypeInfo().GetMethod("GetBoolean");
Expand Down

0 comments on commit 1ddab8f

Please sign in to comment.