传递参数给SQLCommand的最佳方法是什么?

64

什么是将参数传递给SQLCommand的最佳方法?您可以执行以下操作:

cmd.Parameters.Add("@Name", SqlDbType.VarChar, 20).Value = "Bob";

或者

cmd.Parameters.Add("@Name", SqlDbType.VarChar).Value = "Bob";

或者
cmd.Parameters.Add("@Name").Value = "Bob";

第一个似乎在性能或错误检查方面更“好”。但我想更明确地了解。

5个回答

66

里面在发生什么?

您引用了几个Add的重载参数列表。这些是方便方法,直接对应于SqlParameter类的构造函数重载。它们基本上使用与您调用的方便方法具有相同签名的任何构造函数来构造参数对象,然后像这样调用SqlParameterCollection.Add(SqlParameter)

SqlParameter foo = new SqlParameter(parameterName, dbType, size);
this.Add(foo);
AddWithValue方法与Add类似,但更加方便,可以设置参数值。然而,它实际上是为了解决框架缺陷而引入的。引用MSDN所说,
“因为通过传递整数和字符串可能会被解释为参数值或相应的SqlDbType值之一,所以 Add 方法的一个重载已经被弃用,该方法接受字符串和对象作为参数。使用 AddWithValue 来指定参数名称和值添加参数。” SqlParameter 类的构造函数重载仅用于设置实例属性的方便性。它们缩短了代码,对性能影响很小:构造函数可能会绕过设置器方法并直接操作私有成员。如果有差异,那也不会太大。
要注意以下内容(来自MSDN):
“对于双向和输出参数以及返回值,必须设置 Size 的值。对于输入参数则不需要,如果没有显式设置,则在执行参数化语句时会从指定参数的实际大小中推断出该值。”
默认类型是输入。然而,如果像这样允许大小被推断,而且在循环中重复使用参数对象(您说您关心性能),则大小将由第一个值设置,并且任何后续的比此更长的值都将被截断。显然,这只对可变长度值(如字符串)有意义。
如果在循环中重复传递相同的逻辑参数,我建议您在循环外创建一个 SqlParameter 对象,并适当调整其大小。过度设置 varchar 是无害的,因此如果您难以获得精确的最大值,请将其设置为比您预计该列要大。由于您正在回收对象而不是为每个迭代创建新对象,因此即使您过度调整大小,内存消耗在循环期间也可能会减少。
说实话,除非您处理数千个调用,否则所有这些都不会产生太大的差异。 AddWithValue 创建一个新对象,避开了大小问题。它简短易懂,容易理解。如果您需要循环执行数千次,则使用我的方法。如果不是,请使用 AddWithValue 使您的代码简单易于维护。
自从我写下这篇文章以来,2008年已经过去很久了。世界发生了变化。有了新的日期类型,直到最近日期出现问题才让我考虑到扩展的影响。
对于那些不熟悉术语的人来说,扩展和收缩是数据类型转换的特性。如果将 int 分配给 double,则不会丢失精度,因为 double 更宽。这样做始终是安全的,因此转换是自动的。这就是为什么可以将 int 分配给 double,但反过来必须进行显式转换——double 到 int 是一种收缩转换,可能会丢失精度。
这也适用于字符串:NVARCHAR 比 VARCHAR 更宽,因此可以将 VARCHAR 分配给 NVARCHAR,但反过来则需要进行转换。比较起来是有效的,因为 VARCHAR 隐式扩展到 NVARCHAR,但这将干扰索引的使用!
C# 字符串是 Unicode 的,因此 AddWithValue 将生成一个 NVARCHAR 参数。在另一端,VARCHAR 列值扩展到 NVARCHAR 进行比较。这不会阻止查询执行,但会阻止索引的使用。这很糟糕。
您可以采取什么

我突然有一种强烈的欲望,想要清除我的数据库中的VARCHAR,并且需要双倍的存储空间和更多的内存来运行查询。这在托管数据库方面是更大的问题。我会根据领域需求使用NVARCHAR。 - Mitch Wheat
@MitchWheat “..并且将存储容量翻倍…” 那又怎样呢?存储价格正在暴跌。_手机_以千兆字节为单位测量存储容量。如果托管存储太贵,那就不要使用它。云只是对于地理分布式系统而言是个好主意。对于其他所有情况,它只是遵循流行词的套路。 - Peter Wone
1
不仅仅是存储大小的问题。任何设计过大型和/或高性能数据仓库的人都会告诉你,任何好的数据库设计师都会告诉你:使用最适合域类型的数据类型,而不仅仅是“将所有字符串列类型更改为NVARCHAR”。 - Mitch Wheat
1
一切都要看上下文。谁会对长字符串进行索引查找?数据库设计是完全不同的论点。我确实说过我的数据库,它们既不是庞大到惊人,也不是设计不良。但我会修改这个陈述,因为有些人可能会盲目地应用它。 - Peter Wone

61

您也可以使用AddWithValue(),但要注意可能发生错误的隐式类型转换。

cmd.Parameters.AddWithValue("@Name", "Bob");

11
使用AddWithValue()可能会出现问题。 - Joel Coehoorn
3
这是另一种方法,但问题在于最好的方式肯定不是这个。 - Philippe
1
但要注意可能发生的错误隐式类型转换。那么这怎么成为最佳方法和被接受的答案呢?! - Ian
@JoelCoehoorn +1 https://www.dbdelta.com/addwithvalue-is-evil/ - Haithem KAROUI

7

我曾经使用过您的选项1:

cmd.Parameters.Add("@Name", SqlDbType.VarChar, 20).Value = "Bob";

这个方法很好用,但后来我开始使用.AddWithValue,它非常简单。在使用了成千上万次后,它从未给我带来问题。不过需要注意的是,我几乎总是将私有变量传递给我的类,因此我不必太担心隐式类型转换的问题。


5

1
我也使用#1,因为cmd.Prepare()要求变量大小的长度,比如nvarchar等。 - J Pollack

3

这取决于您的应用程序。我个人更喜欢第二种方式,因为如果我更改了存储过程参数的长度,我不想改变我的DAO。但这只是我的个人看法,我不知道是否会有任何性能损失或其他问题。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接