2016-01-20 16 views
1

У меня этот код, где ISTM я защищаясь кодирования против присваивающих нулям:Как я могу защитить от «Объект нельзя отбрасывать из DBNull в другие типы»?

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

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

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

ответ

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"); 
+1

Вы можете улучшить это, используя дженерики. вам нужно сделать что-то вроде 'return (T) (row.IsDBNull (порядковый номер)? default (T): row.GetValue (порядковый номер))' в вашем методе расширения. –

+1

@KosalaW Я не мог использовать generics, если я хочу использовать это как класс расширения. Синтаксис будет более искаженным –

+2

Не совсем. Взгляните на мой ответ. Ну в основном это ваш ответ. не мой ответ. :) –

1

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

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

Просто добавив дженерики @ ответ Ü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); 
} 
+0

Это очень впечатляет (действительно, я не саркастичен), но имеет ли он какое-либо преимущество перед ответом Юберкодера (который работает для меня)? –

+1

@ B.ClayShannon. У него есть преимущество перед ответом Юберкодера'_. Производительность мудрая, нет. но LOC мудрый, да. Вот почему я сказал, что это ответ Юберкодера с дженериками. :) –