DBNull的意义是什么?


72

在.NET中,存在一个null引用,该引用到处都用于表示对象引用为空,然后是DBNull,该引用由数据库驱动程序(和其他一些驱动程序)用来表示...几乎是同一件事。自然,这会引起很多混乱,必须转换转换例程,等等。

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

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

关于“不存在的列”的废话是什么?有一列存在,只是没有特定行的值。如果不存在,则尝试访问特定单元格时会出现异常,而不是DBNull!我可以理解需要区分VT_NULLVT_EMPTY,但是为什么不COMEmpty上课呢?这将更适合整个.NET框架。

我想念什么吗?谁能说出为什么DBNull发明以及它可以解决什么问题?


4
只是另一个数据点... Perl语言中的DBI(数据库接口)模块没有DbNull的概念。当数据库中的值为NULL时,DBI将其表示为Perl“ undef”,这与C#中的Perl等效于“ null”。因此,Perl采取的立场是,不需要特殊的“ DbNull”概念,而且我还没有听说过任何Perl程序员希望他们拥有DbNull。
JoelFan 2011年

1
我和你在一起,@ JoelFan-我也没有看到它的真正用途
Marc Gravell

除了下面的答案之外,@ thomas-levesque在下面的注释非常重要:DBNull在.NET框架中引入真正的可空类型之前。他们的行为略有不同,因此DBNull不得不留下来(我很遗憾,但这是一个不同的故事)。
Jeroen Wiert Pluimers 2013年

3
我喜欢这个问题,但不得不说社区前后矛盾。关于“为什么要这样呢?”的许多问题 被封闭为“不是问题”,特别是如果问问者允许自己解释为什么有些东西看起来毫无意义的时候。(当我将WCF服务作为Web服务公开时,我试图询问/抱怨抽象类型缺乏适当的支持,并立即被击落!)
The Dag

Answers:


46

关键是,在某些情况下,数据库值为null和.NET Null之间会有区别。

例如。如果使用ExecuteScalar(返回结果集中第一行的第一列),并且返回null,则表示执行的SQL不返回任何值。如果返回DBNull,则表示SQL返回了一个值,该值为NULL。您需要能够分辨出差异。


20
好吧,我想这是一个很好的例子。尽管没有足够的理由证明整个混乱。ExecuteScalar可以用不同的方式重写以解决此特定问题(DBEmptyRowset,有人吗?)
Vilx-

2
我对Microsoft编写ADO.NET的方式感到满意。使用DBNull可使所有内容保持一致。每当列值为null时,您将获得一个DBNull。确保要处理的代码要比返回一个简单的null麻烦一些,尤其是如果您要做的只是将DBNull转换为null,但是最终的一致性是关键。如果他们因需要区分这种情况而使ExecuteScalar有所不同,那么它将带来更多的麻烦。当然,这只是我的看法。
科林·麦凯

8
改变一种ExecuteScalar()方法与改变所有的数据库提供程序,数据集/表/行/列,数据绑定控件,还有什么不一样?我会去第一个。归根结底,这会使所有代码变得更加一致。很简单。此外-ExecuteScalar是数据提供者中使用最少的方法之一。DataReader的案例超过90%。另外-您是否真的认为abool ExecuteScalar(out object Value)会导致不一致?
Vilx- 2010年

1
就个人而言,如果没有要返回的值(无行或无列),则我倾向于ExecuteScalar抛出一个异常(DbException),并使用它null代替DBNull
Robert McKee

1
@UfukSURMEN我不明白您的意思-内存和硬盘都是存储介质(尽管临时性比另一个临时性大),因此在这方面没有区别。如果该行存在,则该行中某个列的值可能会返回DBNULL以指示该行的列没有值。对于ExecuteScalarNULL表示不返回任何行,而DBNULL表示至少返回了一行,但第一列没有值。
科林·麦凯

177

我不同意这里的趋势。我会继续记录:

我不同意DBNull任何有用的目的。它增加了不必要的混乱,却几乎没有任何价值。

通常会提出null一个参数,它是一个无效的引用,并且DBNull是一个空对象模式。两者都不是真的。例如:

int? x = null;

