TypeHandlers not called for parameters extracted from list expansion
Author: benbryant0Created Apr 10, 2021Updated Sep 16, 2026
I've noticed that types with a TypeHandler registered will not have that handler executed if the type is part of a collection that Dapper expands. Here's a test to reproduce this:
private readonly struct IdentityType
{
private readonly int _value;
public IdentityType(int value)
{
_value = value;
}
public int CompareTo(IdentityType other) => _value.CompareTo(other._value);
public override int GetHashCode() => _value;
public override string ToString() => _value.ToString();
public static explicit operator int(IdentityType d) => d._value;
public static explicit operator IdentityType(int d) => new(d);
}
private class IdentityTypeHandler : SqlMapper.TypeHandler<IdentityType>
{
public override IdentityType Parse(object value) => new((int)value);
public override void SetValue(IDbDataParameter parameter, IdentityType value)
{
parameter.DbType = DbType.Int32;
parameter.Value = (int)value;
}
}
[Fact]
public void Potato()
{
SqlMapper.AddTypeHandler(new IdentityTypeHandler());
var identityList = Enumerable.Range(1, 5)
.Select(i => new IdentityType(i))
.ToList();
var param = new { firstItem = identityList[0], allItems = identityList };
var result = connection.Query<int?>("SELECT * FROM (VALUES(1)) AS t(Id) WHERE @firstItem IN @allItems;", param)
.SingleOrDefault();
Assert.Equal(1, result);
}I'm not familiar with the code here, but a bit of investigating led me to this line where the type handler would be fetched but not used. I managed to get my test to pass by persisting the handler and using it if it was set; but due to the other branches based on specific types (isString and isDbString), I'm not sure what the correct behaviour should be.. otherwise this would be a PR :D
Forgive me if this is intended and/or there's a different way to do it. I've searched around and couldn't find any information about this.
Source: DapperLib/Dapper