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

Non-nullable TypeHandler passed DBNull when selecting scalar results #224

Open
chilversc opened this issue Dec 19, 2014 · 2 comments
Open

Comments

@chilversc
Copy link
Contributor

When selecting a single column result for a custom type that is nullable, DBNull is passed to the type handler normally resulting in an InvalidCastException as the type handler is not expecting a null. Since the type handler can't return null either, it has no sensible way to handle the DBNull.

void Main()
{
    SqlMapper.AddTypeHandler (new MoneyTypeHandler ());
    using (var db = new SqlConnection("Server=(local); Integrated Security=SSPI")) {
        db.Open ();

        // Normal CLR types work fine when nullable
        db.Query<int?> ("SELECT 1").Single ().Dump ();
        db.Query<int?> ("SELECT NULL").Single ().Dump ();

        // Nullable custom type works correctly when used as a property
        db.Query<Foo> ("SELECT 1 AS Cost").Single ().Dump ();
        db.Query<Foo> ("SELECT NULL AS Cost").Single ().Dump ();

        // Fails when used as a single column scalar result
        db.Query<Money?> ("SELECT 1").Single ().Dump ();
        db.Query<Money?> ("SELECT NULL").Single ().Dump ();
    }  
}

public class MoneyTypeHandler : SqlMapper.TypeHandler<Money>
{
    public override void SetValue (IDbDataParameter p, Money value)
    {
        p.DbType = DbType.Int32;
        p.Value = value.Value;
    }

    public override Money Parse (object obj)
    {
        if (obj is DBNull) {
            throw new InvalidCastException ("Expected type int, got type DBNull");
        }
        return new Money ((int) obj);
    }
}

public struct Money
{
    private readonly int value;

    public Money (int value)
    {
        this.value = value;
    }

    public int Value { get { return value; } }
}

public class Foo
{
    public Money? Cost { get; set; }
}
@chilversc
Copy link
Contributor Author

I've just taken a look at this and the problem is the Parse method on TypeHander<T>

            object ITypeHandler.Parse(Type destinationType, object value)
            {
                return Parse(value);
            }

The obvious fix would be to add a check for DBNull, but that would be a breaking change to any one who expects and handles DBNull already (such as translating to/from a sentinel value).

Though I'm not sure how many people would run in to that problem since they can't apply the reverse transformation because SetValue does check for DBNull.

@chilversc
Copy link
Contributor Author

Thinking about it, I'm inclined to say that no one should be relying on getting a DBNull there since it only occurs in one scenario and that is when you're querying a nullable struct directly. In most other cases (i.e. when it's a property of another type) the type handler never sees the null because the standard property handling filters it out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants