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

Query with Multiple columns with same name / How to change TypeHandler for Primitive Types #1600

Open
Drizin opened this issue Dec 23, 2020 · 0 comments

Comments

@Drizin
Copy link

Drizin commented Dec 23, 2020

I have a table with tens of columns but the boolean columns are stored as varchar "YES" / "NO":

CREATE TABLE [tab] (
    [boolcol] varchar(3), // 'YES' or 'NO'
    [col1] [...],
    [col2] [...] etc... tens of columns.
)

Then I have a POCO class with all those columns, but the boolean columns are correctly defined as bool type.
If I just query SELECT * FROM [tab] it obviously crashes, because Dapper can't convert "YES" to true.

I know that I can query by enumerating ALL columns and using formulas to convert the ones which require conversion:

cn.Query<POCO>(@"
    SELECT 
    CAST(CASE WHEN [boolcol]='YES' THEN 1  ELSE 0  END AS BIT) as [boolcol], -- it works
    col1, 
    col2, 
    etc.. many many columns here
    FROM [tab]")

However, since there are many columns I'd like to use * to get all columns and only specify exceptional conversions for the columns which won't be automatically converted by Dapper, but this fails:

cn.Query<POCO>(@"
    SELECT 
    CAST(CASE WHEN [boolcol]='YES' THEN 1  ELSE 0  END AS BIT) as [boolcol],
    * -- this fails because boolcol will ALSO be returned here, and will be converted and crash
    FROM [tab]")

When the second occurrence of boolcol is returned (the real column, not the formula with alias) it will also try to convert to bool, and it will crash.

I know that the table design is ugly, and I know that I could use getters/setters in my POCO to translate the string to boolean, but it's all legacy (used in multiple places and spread across multiple forks) so I'm trying to handle everything in the query.

First question: Why does Dapper try to convert ALL occurrences with the same column name?
I mean: If I add two occurrences of [boolcol] in my query I would expect that only the first occurrence is mapped (and therefore converted) so that my conversion in the last SQL Query above would work and the second occurrence of [boolcol] would be ignored - but what happens is that ALL occurrences are converted, which make invalid conversions crash (and if they didn't crash the winning value would be the last occurrence, which sounds counterintuitive).

If I can't do that using query, my second approach to this problem would be registering a TypeHandler to convert the string to boolean:

public class LenientBooleanHandler : SqlMapper.TypeHandler<bool>
{
    public override bool Parse(object value)
    {
        if (value is string)
        {
            return ((string)value == "YES");
        }

        // Here: How can I just rely on existing Dapper conversions 
        // to fallback on the regular 1==true, "true"==true, etc?
        if (value is int)
        {
            return ((int)value == 1);
        }
        if (value is decimal)
        {
            return ((decimal)value == 1);
        }
        return (bool)value;
    }
    public override void SetValue(IDbDataParameter parameter, bool value)
    { 
        // will all booleans passed to the database be converted through here?
        // even if they are NOT to be converted as strings? 
        // what should I put here to keep the default out-of-the-box behavior?
        // I don't want to break primitive types!
    }
}

Second question: In the TypeHandler above can I just test for those exceptional conversions and rely on regular Dapper conversions for 1, 0, "true", etc? Mostly for SetValue - what should I put in SetValue so that I don't break behavior in other places?

I'm very scared of replacing bool TypeHandler, and I'm aware that TypeHandlers have some issues with primitive types (607 and 433) - that's why the query-only approach would be my first option.

@Drizin Drizin changed the title TypeHandler with fallback behavior / Query with Multiple columns with same name Query with Multiple columns with same name / How to change TypeHandler for Primitive Types Dec 23, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant