Как я могу защититься от того, что объект не может быть преобразован из DBNull в другие типы?

У меня есть этот код, где ISTM я защищаю от присвоения нулей:

foreach (DataRow priceAndUsageVarianceRow in _dtUsage.Rows)
{
    //var pauv = new PriceAndUsageVariance
    //{
    //    Description = priceAndUsageVarianceRow["Description"].ToString(),
    //    Week1Usage = Convert.ToDouble(priceAndUsageVarianceRow["Week1Usage"]),
    //    Week2Usage = Convert.ToDouble(priceAndUsageVarianceRow["Week2Usage"]),
    //    UsageVariance = Convert.ToDouble(priceAndUsageVarianceRow["UsageVariance"]),
    //    Week1Price = Convert.ToDecimal(priceAndUsageVarianceRow["Week1Price"]),
    //    Week2Price = Convert.ToDecimal(priceAndUsageVarianceRow["Week2Price"]),
    //    PriceVariance = Convert.ToDecimal(priceAndUsageVarianceRow["PriceVariance"]),
    //    PriceVariancePercentage = Convert.ToDouble(priceAndUsageVarianceRow["PriceVariancePercentage"])
    //};
    // Got exception with the code above; trying to prevent it with this:
    var pauv = new PriceAndUsageVariance();
    pauv.Description = String.Empty;
    pauv.Week1Usage = 0.0;
    pauv.Week2Usage = 0.0;
    pauv.UsageVariance = 0.0;
    pauv.Week1Price = 0.00M;
    pauv.Week2Price = 0.00M;
    pauv.PriceVariance = 0.00M;
    pauv.PriceVariancePercentage = 0.0;
    if (null != priceAndUsageVarianceRow["Description"])
    {
        pauv.Description = priceAndUsageVarianceRow["Description"].ToString();
    }
    if (null != priceAndUsageVarianceRow["Week1Usage"])
    {
        pauv.Week1Usage = Convert.ToDouble(priceAndUsageVarianceRow["Week1Usage"]);
    }
    if (null != priceAndUsageVarianceRow["Week2Usage"])
    {
        pauv.Week2Usage = Convert.ToDouble(priceAndUsageVarianceRow["Week2Usage"]);
    }
    . . .

... тем не менее, я получаю "Объект не может быть преобразован из DBNull в другие типы" на определенной итерации при последней попытке назначения (в pauv.Week2Usage).

Это поле появляется в классе:

public class PriceAndUsageVariance
{
    public String Description { get; set; }
    public Double Week1Usage { get; set; }
    public Double Week2Usage { get; set; }
    public Double UsageVariance { get; set; }
    public Decimal Week1Price { get; set; }
    public Decimal Week2Price { get; set; }
    public Decimal PriceVariance { get; set; }
    public Double PriceVariancePercentage { get; set; }
}

... и код работает нормально для большинства записей.

Что может вызвать это исключение и как я могу защититься от него?


person B. Clay Shannon    schedule 20.01.2016    source источник


Ответы (3)


Причина в том, что значения NULL базы данных возвращаются не как CLR null, а как объект типа DBNull, поэтому вы действительно хотите проверить это. Безопаснее всего, в зависимости от обстоятельств, было бы проверить как null, так и объект типа DBNull.

Напишите себе небольшую вспомогательную функцию:

public static class DbNullExt
{
    public static bool IsNullData(this object obj)
    {
        return obj == null || obj is DBNull;
    }
}

и измените свой код таким образом:

if (!IsNullData(priceAndUsageVarianceRow["Description"]))
{
    pauv.Description = priceAndUsageVarianceRow["Description"].ToString();
}
if (!IsNullData(priceAndUsageVarianceRow["Week1Usage"]))
{
    pauv.Week1Usage = Convert.ToDouble(priceAndUsageVarianceRow["Week1Usage"]);
}
if (!IsNullData(priceAndUsageVarianceRow["Week2Usage"]))
{
    pauv.Week2Usage = Convert.ToDouble(priceAndUsageVarianceRow["Week2Usage"]);
}

Поскольку вам нужно постоянно проводить такое тестирование, вы можете позаимствовать мой класс DataRowEx, который еще больше упрощает работу:

public static class DataRowEx
{
    public static string String(this DataRow row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return null;

        return obj.ToString();
    }

    public static Int32 Int32(this DataRow row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return 0;

        return (Int32)obj;
    }

    public static Decimal Decimal(this DataRow row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return 0;

        return Convert.ToDecimal(obj);
    }

    public static Double Double(this DataRow row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return 0;

        return Convert.ToDouble(obj);
    }

    public static Single Single(this DataRow row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return 0;

        return Convert.ToSingle(obj);
    }

    public static bool Bool(this DataRow row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return false;

        if (obj is int)
            return (int) obj != 0;

        return (bool)obj;
    }

    public static DateTime DateTime(this DataRow row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return System.DateTime.MinValue;

        return (DateTime)obj;
    }

    public static object ToType(this DataRow row, Type targetType, string columnName)
    {
        if (targetType == typeof(Int32))
            return row.Int32(columnName);

        if (targetType == typeof(bool))
            return row.Bool(columnName);

        if (targetType == typeof(DateTime))
            return row.DateTime(columnName);

        if (targetType == typeof (Decimal))
            return row.Decimal(columnName);

        if (targetType == typeof(Single))
            return row.Double(columnName);

        if (targetType == typeof(Double))
            return row.Double(columnName);

        if (targetType == typeof(string))
            return row.String(columnName);

        return row.String(columnName);
    }

    public static string String(this DataRowView row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return null;

        return obj.ToString();
    }

    public static Int32 Int32(this DataRowView row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return 0;

        return (Int32)obj;
    }

    public static Decimal Decimal(this DataRowView row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return 0;

        return (Decimal)obj;
    }

    public static Double Double(this DataRowView row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return 0;

        return (Double)obj;
    }

    public static Single Single(this DataRowView row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return 0;

        return (Single)obj;
    }

    public static bool Bool(this DataRowView row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return false;

        return (bool)obj;
    }

    public static DateTime DateTime(this DataRowView row, string columnName)
    {
        object obj = row[columnName];

        if (obj is DBNull)
            return System.DateTime.MinValue;

        return (DateTime)obj;
    }

    public static object ToType(this DataRowView row, Type targetType, string columnName)
    {
        if (targetType == typeof(Int32))
            return row.Int32(columnName);

        if (targetType == typeof(bool))
            return row.Bool(columnName);

        if (targetType == typeof(DateTime))
            return row.DateTime(columnName);

        if (targetType == typeof(Decimal))
            return row.Decimal(columnName);

        if (targetType == typeof(Double))
            return row.Double(columnName);

        if (targetType == typeof(Single))
            return row.Single(columnName);

        return row.String(columnName);
    }
}

Тогда ваш код становится:

pauv.Description = priceAndUsageVarianceRow.String("Description");
pauv.Week1Usage = priceAndUsageVarianceRow.Double("Week1Usage");
pauv.Week2Usage =  priceAndUsageVarianceRow.Double("Week2Usage");
person Jens Meinecke    schedule 20.01.2016
comment
Вы можете улучшить это, используя дженерики. вам нужно сделать что-то вроде return (T)(row.IsDBNull(ordinal)? default(T): row.GetValue(ordinal)) в вашем методе расширения. - person Kosala W; 20.01.2016
comment
@KosalaW Я не мог использовать дженерики, если хочу использовать это как класс расширения. Синтаксис станет более искаженным - person Jens Meinecke; 20.01.2016
comment
Не совсем. Посмотрите на мой ответ. Ну в основном это ваш ответ. не мой ответ. :) - person Kosala W; 20.01.2016
comment
@KosalaW - вы правы - мое понимание ограничений на дженерики в расширениях классов было неполным. Ты узнаешь что-то новое каждый день! - person Jens Meinecke; 20.01.2016

