【问题标题】:How can I guard against "Object cannot be cast from DBNull to other types"?如何防止“对象不能从 DBNull 转换为其他类型”?
【发布时间】:2016-04-25 16:08:36
【问题描述】:

我有这个代码,我在 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"]);
    }
    . . .

...但我得到,“Object cannot be cast from DBNull to other types”在最后一次尝试分配(到 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; }
}

...对于大多数记录,代码运行良好。

什么会导致这个异常,我该如何防范?

【问题讨论】:

    标签: c# double dbnull defensive-programming null-check


    【解决方案1】:

    原因是数据库 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");
    

    【讨论】:

    • 您可以通过使用泛型来改进这一点。您需要在扩展方法中执行类似return (T)(row.IsDBNull(ordinal)? default(T): row.GetValue(ordinal)) 的操作。
    • @KosalaW 如果我想将其用作扩展类,我无法使用泛型。语法会变得更加扭曲
    • 并非如此。看看我的回答。好吧,基本上这是你的答案。不是我的答案。 :)
    • @KosalaW - 你是对的 - 我对类扩展中泛型限制的理解是不完整的。你每天都会学到新东西!
    【解决方案2】:

    将最后一个if 条件更改为

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

    【讨论】:

      【解决方案3】:

      只需将泛型添加到@Ü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);
      }
      

      【讨论】:

      • 这非常令人印象深刻(真的,我不是在讽刺),但它比 Übercoder 的答案(对我有用)有什么优势吗?
      • @B.ClayShannon 与 Übercoder 的回答相比有什么优势吗?。性能方面,没有。但 LOC 明智的,是的。这就是为什么我说,这是 Übercoder 对泛型的回答。 :)
      猜你喜欢
      • 2011-08-31
      • 1970-01-01
      • 1970-01-01
      • 2018-12-21
      • 1970-01-01
      相关资源
      最近更新 更多