这不是“无效的参考”;这是一个null价值。确实null意味着您想要表达的意思,坦率地说,使用可能的值绝对没有问题null(实际上,即使在SQL中,我们也需要正确使用null-这里没有任何变化)。同样,“空对象模式”仅在您实际上将其视为OOP术语中的对象时才有意义,但是如果我们的值可以是“我们的值或DBNull”,那么它必须是object,所以我们不能做任何有用的事情。

坏事太多了DBNull

  • 它迫使您使用object,因为只能object持有DBNull 其他值
  • “可以是一个值或DBNull”与“可以是一个值或null”之间没有真正的区别
  • 它源自1.1(pre-nullable-types)的说法毫无意义;我们可以null在1.1中很好地使用
  • 大多数API具有“是否为null?” 方法,例如-DBDataReader.IsDBNullDataRow.IsNull实际上都不要求DBNull使用API
  • DBNull在无效销售方面失败;value ?? defaultValue如果值是DBNull
  • DBNull.Value 不能在可选参数中使用,因为它不是常量
  • 的运行时语义DBNull与的语义相同null;特别是DBNull实际上等于DBNull-因此它不能代表SQL语义
  • 由于过度使用,通常会强制将值类型值装箱 object
  • 如果您需要测试DBNull,则可能还需要测试null
  • 它会给诸如命令参数之类的问题带来巨大的问题,它具有非常愚蠢的行为,即如果某个参数具有null值,则不会发送...好吧,这是一个主意:如果您不希望发送参数,则不要将其添加到参数集合
  • 每一个ORM我能想到的作品完全良好,没有任何需要或使用的DBNull,聊到ADO.NET代码时,除非额外的滋扰

唯一的甚至是远程我有说服力的论据曾经看到来证明存在这样的值的是DataTable,传递价值,以创建一个新的行时; anull表示“使用默认值”,aDBNull明确为null-坦率地说,此API可能对此情况进行了特定处理-DataRow.DefaultValue例如,虚构方法要比引入DBNull.Value无故感染大量代码的a更好。

同样,ExecuteScalar情况是……充其量是微不足道的。如果执行标量方法,则预期会得到结果。在没有行的情况下,返回null看起来并不可怕。如果您绝对需要在“无行”和“返回一个空值”之间进行歧义,则可以使用阅读器API。

这艘船已经航行很久了,修理它为时已晚。但!请不要认为所有人都认为这是一个“明显”的事情。许多开发人员都没有看到对BCL这个奇怪的皱纹值。

我实际上想知道所有这些是否源于两件事:

  • 必须使用单词Nothing代替VB中涉及“空”的单词
  • 能够为我们if(value is DBNull)提供“看起来像SQL”的语法,而不是让人讨厌的语法if(value==null)

概要:

仅当有一个真实的示例需要区分3种不同情况时,才具有3个选项(nullDBNull或实际值)才有用。我还没有看到需要代表两个不同的“空”状态的情况,因此DBNull鉴于null已经存在并且具有更好的语言和运行时支持,因此完全是多余的。


16
+1,我从来没有遇到过实际需要DBNull.Value与null区分的情况。我一直觉得这是一个痛点,并且由于null!= DBNull.Value
AdaTheDev 2012年

如果没有,“ DBNull实际上等于DBNull”是否有错字-我不明白:)。难道不是“ DBNull实际上等于null”吗?
亚历克斯(Alex)

5
@Alex,不,没有错字;而DBNull绝对不等于空; 尝试一下bool x = DBNull.Value.Equals(DBNull.Value); bool y = DBNull.Value.Equals(null);。我在这里要说的是,如果您在符合ANSI的SQL数据库中尝试这样做,将会发现null不相等null(但同时,null不相等null
Marc Gravell

1
@Dag我对此表示了评论:如果这样做的目的是失败的,正如我在回答中所讨论的那样
Marc Gravell

1
@Blam不,使用dbnull而不是null效率不高。是的,有些联接等数据不存在-空值:但这并不意味着空值与dbnull
Marc Gravell

14

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


7
真?想要详细说明吗?我有点不明白。特别是,由于变量(“盒子”)分配给a时不会消失null
Vilx- 2010年

2
如果您认为变量是盒子的地址,那么当您为变量分配空值时,就是在告诉变量盒子不再存在。也就是说,该地址不可访问。
Rikalous

我同意有关该列不存在的文档是完全错误的。
Rikalous

如果文档是错误的,我怀疑它写得不好,而不是被误导了。
phoog 2012年

1
@TheDag,我认为使用可为空的对象时,不值得检查和转换null和dbnull的麻烦。
drigoangelo

0

您使用DBNull丢失数据。.NET语言中的Null表示没有对象/变量的指针。

DBNull缺少数据:http : //msdn.microsoft.com/zh-cn/library/system.dbnull.value.aspx

数据丢失对统计的影响:

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


1
怎么样Nullable<int>呢?这是一个值类型,不涉及指针。那也许是个错误吗?
Vilx- 2010年

而且-如果不丢失数据,还有什么是“没有对象的指针”?
Vilx- 2010年

3
@ Vilx,.net 1.0 / 1.1中不存在可为空的类型,因此需要另一种表示空值的方法
Thomas Levesque 2010年

1
@Thomas Levesque-我的意思是您的评论值得回答。尽管它仍然不能解释为什么不能同样使用简单的“ null”。要创建既可以包含值类型又可以包含DBNull的变量,无论如何都必须将其设置为“ System.Object”。
Vilx- 2010年

3
@托马斯,逻辑不起作用;存储DBNull我们需要使用的“值或” object-在2.0+中仍然如此;object可以存储null非常清楚-所以即使当使用object时,DBNull.Value仍然是多余的
马克·Gravell

0

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

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

若要查看区别,请创建一个具有默认值的参数的过程:

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空值或数据库空值作为参数值会有两种不同的结果,因此不能仅用其中一种来表示两种情况。如果CLR null是唯一的值,则必须像DBNull.Value那样工作。向提供者指示“我要使用默认值”的一种方式可能是根本不声明参数(具有默认值的参数当然可以描述为“可选参数”),但是在在缓存和重用命令对象的情况下,这确实会导致删除和重新添加参数。

我不确定DBNull是否是个好主意,但很多人不知道我在这里提到的内容,因此我认为值得一提。


1
我现在也学到了一些东西:DBNull实际上没有数据库null的等号语义。第二点仍然存在-DBNull或null作为参数值的作用确实不同。
达格

1
我认为人们普遍认为DBNull和null差异最初存在是因为.NET和SQL Server最初是由独立的团队构建的。这两个空值是否作为团队分离的遗产而存在,或者实际上比烦恼更有益,这是有争议的。
1c1cle 2013年

0

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

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

在某些情况下,可能需要DBNull ...如果您使用某些SQL统计函数(例如,中位数,平均值)..它们特别对待DBNull ..请查看这些文档..某些函数不包含DBNull统计的总计数中...例如:(87总和/ 127总)与(87总和/ 117总)..区别在于那些列值中的10个是DBNull ...您会看到这将会改变统计结果。

我无需使用DBNull设计数据库。如果我需要统计结果,我会为该项目明确发明或添加一列,例如“ UserDidProvideValue”,因为该项目不存在(例如,我的总数为117,即为标记字段的总和) UserDidProvideValue = true)...哈哈哈哈-在我下一世作为宇宙的统治者时-DBNull将再也无法逃脱SQL领域了...整个编程世界现在担负着检查所有事情的重担...何时您是否曾经有一个移动应用程序或台式机应用程序或网站需要一个“空”整数?-从来没有...


噢,要去死灵法师徽章吗?:)对于nullDB中的值,我看到了多个用例。事实上,有时我希望可以定义多个特殊值。
Vilx-

这是一个非常常见的用例:可选外键。如果有一个值(甚至是默认值),那么您需要在远程表中有一条记录。但是,空值不需要在远程表中进行记录。您当然可以说-“确定,但是然后在具有固定ID的远程表中创建该默认记录”。但是随后,您需要在应用程序代码中对DB ID进行硬编码,并且还需要记住,无论您从该远程表中列出值的位置,都跳过该记录。
Vilx-

我确实看到了null将一列分为两部分几乎可以删除任何值的情况-一个包含“ nullness”(可能还有其他特殊值,如我上面想要的)和另一个实际数据(仅在“ nullness”时使用)是false)。但这会更好吗?我不确定...我的第一个直觉是说IFs在代码和CASEsSQL中会有很多很多,因为我现在需要查看两列而不是一列。而且我需要很多CHECK约束来确保数据完整性。
Vilx-
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.