76

在 .NET 中有null引用,它在任何地方都用来表示对象引用是空的,然后是DBNull,它被数据库驱动程序(以及少数其他驱动程序)用来表示......几乎相同的东西。自然,这会造成很多混乱,并且必须大量执行转换例程等。

那么,为什么最初的 .NET 作者决定这样做呢?对我来说这毫无意义。他们的文档也没有任何意义:

DBNull 类表示一个不存在的值。例如,在数据库中,表的一行中的列可能不包含任何数据。也就是说,该列被认为根本不存在,而不仅仅是没有值。DBNull 对象表示不存在的列。此外,COM 互操作使用 DBNull 类来区分指示不存在值的 VT_NULL 变体和指示未指定值的 VT_EMPTY 变体。

关于“列不存在”的废话是什么?存在一列,它只是没有特定行的值。如果它不存在,我会在尝试访问特定单元格时遇到异常,而不是DBNull! 我可以理解区分VT_NULLand的必要性VT_EMPTY,但那为什么不做一个COMEmpty类呢?这将更适合整个 .NET 框架。

我错过了什么吗?谁能阐明为什么DBNull发明以及它有助于解决哪些问题?

4

6 回答 6

184

I'm going to disagree with the trend here. I'll go on record:

I do not agree that DBNull serves any useful purpose; it adds unnecessary confusion, while contributing virtually no value.

The argument is often put forward that null is an invalid reference, and that DBNull is a null object pattern; neither is true. For example:

int? x = null;

this is not an "invalid reference"; it is a null value. Indeed null means whatever you want it to mean, and frankly I have absolutely no problem working with values that may be null (indeed, even in SQL we need to correctly work with null - nothing changes here). Equally, the "null object pattern" only makes sense if you are actually treating it as an object in OOP terms, but if we have a value that can be "our value, or a DBNull" then it must be object, so we can't be doing anything useful with it.

There are so many bad things with DBNull:

  • it forces you to work with object, since only object can hold DBNull or another value
  • there is no real difference between "could be a value or DBNull" vs "could be a value or null"
  • the argument that it stems from 1.1 (pre-nullable-types) is meaningless; we could use null perfectly well in 1.1
  • most APIs have "is it null?" methods, for example DBDataReader.IsDBNull or DataRow.IsNull - neither of which actually require DBNull to exist in terms of the API
  • DBNull fails in terms of null-coalescing; value ?? defaultValue doesn't work if the value is DBNull
  • DBNull.Value can't be used in optional parameters, since it isn't a constant
  • the runtime semantics of DBNull are identical to the semantics of null; in particular, DBNull actually equals DBNull - so it does not do the job of representing the SQL semantic
  • it often forces value-type values to be boxed since it over-uses object
  • if you need to test for DBNull, you might as well have tested for just null
  • it causes huge problems for things like command-parameters, with a very silly behaviour that if a parameter has a null value it isn't sent... well, here's an idea: if you don't want a parameter sent - don't add it to the parameters collection
  • every ORM I can think of works perfectly well without any need or use of DBNull, except as an extra nuisance when talking to the ADO.NET code

The only even remotely compelling argument I've ever seen to justify the existence of such a value is in DataTable, when passing in values to create a new row; a null means "use the default", a DBNull is explicitly a null - frankly this API could have had a specific treatment for this case - an imaginary DataRow.DefaultValue for example would be much better than introducing a DBNull.Value that infects vast swathes of code for no reason.

Equally, the ExecuteScalar scenario is... tenuous at best; if you are executing a scalar method, you expect a result. In the scenario where there are no rows, returning null doesn't seem too terrible. If you absolutely need to disambiguate between "no rows" and "one single null returned", there's the reader API.

This ship has sailed long ago, and it is far far too late to fix it. But! Please do not think that everyone agrees that this is an "obvious" thing. Many developers do not see value in this odd wrinkle on the BCL.

I actually wonder if all of this stems from two things:

  • having to use the word Nothing instead of something involving "null" in VB
  • being able to us the if(value is DBNull) syntax which "looks just like SQL", rather than the oh-so-tricky if(value==null)

Summary:

Having 3 options (null, DBNull, or an actual value) is only useful if there is a genuine example where you need to disambiguate between 3 different cases. I have yet to see an occasion where I need to represent two different "null" states, so DBNull is entirely redundant given that null already exists and has much better language and runtime support.

