diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 8f3956cab..f856fedc8 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1983,10 +1983,10 @@ private static Func GetDeserializer(Type type, DbDataReade return GetSimpleValueDeserializer(type, underlyingType ?? type, startBound, useGetFieldValue); } - private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) - { - return reader => handler.Parse(type, reader.GetValue(startBound))!; - } + private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) + { + return reader => reader.IsDBNull(startBound) ? null! : handler.Parse(type, reader.GetValue(startBound))!; + } private static Exception MultiMapException(IDataRecord reader, string? splitOn = null) { diff --git a/Directory.Packages.props b/Directory.Packages.props index 2f91a3cca..8f2772419 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,6 +35,7 @@ + diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 93b498618..09947a89a 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -31,8 +31,9 @@ - - + + + diff --git a/tests/Dapper.Tests/FakeDbTests.Issue224.cs b/tests/Dapper.Tests/FakeDbTests.Issue224.cs new file mode 100644 index 000000000..d2ffb3766 --- /dev/null +++ b/tests/Dapper.Tests/FakeDbTests.Issue224.cs @@ -0,0 +1,55 @@ +#if !NET481 +using System; +using System.Collections.Generic; +using System.Data; +using pengdows.crud.fakeDb; +using Xunit; + +namespace Dapper.Tests +{ + public sealed class FakeDbIssue224Tests + { + [Fact] + public void NullableScalarTypeHandlerDoesNotReceiveDbNull() + { + var handler = new MoneyHandler(); + SqlMapper.AddTypeHandler(handler); + try + { + using var conn = new fakeDbConnection(new FakeDataStore()); + conn.EnqueueReaderResult(new[] { new Dictionary { ["Value"] = DBNull.Value } }); + conn.Open(); + + var value = conn.QuerySingle("select Value from Values"); + + Assert.Null(value); + Assert.False(handler.ParseCalled); + } + finally + { + SqlMapper.ResetTypeHandlers(); + } + } + + private readonly struct Money + { + public Money(int value) => Value = value; + public int Value { get; } + } + + private sealed class MoneyHandler : SqlMapper.TypeHandler + { + public bool ParseCalled { get; private set; } + + public override Money Parse(object value) + { + ParseCalled = true; + return new Money(Convert.ToInt32(value)); + } + + public override void SetValue(IDbDataParameter parameter, Money value) + => parameter.Value = value.Value; + } + } +} +#endif