2

如何检查附加代码中的 db null 值?请理解我是一个新的 C# 转换...

这段代码所做的是获取一个 IDataReader 对象并将其转换并映射到一个强类型的对象列表。但是我发现当阅读器中返回空列时,它会完全出错。

转换器

internal class Converter<T> where T : new()
{
    // Declare our _converter delegate
    readonly Func<IDataReader, T> _converter;
    // Declare our internal dataReader
    readonly IDataReader dataReader;

    // Build our mapping based on the properties in the class/type we've passed in to the class
    private Func<IDataReader, T> GetMapFunc()
    {
        // declare our field count
        int _fc = dataReader.FieldCount;
        // declare our expression list
        List<Expression> exps = new List<Expression>();
        // build our parameters for the expression tree
        ParameterExpression paramExp = Expression.Parameter(typeof(IDataRecord), "o7thDR");
        ParameterExpression targetExp = Expression.Variable(typeof(T));
        // Add our expression tree assignment to the exp list
        exps.Add(Expression.Assign(targetExp, Expression.New(targetExp.Type)));
        //does int based lookup
        PropertyInfo indexerInfo = typeof(IDataRecord).GetProperty("Item", new[] { typeof(int) });
        // grab a collection of column names from our data reader
        var columnNames = Enumerable.Range(0, _fc).Select(i => new { i, name = dataReader.GetName(i) }).AsParallel();
        // loop through all our columns and map them properly
        foreach (var column in columnNames)
        {
            // grab our column property
            PropertyInfo property = targetExp.Type.GetProperty(column.name, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);
            // check if it's null or not
            if (property != null)
            {
                // build our expression tree to map the column to the T
                ConstantExpression columnNameExp = Expression.Constant(column.i);
                IndexExpression propertyExp = Expression.MakeIndex(paramExp, indexerInfo, new[] { columnNameExp });
                UnaryExpression convertExp = Expression.Convert(propertyExp, property.PropertyType);
                BinaryExpression bindExp = Expression.Assign(Expression.Property(targetExp, property), convertExp);
                // add it to our expression list
                exps.Add(bindExp);
            }
        }
        // add the originating map to our expression list
        exps.Add(targetExp);
        // return a compiled cached map
        return Expression.Lambda<Func<IDataReader, T>>(Expression.Block(new[] { targetExp }, exps), paramExp).Compile();
    }

    // initialize
    internal Converter(IDataReader dataReader)
    {
        // initialize the internal datareader
        this.dataReader = dataReader;
        // build our map
        _converter = GetMapFunc();
    }

    // create and map each column to it's respective object
    internal T CreateItemFromRow()
    {
        return _converter(dataReader);
    }
}

映射器

    private static IList<T> Map<T>(DbDataReader dr) where T : new()
    {
        try
        {
            // initialize our returnable list
            List<T> list = new List<T>();
            // fire up the lamda mapping
            var converter = new Converter<T>(dr);
            while (dr.Read())
            {
                // read in each row, and properly map it to our T object
                var obj = converter.CreateItemFromRow();
                // add it to our list
                list.Add(obj);
            }
            // reutrn it
            return list;
        }
        catch (Exception ex)
        {
            // make sure this method returns a default List
            return default(List<T>);
        }
    }

我只是不太明白键入对象的列在哪里发生,所以我会尝试自己做......但我只是不知道它在哪里。

我知道这可能无济于事,但我得到的错误是:

Unable to cast object of type 'System.DBNull' to type 'System.String'.

它发生在

internal T CreateItemFromRow()
    {
        return _converter(dataReader); //<-- Here
    }

笔记

如果我用 ISNULL(column, '') 包装查询本身中的列,则不会发生这种情况,但我相信您可以理解这肯定不是解决方案

4

3 回答 3

2

这是处理数据集时最烦人的问题之一。

我通常解决它的方法是将 DBNull 值转换为更有用的值,例如实际的 null 或在某些情况下甚至是空白字符串。这可以通过多种方式完成,但最近我开始使用扩展方法。

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));
        }

可空类型的便捷扩展方法,例如:

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

或者更通用的方法是将 DBNull 转换为 null:

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

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

然后,您将获得一个空字符串,而不是尝试将 aDBNull放入字符串中。

希望这可以帮助你一点。

于 2013-12-06T15:37:02.763 回答
2

问题出在线路上convertExp = Expression.Convert(propertyExp, property.PropertyType)。您不能期望将DbNull值转换为框架类型中的等价物。当您的类型是值类型时,这尤其令人讨厌。一种选择是检查从 db 读取的值是否是DbNull.Value,如果是,您需要自己找到一个兼容的值。在某些情况下,人们可以接受 C# 中这些类型的默认值。如果你必须这样做

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

一个通用的实现看起来像(就foreach你的转换器类而言):

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);
}

现在这相当于

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

您可以通过将其分配给变量并在条件检查中使用其值来避免读取器的双重查找。所以这应该稍微好一点,但更复杂一点:

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);
}

这会以这种方式进行条件检查:

value = reader[index];
property = value == DBNull.Value ? default(T): value;
于 2013-12-11T17:38:18.087 回答
0

当我最近遇到这个问题时

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

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

对我不起作用,因为它们确实增加了内存分配(在这种情况下,这是我最关心的问题)

这是在 10K 行查询上与 Dapper 相比,这两种映射器方法的基准。

类型是 在此处输入图像描述

平等 在此处输入图像描述

所以为了解决这个问题,IDataRecord 能够调用“IsDBNull”来检查当前阅读器中的列是否为 DBNull

在此处输入图像描述

并且可以写成表达式

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

最后,我最终得到了这个解决方案

在此处输入图像描述

现在性能再次可以接受。

于 2019-11-15T09:17:06.140 回答