于 2012-03-09T09:56:04.827 回答
48

关键是在某些情况下,数据库值为 null 和 .NET Null 之间存在差异。

例如。如果您使用 ExecuteScalar(它返回结果集中第一行的第一列)并且您得到一个 null 返回,这意味着执行的 SQL 没有返回任何值。如果您返回 DBNull,则表示 SQL 返回了一个值,并且它为 NULL。你需要能够区分。

于 2010-12-20T10:28:16.140 回答
15

DbNull表示一个没有内容的盒子;null表示该框不存在。

于 2010-12-20T10:35:07.267 回答
0

您使用 DBNull 来处理丢失的数据。.NET 语言中的 Null 意味着没有指向对象/变量的指针。

DBNull 缺失数据:http: //msdn.microsoft.com/en-us/library/system.dbnull.value.aspx

缺失数据对统计的影响:

http://en.wikipedia.org/wiki/Missing_values

于 2010-12-20T10:33:38.010 回答
0

CLR null 和 DBNull 之间存在一些差异。首先,关系数据库中的 null 具有不同的“等于”语义:null 不等于 null。CLR null 等于 null。

但我怀疑主要原因是与参数默认值在 SQL 服务器中的工作方式和提供程序的实现有关。

要查看差异,请使用具有默认值的参数创建一个过程:

CREATE PROC [Echo] @s varchar(MAX) = 'hello'
AS
    SELECT @s [Echo]

结构良好的 DAL 代码应该将命令的创建与使用分开(以允许多次使用相同的命令,例如多次有效地调用存储过程)。编写一个返回代表上述过程的 SqlCommand 的方法:

SqlCommand GetEchoProc()
{
    var cmd = new SqlCommand("Echo");
    cmd.Parameters.Add("@s", SqlDbType.VarChar);
    return cmd;
}

如果您现在调用命令而不设置 @s 参数,或者将其值设置为 (CLR) null,它将使用默认值“hello”。另一方面,如果您将参数值设置为 DBNull.Value,它将使用该参数值并回显 DbNull.Value。

由于使用 CLR null 或 database null 作为参数值有两种不同的结果,因此您不能只用其中一种来表示这两种情况。如果 CLR null 是唯一的,它必须像今天的 DBNull.Value 那样工作。向提供者指示“我想使用默认值”的一种方法可能是根本不声明参数(具有默认值的参数当然可以描述为“可选参数”),但在命令对象被缓存和重用的场景确实会导致删除和重新添加参数。

我不确定我是否认为 DBNull 是一个好主意,但是很多人不知道我在这里提到的事情,所以我认为值得一提。

于 2013-05-02T16:43:51.267 回答
0

要回答您的问题,您必须考虑为什么 DBNull 甚至存在?

DBNull 在狭窄的用例中是必需的。否则,它不是。大多数人从不需要 DBNull。我从不允许将它们输入到我设计的数据存储中。我总是有一个值,因此我的数据永远不会是“<null>”,我总是选择一个有意义的“默认值”类型,而且我永远不必在代码中做这种荒谬的仔细检查两次,一次是对象为空,在我将对象转换为我的实际数据类型(例如 Int 等)之前,它是否是 DBNull。

在您可能需要的一种情况下,DBNull 是必需的...如果您使用一些 SQL 统计函数(例如:中值、平均值).. 他们特别对待 DBNull.. 去看看那些文档.. 一些函数不包含 DBNull在统计的总计数中......例如:(87 sum / 127 total)与(87 sum / 117 total).. 不同之处在于这些列值中有 10 个是 DBNull ......你可以看到这会改变统计结果。

我不需要用 DBNull 设计我的数据库。如果我需要统计结果,我会为需要某种特殊处理的项目显式发明或添加一个列,例如“UserDidProvideValue”,因为它不存在(例如,我的总数 117 将是标记的字段的总和UserDidProvideValue=true) ...哈哈哈哈哈——在我的下辈子作为宇宙的统治者哈哈——永远不会允许 DBNull 逃离 SQL 领域...整个编程世界现在有责任检查所有内容两次...当你有没有一个移动应用程序或桌面应用程序或网站需要有一个“空”整数?- 绝不...

于 2020-06-27T15:50:45.603 回答