Проверка нулей на картографировании записей DB

Как проверить нулевые значения db во вложенном коде? Пожалуйста, поймите, что я новый конвертер С#...

Что делает этот код, он принимает объект 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 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 respective object
    internal T CreateItemFromRow()
    {
        return _converter(dataReader);
    }
}

Mapper

    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 (столбец, ''), но я уверен, что вы можете понять, что это, безусловно, не решение

Ответ 1

Задача лежит в строке convertExp = Expression.Convert(propertyExp, property.PropertyType). Вы не можете рассчитывать преобразовать значение DbNull в его эквивалент в виде рамки. Это особенно неприятно, когда ваш тип является типом значений. Один из вариантов - проверить, является ли значение чтения из db DbNull.Value, и в случае да, вам нужно найти совместимое значение самостоятельно. В некоторых случаях люди в порядке со значениями по умолчанию этих типов в С#. Если вам нужно это сделать

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;

Ответ 2

Это одна из самых неприятных проблем при работе с наборами данных в целом.

Как я обычно обойдусь, это преобразовать значение DBNull в нечто более полезное, например, в случае нулевой или даже пустой строки. Это можно сделать несколькими способами, но совсем недавно я использовал методы расширения.

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 в нуль:

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

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

Затем вы получите пустую строку, вместо того, чтобы ставить строку DBNull в строку.

Надеюсь, это поможет вам немного.