对SqlDataReader对象进行内联空检查

本文关键字:检查 SqlDataReader 对象 | 更新日期: 2023-09-27 18:05:07

我试图建立一个方法,查询一个SQL表,并将它找到的值分配给一个新的对象列表。下面是它如何工作的一个快速示例(假设阅读器和连接已设置并正常工作):

List<MyObject> results = new List<MyObject>();
int oProductID = reader.GetOrdinal("ProductID");
int oProductName = reader.GetOrdinal("ProductName");
while (reader.Read())
{
    results.Add(new MyProduct() {
        ProductID = reader.GetInt32(oProductID),
        ProductName = reader.GetString(oProductName)
    });
}

还有大约40个其他属性,所有MyObject定义中可为空,所以我试图保持分配尽可能整洁。问题是,只要阅读器返回null,我就需要将null值赋给对象。在上面的代码中,如果阅读器抛出"Data is Null"异常。我知道可以先使用if语句来检查DbNull,但是由于有这么多属性,我希望通过不必为每个属性拼写if语句来保持代码更干净。

经过一番搜索,我找到了null-coalescing运算符,它看起来应该完全符合我的要求。所以我试着改变分配看起来像这样:

ProductID = reader.GetInt32(oProductID) ?? null,
ProductName = reader.GetString(oProductName) ?? null

它适用于任何string,但给我Operator '??' cannot be applied to operands of type 'int' and '<null>'(或除string以外的任何其他数据类型)的错误。我特意在对象定义中将int(以及其他所有内容)称为可空的,但这里它告诉我它不能这样做。

问题

在这种情况下,是否有一种方法可以处理null: (1)被清楚地写在行中(以避免每个属性单独的if语句),并且(2)与任何数据类型一起工作?

对SqlDataReader对象进行内联空检查

数据库中的Null不是" Null ",而是DbNull.Value。?? 和?。在这种情况下,操作符将不起作用。GetInt32等将抛出异常,如果该值在DB中为空。我使用泛型方法,并保持简单:

T SafeDBReader<T>(SqlReader reader, string columnName)
{
   object o = reader[columnName];
   if (o == DBNull.Value)
   {
      // need to decide what behavior you want here
   }
   return (T)o;
}
例如,如果你的数据库有可空的整型,你就不能把它们读入整型,除非你想默认为0或类似的值。对于可空类型,您可以只返回null或default(T)。

Shannon的解决方案既过于复杂,又会造成性能问题(很多过度反射)。

您可以为每个标准GetXXXX编写一系列扩展方法。这些扩展接收一个额外的参数,当字段的值为空时,默认返回该参数。

public static class SqlDataReaderExtensions
{
    public int GetInt32(this SqlDataReader reader, int ordinal, int defValue = default(int))
    {
        return (reader.IsDBNull(ordinal) ? defValue : reader.GetInt32(ordinal);
    }
    public string GetString(this SqlDataReader reader, int ordinal, int defValue = "")
    {
        return (reader.IsDBNull(ordinal) ? defValue : reader.GetString(ordinal);
    }
    public int GetDecimal(this SqlDataReader reader, int ordinal, decimal defValue = default(decimal))
    {
       ....
    }
}

这允许您保持当前代码不变,不进行更改,或者只更改需要null作为返回

的字段。
while (reader.Read())
{
    results.Add(new MyProduct() {
        ProductID = reader.GetInt32(oProductID),
        ProductName = reader.GetString(oProductName, "(No name)"),
        MinReorder = reader.GetInt32(oReorder, null)
        .....
    });
}

您也可以有一个版本,您传递列名而不是顺序位置,并在扩展中搜索位置,但从性能的角度来看,这可能不是很好。

下面是一个适用于字段(可以很容易地转换为属性)并允许空检查的示例。它做了可怕的if(在开关中),但它相当快。

 public static object[] sql_Reader_To_Type(Type t, SqlDataReader r)
    {
        List<object> ret = new List<object>();
        while (r.Read())
        {
            FieldInfo[] f = t.GetFields();
            object o = Activator.CreateInstance(t);
            for (int i = 0; i < f.Length; i++)
            {
                string thisType = f[i].FieldType.ToString();
                switch (thisType)
                {
                    case "System.String":
                        f[i].SetValue(o, Convert.ToString(r[f[i].Name]));
                        break;
                    case "System.Int16":
                        f[i].SetValue(o, Convert.ToInt16(r[f[i].Name]));
                        break;
                    case "System.Int32":
                        f[i].SetValue(o, Convert.ToInt32(r[f[i].Name]));
                        break;
                    case "System.Int64":
                        f[i].SetValue(o, Convert.ToInt64(r[f[i].Name]));
                        break;
                    case "System.Double":
                       double th;
                        if (r[f[i].Name] == null)
                        {
                            th = 0;
                        }
                        else
                        {
                            if (r[f[i].Name].GetType() == typeof(DBNull))
                            {
                                th = 0;
                            }
                            else
                            {
                                th = Convert.ToDouble(r[f[i].Name]);
                            }
                        }
                        try { f[i].SetValue(o, th); }
                        catch (Exception e1)
                        {
                            throw new Exception("can't convert " + f[i].Name + " to doube - value =" + th);
                        }
                        break;
                    case "System.Boolean":
                        f[i].SetValue(o, Convert.ToInt32(r[f[i].Name]) == 1 ? true : false);
                        break;
                    case "System.DateTime":
                        f[i].SetValue(o, Convert.ToDateTime(r[f[i].Name]));
                        break;
                    default:
                        throw new Exception("Missed data type in sql select ");
                }
            }
            ret.Add(o);
        }
        return ret.ToArray();

    }