Checking for Nulls on DB Record Mapping

送分小仙女□ 提交于 2019-12-04 15:34:12

The problem lies in the line convertExp = Expression.Convert(propertyExp, property.PropertyType). You can't expect to convert DbNull value to its equivalent in framework type. This is especially nasty when your type is a value type. One option is to check if the read value from db is DbNull.Value and in case yes, you need to find a compatible value yourself. In some cases people are ok with default values of those types in C#. If you have to do this

property = value == DBNull.Value ? default(T): value;

a generic implementation would look like (as far as the foreach in your converter class goes):

foreach (var column in columns)
{
    var property = targetExp.Type.GetProperty(
        column.name,
        BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
    if (property == null)
        continue;

    var columnIndexExp = Expression.Constant(column.i);
    var propertyExp = Expression.MakeIndex(
        paramExp, indexerInfo, new[] { columnIndexExp });
    var convertExp = Expression.Condition(
        Expression.Equal(
            propertyExp, 
            Expression.Constant(DBNull.Value)), 
        Expression.Default(property.PropertyType), 
        Expression.Convert(propertyExp, property.PropertyType));
    var bindExp = Expression.Assign(
        Expression.Property(targetExp, property), convertExp);
    exps.Add(bindExp);
}

Now this does an equivalent of

property = reader[index] == DBNull.Value ? default(T): reader[index];

You could avoid the double lookup of the reader by assigning it to a variable and using its value in the conditional check. So this should be marginally better, but a lil' more complex:

foreach (var column in columns)
{
    var property = targetExp.Type.GetProperty(
        column.name,
        BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
    if (property == null)
        continue;

    var columnIndexExp = Expression.Constant(column.i);
    var cellExp = Expression.MakeIndex(
        paramExp, indexerInfo, new[] { columnIndexExp });
    var cellValueExp = Expression.Variable(typeof(object), "o7thPropValue");
    var convertExp = Expression.Condition(
        Expression.Equal(
            cellValueExp, 
            Expression.Constant(DBNull.Value)), 
        Expression.Default(property.PropertyType), 
        Expression.Convert(cellValueExp, property.PropertyType));
    var cellValueReadExp = Expression.Block(new[] { cellValueExp },
        Expression.Assign(cellValueExp, cellExp), convertExp);
    var bindExp = Expression.Assign(
        Expression.Property(targetExp, property), cellValueReadExp);
    exps.Add(bindExp);
}

This does the conditional check this way:

value = reader[index];
property = value == DBNull.Value ? default(T): value;
Pete Ebdon

This is one of the most annoying problems in dealing with datasets in general.

The way I normally get around it is to convert the DBNull value to something more useful, like an actual null or even a blank string in some cases. This can be done in a number of ways, but just recently I've taken to using extension methods.

public static T? GetValueOrNull<T>(this object value) where T : struct
        {
            return value == null || value == DBNull.Value ? (T?) null : (T) Convert.ChangeType(value, typeof (T));
        }

A handy extension method for nullable types, so for example:

int? myInt = DataSet.Tables[0].Rows[0]["DBNullInt"].GetValueOrNull<int>();

Or a more generic one to just convert a DBNull in to a null:

public static object GetValueOrNull(this object value)
        {
            return value == DBNull.Value ? null : value;
        }

string myString DataSet.Tables[0].Rows[0]["DBNullString"].GetValueOrNull();

You'll then get a null string, rather than trying to put a DBNull in to a string.

Hopefully that may help you a little.

As I come across this problem recently both

Expression.TypeIs(propertyExp,typeof(DBNull));

and

Expression.Equal(propertyExp,Expression.Constant(DBNull.Value));

didn't work for me as they did increase memory allocation (which is my primary concern in this case)

here is the benchmark for both mapper approach compare to Dapper on 10K rows query.

TypeIs

and Equal

so to fix this problem it came out that an IDataRecord is able to call "IsDBNull" to check whether the column in current reader is DBNull or not

and can be write as expression like

var isReaderDbNull = Expression.Call(paramExp, "IsDBNull", null, readerIndex);

finally, I end up with this solution

and now the performance is acceptable again.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!