DapperLib / Dapper

Dapper - a simple object mapper for .Net
https://www.learndapper.com/
Other
17.29k stars 3.67k forks source link

Option SqlMapper.Settings.ApplyNullValues is not respected #2086

Open HugoRoss opened 1 month ago

HugoRoss commented 1 month ago

In the following case, option SqlMapper.Settings.ApplyNullValues is not respected, null-values are not assigned:

I have a DTO (PersonDto) with some properties (Id, GivenName, MiddleName, Surname, Picture). The Id is of data type Int32, the other properties are Settable<T>:

public class PersonDto {

    public int Id { get; set; }

    public Settable<string> GivenName { get; set; }

    public Settable<string?> MiddleName { get; set; }

    public Settable<string> Surname { get; set; }

    public Settable<byte[]?> Picture { get; set; }

}

Settable<T> is a structure (value type) that allows to distinguish whether a value was assigned null or was not initialized. Imagine now a data access method that returns a list of PersonDto and that has a Boolean parameter IncludePicture that is false by default and that adjusts the SQL query accordingly to avoid loading expensive property Picture when it is not needed. When the consumer of this method now accidentally accesses uninitialized property Picture, an InvalidOperationException is thrown. Unfortunately that is now also the case for the MiddleName that should have been initialized with null and wasn't. Here the test method:

[TestMethod]
public void Null_Value_Is_Not_Applied_Although_ApplyNullValues_Flag_Is_Set() {
    LocalDB localDB = LocalDBManager.CreateLocalDB("SQL-Scripts"); //Creates a new database on LocalDB-SQLServer 

    IDbConnection dbConnection = localDB.DatabaseConnection;
    dbConnection.EnsureSettableConvertersLoaded(); //Sets ApplyNullValues and registers the converters to Settable<T>
    Assert.IsTrue(SqlMapper.Settings.ApplyNullValues);
    PersonDto actual = dbConnection.QuerySingle<PersonDto>("SELECT Id, GivenName, MiddleName, Surname FROM Person WHERE Id = 3;");
    Assert.IsTrue(SqlMapper.Settings.ApplyNullValues);
    Assert.AreEqual("Kerry", actual.GivenName);
    Assert.AreEqual("Norton", actual.Surname);
    Assert.IsNull(actual.MiddleName.Value); // <--- Fails (null was not assigned!)
    Assert.ThrowsException<InvalidOperationException>(() => actual.Picture.Value); //Works as expected, throws an exception because the value was not initialized.
}

And here the prove that the bug is not in Settable<T>:

[TestMethod]
public void Settable_Works_As_Expected() {
    PersonDto actual = new PersonDto();
    actual.GivenName = "Kerry";
    actual.MiddleName = null;
    actual.Surname = "Norton";

    Assert.AreEqual("Kerry", actual.GivenName);
    Assert.AreEqual("Norton", actual.Surname);
    Assert.IsNull(actual.MiddleName.Value); //Works, null was assigned.
    Assert.ThrowsException<InvalidOperationException>(() => actual.Picture.Value); //Works as expected, throws an exception because the value was not initialized.
}

Here the complete test project (Visual Studio 2022 solution) including database initialization on test execution (using LocalDB): BugReport.zip