Dapper icon indicating copy to clipboard operation
Dapper copied to clipboard

Explicitly implemented ICustomQueryParameter fails with an ArgumentNullException

Open robert-io opened this issue 3 years ago • 1 comments

When a custom parameter explicitly implements the ICustomQueryParameter interface Dapper throws and ArgumentNullException.

See the following example (modified from an existing Unit Test)

private class IntExplicitCustomParam : SqlMapper.ICustomQueryParameter
{
    private readonly IEnumerable<int> numbers;
    public IntExplicitCustomParam(IEnumerable<int> numbers)
    {
        this.numbers = numbers;
    }

    void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name)
    {
        command.CommandType = CommandType.StoredProcedure;

        var number_list = CreateSqlDataRecordList(command, numbers);

        // Add the table parameter.
        AddStructured(command, number_list);
    }
}

[Fact]
public void TestExplicitTVPWithAnonymousObject()
{
    try
    {
        connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)");
        connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers");

        var nums = connection.Query<int>("get_ints", new { integers = new IntExplicitCustomParam(new int[] { 1, 2, 3 }) }, commandType: CommandType.StoredProcedure).ToList();
        Assert.Equal(1, nums[0]);
        Assert.Equal(2, nums[1]);
        Assert.Equal(3, nums[2]);
        Assert.Equal(3, nums.Count);
    }
    finally
    {
        try
        {
            connection.Execute("DROP PROC get_ints");
        }
        finally
        {
            connection.Execute("DROP TYPE int_list_type");
        }
    }
}

The issue is caused by the CreateParamInfoGenerator method using the PropertyType to find the method instead of typeof(ICustomQueryParameter), this can be seen in the following lines of code on the il.EmitCall line as GetMethod is unable to find that method:

if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType))
{
    il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [typed-param]
    il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [custom]
    il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command]
    il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name]
    il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter)), null); // stack is now [parameters]
    continue;
}

robert-io avatar Jul 01 '22 07:07 robert-io

I have created a PR to address this issue: https://github.com/DapperLib/Dapper/pull/1794

robert-io avatar Jul 01 '22 08:07 robert-io