对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)与任何数据类型一起工作?
数据库中的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();
}