Dapper MultiMap doesn't work with splitOn with

2019-03-17 22:40发布

问题:

I have a problem with MultiMaps in dapper trying to split on column that contains NULL. Dapper seems not to instantiate object and my mapping function receives null instead of object.

Here's my new test:

    class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public Category Category { get; set; }
    }
    class Category
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Description { get; set; }
    }
    public void TestMultiMapWithSplitWithNullValue()
    {
        var sql = @"select 1 as id, 'abc' as name, NULL as description, 'def' as name";
        var product = connection.Query<Product, Category, Product>(sql, (prod, cat) =>
        {
            prod.Category = cat;
            return prod;
        }, splitOn: "description").First();
        // assertions
        product.Id.IsEqualTo(1);
        product.Name.IsEqualTo("abc");
        product.Category.IsNotNull();
        product.Category.Id.IsEqualTo(0);
        product.Category.Name.IsEqualTo("def");
        product.Category.Description.IsNull();
    }

The line that fails is product.Category.IsNotNull(); due to the fact that cat passed to mapping function is null.

I've also added this method to Assert class:

public static void IsNotNull(this object obj)
{
    if (obj == null)
    {
        throw new ApplicationException("Expected not null");
    }
}

回答1:

This is "by-design" though I would be ok to revisit it.

In particular this behaviour is there to help with left joins. Take this for example:

cnn.Query<Car,Driver>("select * from Cars c left join Drivers on c.Id = CarId",
   (c,d) => {c.Driver = d; return c;}) 

Trouble is that if we allow a "blanket" creation of a Driver object, every Car is going to have a Driver even ones where the join failed.

To work around we could scan the entire segment being split and ensure ALL values are NULL before mapping a NULL object. This will have a very minor perf impact on the multi mapper.

To workaround for your case, you could insert a surrogate column:

var sql = @"select 1 as id, 'abc' as name, '' as split, 
            NULL as description, 'def' as name";
    var product = connection.Query<Product, Category, Product>(sql, (prod, cat) =>
    {
        prod.Category = cat;
        return prod;
    }, splitOn: "split").First();


回答2:

For all who wants visualization :

Dapper splits by the last equal column name :

Let's swap location of columns :

null problem :

Swapped column null :

Spliton to the rescue :



回答3:

I have the same issue, i'm forced to select a fake 'split' columns to make Dapper fill my object instead of just null it;

My workaround :

    string req = @"
SELECT
    T1.a as PropA,
    T1.b as PropB,

    1 as Split,
    T2.a as PropA,
    T2.b as PropB,

    1 as Split,
    ...
FROM
    xxx T1,
    yyy T2,
    ...";
    using (var db = new OracleConnection(...))
    {
        return db.Query(
            req,
            (T1, T2) => {
                ...
            },
            splitOn:'Split,Split,...');
    }

Dapper should have an option to avoid the splitOn:'Split,Split,...'



标签: dapper