始终将 DBNull SqlParameter 键入为 int 是不是安全?

Posted

技术标签:

【中文标题】始终将 DBNull SqlParameter 键入为 int 是不是安全?【英文标题】:Is it safe to always type a DBNull SqlParameter as an int?始终将 DBNull SqlParameter 键入为 int 是否安全? 【发布时间】:2016-12-21 18:21:53 【问题描述】:

最近,我们遇到了以下问题:

Null value in a parameter varbinary datatype

简而言之:myCommand.Parameters.AddWithValue("@SomeParameter", DBNull.Value); 显然将 DBNull 参数“类型化”为 nvarchar,它可以隐式转换为几乎所有其他类型,但不幸的是,不是 varbinary,从而产生以下错误:

不允许从数据类型 nvarchar 到 varbinary(max) 的隐式转换。使用 CONVERT 函数运行此查询。

不幸的是,链接问题中建议的解决方案不适用,因为我们在数据访问库的深处使用AddWithValue,该库旨在从参数类型推断数据类型并且不支持添加“真实”SQL服务器类型。

我通过将 DBNull 参数显式键入为 ints 来“修复”了这个问题:

void MyImprovedAddWithValue(SqlCommand cmd, string key, object value)

    if (value == DBNull.Value) 
    
        cmd.Parameters.Add(key, SqlDbType.Int).Value = DBNull.Value;
    
    else
    
        cmd.Parameters.AddWithValue(key, value);
    

似乎有效。显然,类型为 int 的 NULL 可以隐式转换为 SQL Server 支持的任何其他类型。

忽略AddWithValue has some well-known shortcomings(我们非常清楚)这一事实,使用这种方法是否会出现任何问题? SqlParameterCollection.AddWithValue 的设计者是否有充分的理由不“默认”这样做,而我忽略了这一点?

【问题讨论】:

AddWithValue 与 DBNULL 配合使用效果很好,当 value == null 时会出现问题 我建议您完全避免 AddWithValue 并明确指定参数的正确类型。这不仅可以避免意外,还可以在某些情况下显着提高性能。 当您在***代码中构造参数时,这一切都很好,但对于在提取层深处构造 SqlParameters 的框架或 API 来说没有多大意义。跨度> @MikhailLobanov:当目标数据类型为 varbinary 时不会,请参阅链接问题。 @DanGuzman:我同意,这就是我们下次完全重写数据访问层时要做的事情。已经吸取了教训。目前,我们有大量的遗留代码提供正确的参数类型,并且没有计划在短期内重写。 【参考方案1】:

不,这并不总是安全的,根据conversion chart(在“隐式转换”下)。 INT 可以隐式转换为很多,但不能转换为 DATETIMEDATETIMEOFFSETDATETIME2UNIQUEIDENTIFIERIMAGENTEXTTEXT、@987654 CLR UDT 和 HIERARCHYID。其中一些类型比其他类型更常见,但与DATETIME2UNIQUEIDENTIFIER 的不兼容在实践中很容易遇到。

没有任何 T-SQL 类型可以隐式转换为其他所有类型,因此如果您不知道目标类型,则确实没有好方法。在所有类型中,字符类型CHARVARCHAR 具有最多的隐式转换,仅缺少BINARYVARBINARY(您的情况)和很少使用的TIMESTAMPNCHARNVARCHAR 紧随其后,另外缺少已弃用的 IMAGE 类型。考虑到这一切,NVARCHAR 显然是默认值的最佳选择,因为它可以表示所有字符串,并且仅在隐式转换为 BINARY 类型时失败,这比 INT 不转换的类型不太常见到(即使是NULL)。

【讨论】:

哎呀,缺少的uniqueidentifier 转换会破坏很多代码。谢谢!这不是我所希望的,但它完全回答了这个问题。显然,用文字 NULL 替换参数是解决这个问题的唯一方法......【参考方案2】:

AddWithValue 有更多问题。存在query plan cache pollution 的问题是因为它创建了NVARCHAR(X) 类型的参数,其中X 是值的确切长度。由于NVARCHAR(73) 是与NVARCHAR(74) 不同的类型,SQL Server 查询计划缓存被许多仅在参数类型上不同的相同计划污染。另一个问题是当VARCHAR 类型的列参与比较时,NVARCHAR 类型的参数会导致类型转换,而不是取消在所述列上使用索引的资格。

反对AddWithValue 的原因有很多,我从不使用它,从不推荐它,并且总是在它周围添加 typed 包装器。我建议你也这样做。

【讨论】:

以上是关于始终将 DBNull SqlParameter 键入为 int 是不是安全?的主要内容,如果未能解决你的问题,请参考以下文章

将 null 分配给 SqlParameter

对象不能从 DBNull 转换为其他类型。

System.DBNull.System.IConvertible.ToDateTime

如何将 collasce null 运算符与 DbNull.Value 一起使用?

如何查找无法将 DBNull 转换为 String 的列?

无法将参数值从 SqlParameter 转换为字符串