SqlDataReader 对象的内联空检查

Inline null check for SqlDataReader objects

我正在尝试构建一个方法来查询 SQL table 并将它找到的值分配给一个新的对象列表。这是它如何工作的一个简单示例(假设 reader 和连接已设置并正常工作):

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,所以我尽量保持分配的整洁。问题是我需要为 reader returns 为空的对象分配空值。在上面的代码中,如果 reader 抛出 "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 (和其他所有内容)在对象定义中都可以为空,但这里告诉我它不能这样做。

问题

在这种情况下有没有一种方法可以处理空值:(1) 清楚地写在行内(以避免每个 if 语句单独 属性), 和 (2) 使用任何数据类型?

这是一个适用于字段(可以轻松转换为属性)并允许进行空值检查的示例。它会执行可怕的 if(在 switch 中),但速度非常快。

 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();


    }

数据库中的 Null 不是 "null",而是 DbNull.Value。 ??和 ?。在这种情况下,运营商将无法工作。 GetInt32 等如果数据库中的值为 null 将抛出异常。我做了一个通用方法并保持简单:

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 或类似值。对于可空类型,您可以 return null 或 default(T).

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

你可以为每个标准的GetXXXX写一系列的扩展方法。这些扩展接收一个额外的参数,该参数默认为 return 以防字段值为 null。

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 的字段更改为 return

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

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