Просто добавьте Generics к ответу @ Übercoder.

Ваш метод расширения;

public static T GetValueOrDefault<T>(this IDataRecord row, string fieldName)
{
    int ordinal = row.GetOrdinal(fieldName);
    return row.GetValueOrDefault<T>(ordinal);
}

public static T GetValueOrDefault<T>(this IDataRecord row, int ordinal)
{
    return (T)((row.IsDBNull(ordinal) ? default(T) : row.GetValue(ordinal)));
}

Образец вызова;

DataReader reader = //your database call
var employees =  new List<Employee>();
while (reader.Read())
{
    var employee = new Employee
    {
        Id = reader.GetValueOrDefault<int>("EmpId"),
        Name = reader.GetValueOrDefault<string>("Name")
    };
    employees.Add(employee);
}
person Kosala W    schedule 20.01.2016
comment
Это очень впечатляет (правда, я не саркастичен), но есть ли у него какое-то преимущество перед ответом Übercoder (который работает для меня)? - person B. Clay Shannon; 20.01.2016
comment
@B.ClayShannon есть ли у него какие-либо преимущества перед ответом Übercoder`. С точки зрения производительности, нет. но мудрый LOC, да. Вот почему я сказал, что это ответ Übercoder с дженериками. :) - person Kosala W; 21.01.2016

Измените последнее условие if на

if (!(priceAndUsageVarianceRow["Week2Usage"] is DBNull) && null != priceAndUsageVarianceRow["Week2Usage"])
{
    pauv.Week2Usage = Convert.ToDouble(priceAndUsageVarianceRow["Week2Usage"]);
}
person Rahul    schedule 20.01.2016