我正在尝试构建一个查询SQL表的方法,并将找到的值分配给新的对象列表。以下是它如何工作的快速示例(假设读者和连接已设置并正常工作):
[08/31/2016 21:59:39 > 0d02fe: INFO] Found the following functions:
[08/31/2016 21:59:39 > 0d02fe: INFO] XXXX.jobs.Functions.ProcessXml
[08/31/2016 21:59:39 > 0d02fe: ERR ]
[08/31/2016 21:59:39 > 0d02fe: ERR ] Unhandled Exception: System.InvalidOperationException: Path 'D:\home\data\XML' does not exist.
[08/31/2016 21:59:39 > 0d02fe: ERR ] at Microsoft.Azure.WebJobs.Files.Listeners.FileListener.CreateFileWatcher()
[08/31/2016 21:59:39 > 0d02fe: ERR ] at Microsoft.Azure.WebJobs.Files.Listeners.FileListener.<StartAsync>d__6.MoveNext()
还有大约40个其他属性,所有在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)
});
}
定义中都可以为空,所以我试图让分配尽可能整洁。问题是我需要在读者返回null的任何地方为对象分配空值。在上面的代码中,如果读者抛出一个&#34;数据是空的&#34;例外。我知道可以首先使用MyObject
语句来检查if
,但由于有很多属性我希望保持代码更清洁通过不必为每个属性拼出DbNull
语句。
一些搜索引导我进入null-coalescing运算符,这似乎应该完全符合我的要求。所以我尝试将分配更改为:
if
适用于任何ProductID = reader.GetInt32(oProductID) ?? null,
ProductName = reader.GetString(oProductName) ?? null
但适用于string
(或除Operator '??' cannot be applied to operands of type 'int' and '<null>'
之外的任何其他数据类型的错误。我特别提到了string
(以及其他所有内容) )在对象定义中可以为空,但在这里它告诉我它不能这样做。
问题
在这种情况下是否有办法处理空值,可以:(1)清楚地在线编写(以避免每个属性单独的int
语句),并且(2)使用任何数据类型?
答案 0 :(得分:3)
数据库中的空值不是“null”,而是DbNull.Value。 ??和?在这种情况下,运营商不会工作。如果DB中的值为null,则GetInt32等将抛出异常。我做了一个通用的方法,并保持简单:
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或类似内容,否则无法将其读入int。对于可空类型,您只需返回null或default(T)。
Shannon的解决方案既过于复杂,也会成为一个性能问题(很多反映在顶部)IMO。
答案 1 :(得分:1)
您可以为每个标准GetXXXX编写一系列扩展方法。这些扩展接收一个额外的参数,如果该字段的值为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作为返回的字段
while (reader.Read())
{
results.Add(new MyProduct() {
ProductID = reader.GetInt32(oProductID),
ProductName = reader.GetString(oProductName, "(No name)"),
MinReorder = reader.GetInt32(oReorder, null)
.....
});
}
您还可以使用一个版本来传递列名而不是序号位置并搜索扩展中的位置,但从性能的角度来看,这可能并不好。
答案 2 :(得分:-1)
这是一个适用于字段的示例(可以很容易地转换为属性)并允许空检查。它确实是可怕的(在开关中),但它非常快。
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();
}