Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unable to cast object of type 'ClickHouse.Client.Numerics.ClickHouseDecimal' to type 'System.Nullable`1[System.Decimal] #340

Open
joshbartley opened this issue Jul 23, 2023 · 4 comments
Assignees
Labels
bug Something isn't working

Comments

@joshbartley
Copy link

Version: 6.7.2

When using Dapper's connection.QueryAsync<ModelClass>(commandDefintion) with a property of decimal?, the below error gets thrown on mapping. Clickhouse table schema includes a Nullable(Decimal(18, 2)) property that has it's value set to 123.00

var commandDefintion = new CommandDefinition(sqlString, dapperParameters, flags: CommandFlags.NoCache, commandTimeout: 60 * 4);
var results = await connection.QueryAsync<UniverseSearchReturnRow>(commandDefintion);
System.Data.DataException: Error parsing column 9 (DecimalColumn=123.00 - Object)
       ---> System.InvalidCastException: Unable to cast object of type 'ClickHouse.Client.Numerics.ClickHouseDecimal' to type 'System.Nullable`1[System.Decimal]'.
         at Deserializeee96f26b-a164-4613-be13-f787cf48453c(IDataReader )
         --- End of inner exception stack trace ---
         at Dapper.SqlMapper.ThrowDataException(Exception ex, Int32 index, IDataReader reader, Object value) in /_/Dapper/SqlMapper.cs:line 3706
         at Deserializeee96f26b-a164-4613-be13-f787cf48453c(IDataReader )
         at Dapper.SqlMapper.ExecuteReaderSync[T](IDataReader reader, Func`2 func, Object parameters)+MoveNext() in /_/Dapper/SqlMapper.Async.cs:line 976
         at System.Linq.Enumerable.SelectEnumerableIterator`2.ToList()
@DarkWanderer
Copy link
Owner

Hi,

Yes, it seems to be a compatibility issue between ClickHouse.Client's type system and the way Dapper performs conversions

As an interim solution, does using a handler like:

    private class ClickHouseNullableDecimalHandler : SqlMapper.TypeHandler<decimal?>
    {
        public override void SetValue(IDbDataParameter parameter, decimal? value) => parameter.Value = value.ToString(CultureInfo.InvariantCulture);

        public override decimal? Parse(object value) => value switch
        {
            DBNull => null,
            null => null,
            ClickHouseDecimal chd => chd.ToDecimal(CultureInfo.InvariantCulture),
            IConvertible ic => Convert.ToDecimal(ic),
            _ => throw new ArgumentException(null, nameof(value))
        };
    }


    SqlMapper.AddTypeHandler(new ClickHouseNullableDecimalHandler ());

improve the situation for you?

@DarkWanderer DarkWanderer self-assigned this Jul 26, 2023
@DarkWanderer DarkWanderer added the bug Something isn't working label Jul 26, 2023
@joshbartley
Copy link
Author

I think that could work. For now we went to manual mapping because Dapper doesn't have a per query type handler system and we have way to many places do double check for that instead of writing out like 15 lines of mapping code. This also allowed us access to the QueryStats.

@DarkWanderer
Copy link
Owner

What I'm suggesting is to add this handler to your code

    private class ClickHouseNullableDecimalHandler : SqlMapper.TypeHandler<decimal?>
    {
        public override void SetValue(IDbDataParameter parameter, decimal? value) => parameter.Value = value.ToString(CultureInfo.InvariantCulture);

        public override decimal? Parse(object value) => value switch
        {
            DBNull => null,
            null => null,
            ClickHouseDecimal chd => chd.ToDecimal(CultureInfo.InvariantCulture),
            IConvertible ic => Convert.ToDecimal(ic),
            _ => throw new ArgumentException(null, nameof(value))
        };
    }
SqlMapper.AddTypeHandler(new ClickHouseNullableDecimalHandler ());

This is a static operation and should not require using manual mapping. As another workaround, you can try using UseCustomDecimals=false in connection string to revert to .NET decimals

@joshbartley
Copy link
Author

I understand that, this is an existing code base connected to more than Clickhouse with Dapper and taking over the default mapping for all Dapper queries is more of a risk (and testing effort) then I'm willing to take versus mapping out a few properties. I'll check out the connection string change too. I've attempted a fork but 4.6.2 isn't installed on my laptop and I can't get a failing unit test yet for the decimal? conversion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants