【问题标题】:What is the point of DBNull?DBNull 的意义何在?
【发布时间】:2023-03-29 04:59:01
【问题描述】:

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

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

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

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

我错过了什么吗?谁能解释一下为什么DBNull 被发明出来以及它有助于解决什么问题?

【问题讨论】:

  • 只是另一个数据点... Perl 语言中的 DBI(DataBase Interface)模块没有 DbNull 的概念。当数据库中的值为 NULL 时,DBI 将其表示为 Perl“undef”,它在 C# 中相当于 Perl 的“null”。所以 Perl 的立场是不需要特殊的“DbNull”概念,而且我还没有听说过任何 Perl 程序员希望他们拥有 DbNull。
  • 我支持你,@JoelFan - 我也认为它没有真正的用途
  • 除了下面的答案,@thomas-levesque 下面的评论非常重要:DBNull 早于 .NET 框架中引入真正的可空类型。他们的行为略有不同,所以DBNull 不得不留下来(我很遗憾,但那是另一回事)。
  • 我喜欢这个问题,但不得不说社区是不一致的。许多关于“为什么会这样?”的问题。被关闭为“不是一个问题”,特别是如果提问者允许自己解释为什么某事似乎没有意义。 (在将 WCF 服务公开为 Web 服务时,我尝试询问/抱怨缺乏对抽象类型的适当支持,但立即遭到拒绝!)

标签: .net history dbnull rationale


【解决方案1】:

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

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

经常有人提出null是无效引用,DBNull是空对象模式的说法; 两者都不是。例如:

int? x = null;

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

DBNull有很多不好的地方:

  • 它迫使您使用object,因为只有object 可以保存DBNull 另一个值
  • “可能是一个值或DBNull”与“可能是一个值或null”之间没有真正的区别
  • 它源于 1.1(可空类型)的论点毫无意义;我们可以在 1.1 中完美地使用 null
  • 大多数 API 都有“它是空的吗?”方法,例如 DBDataReader.IsDBNullDataRow.IsNull - 就 API 而言,这两种方法实际上都不需要存在 DBNull
  • DBNull 在空合并方面失败;如果值为DBNullvalue ?? defaultValue 不起作用
  • DBNull.Value 不能用于可选参数,因为它不是常量
  • DBNull 的运行时语义与null 的语义相同;特别是,DBNull 实际上等于 DBNull - 所以它完成表示 SQL 语义的工作
  • 由于过度使用object,它经常强制将值类型的值装箱
  • 如果你需要测试DBNull,你还不如只测试null
  • 它会给命令参数之类的东西带来巨大的问题,如果一个参数有一个null 值,它就不会被发送......好吧,这是一个想法:如果你不想要一个已发送参数 - 不要将其添加到参数集合中
  • 我能想到的每个 ORM 都运行良好,无需任何需要或使用 DBNull,除非在与 ADO.NET 代码交谈时会产生额外的麻烦

曾经看到的唯一一个甚至是远程令人信服的论点来证明这种值的存在是在DataTable中,当传入值以创建新行; null 表示“使用默认值”,DBNull 明确为 null - 坦率地说,这个 API 可以针对这种情况进行特定处理 - 例如,想象的 DataRow.DefaultValue 比引入 DBNull.Value 要好得多这会无缘无故地感染大量代码。

同样,ExecuteScalar 场景……充其量是微弱的;如果你正在执行一个标量方法,你期望一个结果。在没有行的情况下,返回null 似乎并不太糟糕。如果您绝对需要区分“无行”和“返回一个空值”,则可以使用阅读器 API。

这艘船很久以前就航行了,现在修复它已经太迟了。但!请不要认为每个人都同意这是一件“显而易见的”事情。许多开发人员没有看到 BCL 上这种奇怪的皱纹的价值。

我真的想知道这一切是否源于两件事:

  • 在 VB 中必须使用单词 Nothing 而不是涉及“null”的东西
  • 能够让我们使用“看起来就像 SQL”的 if(value is DBNull) 语法,而不是那么棘手的 if(value==null)

总结:

只有当您需要在 3 种不同情况之间消除歧义的真实示例时,拥有 3 个选项(nullDBNull 或实际值)才有用。我还没有看到我需要表示两种不同的“空”状态的场合,所以DBNull 是完全多余的,因为null 已经存在并且具有更好的语言和运行时支持。

【讨论】:

  • +1,我从来没有遇到过我真正希望 DBNull.Value 与 null 区分开来的情况。我一直觉得这是一个痛点,并且由于 null != DBNull.Value 这一事实而看到(+ 经历过)很多时间被浪费了
  • “DBNull 实际上等于 DBNull”是否有错字,如果没有 - 我不明白 :)。不应该是“DBNull 实际上等于 null”吗?
  • @Alex 不,没有错字;而DBNull 绝对不等于null;试试bool x = DBNull.Value.Equals(DBNull.Value); bool y = DBNull.Value.Equals(null);。我在这里要说明的一点是,如果您在符合 ANSI 的 SQL 数据库中尝试这样做,您会发现 null 不等于 null(但同时,null 并不“不等于” null)
  • @Dag 我对此发表了评论:如果那是它的意图,它会失败,正如我的回答中所讨论的那样
  • @Blam 不,使用 dbnull 而不是 null 效率并不高。是的,一些连接等会留下数据缺失 - 一个空值:但这并不代表空值与 dbnull
【解决方案2】:

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

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

【讨论】:

  • 好吧,我想这是一个很好的例子。虽然没有足够的理由单独证明制造整个混乱是合理的。 ExecuteScalar 可以用不同的方式重写来解决这个特殊问题(DBEmptyRowset,有人吗?)
  • 我对 Microsoft 编写 ADO.NET 的方式感到满意。 DBNull 的使用使一切保持一致。任何时候列值为空时,您都会得到一个 DBNull。当然,要处理的代码可能比返回简单的 null 更麻烦,特别是如果您所做的只是将 DBNull 转换为 null,但最终一致性是关键。如果他们改变 ExecuteScalar 是因为您需要区分这种情况,那么它会更加震撼。当然,这只是我的看法。
  • 改变一个ExecuteScalar() 方法与改变所有的数据库提供者、数据集/表/行/列、数据绑定控件等等?我会去第一个。归根结底,这将使所有代码更加一致。而且很简单。此外 - ExecuteScalar 是数据提供者中使用最少的方法之一。 >90% 的案例是使用 DataReader。另外 - 你真的认为 bool ExecuteScalar(out object Value) 会如此不一致吗?
  • 就个人而言,如果没有要返回的值(没有行或没有列),我宁愿 ExecuteScalar 抛出异常 (DbException),并使用 null 而不是DBNull
  • @UfukSURMEN 我不明白你的意思 - 内存和硬盘都是存储介质(尽管一个比另一个更短暂)所以在这方面没有区别。如果该行存在,则该行中的列的值可能会返回 DBNULL 以指示该行的列没有值。对于ExecuteScalar,NULL 表示没有返回任何行,DBNULL 表示至少返回了一行但第一列没有值。
【解决方案3】:

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

【讨论】:

  • 真的吗?想详细说明一下吗?我有点不明白。特别是,当您将 null 分配给它时,变量(“盒子”)不会消失。
  • 如果您认为变量是盒子的地址 - 那么当您为变量分配空值时,您就是在告诉变量盒子不再存在。也就是说,该地址不是“可访问的”。
  • 我同意关于该列不存在的文档是完全错误的。
  • 就文档错误的程度而言,我怀疑它写得不好而不是被误导了。
  • @TheDag,我认为这个功能不值得在使用可为空对象时检查和转换 null 和 dbnull。
【解决方案4】:

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

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

缺失数据对统计数据的影响:

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

【讨论】:

  • 那么Nullable<int> 呢?这是一个值类型,不涉及指针。那可能是个错误?
  • 还有——如果没有丢失数据,还有什么是“没有对象的指针”?
  • @Vilx,.NET 1.0/1.1 中不存在可空类型,因此需要另一种表示空值的方式
  • @Thomas Levesque - 我会说,您的评论值得回答。虽然它仍然没有解释为什么不能使用简单的“null”。要创建一个可以同时包含值类型和 DBNull 的变量,无论如何您都必须将其设为“System.Object”。
  • @Thomas 逻辑不起作用;要存储“值或DBNull”,我们需要使用object - 在2.0+ 中仍然如此; object 可以很好地存储null - 所以即使使用objectDBNull.Value 仍然是多余的
【解决方案5】:

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 是个好主意,但很多人不知道我在这里提到的事情,所以我认为值得一提。

【讨论】:

  • 我现在也学到了一些东西:DBNull 实际上并没有数据库空值的等于语义。第二点仍然存在 - DBNull 或 null 作为参数值的工作方式确实不同。
  • 我认为 DBNull 和 null 差异最初存在是普遍的看法,因为 .NET 和 SQL Server 最初是由不同的团队构建的。如果这两个空值现在作为这个独立团队的遗产而存在,或者作为实际上比烦恼更多的东西存在,那么这是有争议的。
【解决方案6】:

要回答您的问题,您必须考虑 DBNull 为何存在?

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

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

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

【讨论】:

  • 哦,要获得死灵法师徽章吗? :) 我在 DB 中看到了不止一个 null 值的好用例。事实上,有时我希望我可以定义超过 1 个特殊值。
  • 这是一个非常常见的用例:可选外键。如果有一个值(甚至是默认值),那么您需要在远程表中记录。但是,空值不需要远程表中的记录。您当然可以说 - “好的,但是然后创建 远程表中具有固定 ID 的默认记录”。但是,您需要在应用程序代码中硬编码 DB ID,并且您还需要记住在列出该远程表中的值的任何位置都跳过该记录。
  • 我确实看到几乎任何null 值都可以通过将一列分成两列来删除 - 一个包含“nullness”(甚至可能包含其他特殊值,就像我在上面想要的那样)和另一个实际数据(仅在“nullness”为false 时使用)。但这会更好吗?我不确定...我的第一直觉说代码中会有很多很多 IFs 和 SQL 中的 CASEs,因为我现在需要查看两列而不是一列。而且我需要大量 CHECK 约束来确保数据完整性。
猜你喜欢
  • 2020-11-10
  • 2012-12-09
  • 2016-04-14
  • 2010-10-27
  • 2023-03-04
  • 2011-09-06
  • 2012-11-14
  • 2016-11-30
  • 2012-05-19
相关资源
最近更新 更多