处理 DBNull 的最佳方法是啥
Posted
技术标签:
【中文标题】处理 DBNull 的最佳方法是啥【英文标题】:What is the best way to deal with DBNull's处理 DBNull 的最佳方法是什么 【发布时间】:2010-09-06 19:25:34 【问题描述】:在处理从SqlDataAdapters
返回的DataRows
时,我经常遇到问题。当我尝试使用这样的代码填充对象时:
DataRow row = ds.Tables[0].Rows[0];
string value = (string)row;
在这种情况下,处理DBNull's
的最佳方法是什么。
【问题讨论】:
关闭:most-efficient-way-to-check-for-dbnull-and-then-assign-to-a-variable 【参考方案1】:可以为空的类型很好,但只适用于一开始就不能为空的类型。
要使类型“可为空”,请在该类型上附加一个问号,例如:
int? value = 5;
我还建议使用“as
”关键字而不是强制转换。您只能在可空类型上使用“as”关键字,因此请确保您正在转换已经可以为空的内容(如字符串)或使用上述可空类型。原因是
-
如果一个类型可以为空,如果一个值为
DBNull
,“as
”关键字返回null
。
虽然是only in certain cases,但它是ever-so-slightly faster than casting。这本身并不是使用as
的充分理由,但结合上述原因,它很有用。
我建议做这样的事情
DataRow row = ds.Tables[0].Rows[0];
string value = row as string;
在上述情况下,如果row
以DBNull
的形式返回,那么value
将变为null
而不会引发异常。请注意,如果您的数据库查询更改了返回的列/类型,使用as
将导致您的代码静默失败并使值简单null
而不是在出现错误数据时抛出适当的异常返回,因此建议您进行适当的测试,以其他方式验证您的查询,以确保随着代码库的发展而数据完整性。
【讨论】:
这是很老的帖子了,但是值得注意的是,“as”关键字只能用于引用类型和可空类型的转换,不能用于值类型。【参考方案2】:如果您不使用可空类型,最好检查列的值是否为 DBNull。如果是 DBNull,则将引用设置为对应数据类型的 null/empty。
DataRow row = ds.Tables[0].Rows[0];
string value;
if (row["fooColumn"] == DBNull.Value)
value = string.Empty;
else
value = Convert.ToString(row["fooColumn"]);
正如 Manu 所说,您可以创建一个转换类,其中每个类型都有一个重载的转换方法,因此您不必在代码中添加 if/else 块。
不过,我会强调,如果您可以使用可空类型,则它们是更好的选择。原因是对于不可为空的类型,您将不得不求助于“幻数”来表示空值。例如,如果您将列映射到 int 变量,您将如何表示 DBNull?通常您不能使用 0,因为 0 在大多数程序中具有有效含义。我经常看到人们将 DBNull 映射到 int.MinValue,但这也可能存在问题。我最好的建议是:
对于数据库中可以为空的列,请使用可空类型。 对于数据库中不能为空的列,使用常规类型。可空类型是为了解决这个问题。话虽如此,如果您使用的是旧版本的框架,或者为不了解可空类型的人工作,那么代码示例就可以解决问题。
【讨论】:
if(row["fooColumn"] == DBNull.Value) 行有效,但不正确。它没有被定义 DBNull.Value 应该被实现为单例模式。更好的做法是: if(row["fooColumn"] is DBNull) @doekman - 实际上,DBNull 是 一个单例类。引用 MSDN:“DBNull 是一个单例类,这意味着只能存在此类的这个 [DBNull.Value] 实例。” msdn.microsoft.com/en-us/library/system.dbnull.value.aspx 如果您想将 dbnull 视为空字符串 row["fooColumn"].ToString() 会这样做。【参考方案3】:添加对System.Data.DataSetExtensions
的引用,这增加了对查询数据表的 Linq 支持。
这可能是这样的:
string value = (
from row in ds.Tables[0].Rows
select row.Field<string>(0) ).FirstOrDefault();
【讨论】:
+1:这个答案应该得到更多的支持并且可能也被接受 - 绝对 row.Field我总是发现使用 If/Else 检查的版本清晰、简洁且没有问题,仅使用三元运算符。将所有内容保留在一行中,包括在列为空时分配默认值。
因此,假设一个名为“MyCol”的可空 Int32 列,如果该列为空,我们希望返回 -99,但如果该列不为空,则返回整数值:
return row["MyCol"] == DBNull.Value ? -99 : Convert.ToInt32(Row["MyCol"]);
这与上面的 If/Else 获胜者的方法相同 - 但我发现如果您正在从数据读取器读取多个列,那么将所有列读取行一个接一个排列在一起是一个真正的好处,因为更容易发现错误:
Object.ID = DataReader["ID"] == DBNull.Value ? -99 : Convert.ToInt32(DataReader["ID"]);
Object.Name = DataReader["Name"] == DBNull.Value ? "None" : Convert.ToString(DataReader["Name"]);
Object.Price = DataReader["Price"] == DBNull.Value ? 0.0 : Convert.ToFloat(DataReader["Price"]);
【讨论】:
【参考方案5】:如果您可以控制返回结果的查询,则可以使用 ISNULL() 返回非空值,如下所示:
SELECT
ISNULL(name,'') AS name
,ISNULL(age, 0) AS age
FROM
names
如果您的情况可以容忍这些神奇的值代替 NULL,那么采用这种方法可以解决整个应用程序的问题,而不会弄乱您的代码。
【讨论】:
【参考方案6】:DBNull 像其他所有东西一样实现 .ToString() 。无需做任何事情。调用对象的 .ToString() 方法,而不是强制转换。
DataRow row = ds.Tables[0].Rows[0];
string value;
if (row["fooColumn"] == DBNull.Value)
value = string.Empty;
else
value = Convert.ToString(row["fooColumn"]);
这变成:
DataRow row = ds.Tables[0].Rows[0];
string value = row.ToString()
DBNull.ToString() 返回 string.Empty
我想这是您正在寻找的最佳实践
【讨论】:
如果值不是字符串,这将不起作用。我正在寻找一般案例的答案。 我同意是的,如果它的字符串保持简单但其他类型不起作用,例如尝试做 bool.Parse(row["fooColumn"].ToString()).【参考方案7】:值得一提的是,DBNull.Value.ToString()
等于 String.Empty
你可以利用它来发挥你的优势:
DataRow row = ds.Tables[0].Rows[0];
string value = row["name"].ToString();
但是,这仅适用于字符串,对于其他一切我会使用 linq 方式或扩展方法。对于我自己,我编写了一个小扩展方法来检查 DBNull,甚至通过 Convert.ChangeType(...)
进行强制转换
int value = row.GetValueOrDefault<int>("count");
int value = row.GetValueOrDefault<int>("count", 15);
【讨论】:
【参考方案8】:通常在使用 DataTables 时,您必须处理这种情况,其中行字段可以为 null 或 DBNull,通常我会这样处理:
string myValue = (myDataTable.Rows[i]["MyDbNullableField"] as string) ?? string.Empty;
'as' 运算符为无效转换返回 null,例如 DBNull 到字符串,'??'返回 如果第一个为空,则表达式右侧的项。
【讨论】:
【参考方案9】:您也可以使用Convert.IsDBNull (MSDN) 进行测试。
【讨论】:
【参考方案10】:我通常编写自己的 ConvertDBNull 类来包装内置的 Convert 类。如果值是 DBNull,如果它是引用类型,它将返回 null;如果它是值类型,则返回默认值。
例子:
- ConvertDBNull.ToInt64(object obj)
返回 Convert.ToInt64(obj)
除非 obj 是 DBNull,在这种情况下它将返回 0。
【讨论】:
【参考方案11】:由于某种原因,我在检查 DBNull.Value 时遇到了问题,所以我做了一些稍微不同的事情,并利用了 DataRow 对象中的一个属性:
if (row.IsNull["fooColumn"])
value = string.Empty();
else
value = row["fooColumn"].ToString;
【讨论】:
【参考方案12】:Brad Abrams 几天前发布了一些相关的内容 http://blogs.msdn.com/brada/archive/2009/02/09/framework-design-guidelines-system-dbnull.aspx
总结“避免使用 System.DBNull。更喜欢 Nullable。”
这是我的两分钱(未经测试的代码:))
// Or if (row["fooColumn"] == DBNull.Value)
if (row.IsNull["fooColumn"])
// use a null for strings and a Nullable for value types
// if it is a value type and null is invalid throw a
// InvalidOperationException here with some descriptive text.
// or dont check for null at all and let the cast exception below bubble
value = null;
else
// do a direct cast here. dont use "as", "convert", "parse" or "tostring"
// as all of these will swallow the case where is the incorect type.
// (Unless it is a string in the DB and really do want to convert it)
value = (string)row["fooColumn"];
还有一个问题...您不使用 ORM 的任何原因?
【讨论】:
我们现在正在使用 ORM。那时我们还没有 有什么理由不使用 ORM 吗? 我使用原始 ADO .NET 来提高性能。尝试用 EF 或 NH 合并 100 万条记录。【参考方案13】:您还应该查看扩展方法。 Here 是处理这个场景的一些例子。
推荐read
【讨论】:
【参考方案14】:如果您担心在期望字符串时获取 DBNull,一种选择是将 DataTable 中的所有 DBNull 值转换为空字符串。
这很简单,但会增加一些开销,尤其是在处理大型数据表时。如果您有兴趣,请查看此link,它显示了如何操作
【讨论】:
有些时候,了解 Null 和空字符串之间的区别很重要。例如,将一个值设置为一个空字符串,而不是一个从未被设置的值。以上是关于处理 DBNull 的最佳方法是啥的主要内容,如果未能解决你的问题,请参考以下文章