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)
.....
});
}
您也可以有一个版本,其中您传递列名而不是序号位置并搜索扩展内的位置,但从性能的角度来看这可能不太好。
我正在尝试构建一个方法来查询 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)
.....
});
}
您也可以有一个版本,其中您传递列名而不是序号位置并搜索扩展内的位置,但从性能的角度来看这可能不太好。