里面发生了什么?
您引用了几个重载的参数列表Add
。这些是直接对应于SqlParameter
类的构造函数重载的便利方法。它们本质上使用与您调用的便捷方法具有相同签名的任何构造函数来构造参数对象,然后SqlParameterCollection.Add(SqlParameter)
像这样调用:
SqlParameter foo = new SqlParameter(parameterName, dbType, size);
this.Add(foo);
AddWithValue
类似,但更方便,还设置了值。但是,它实际上是为了解决框架缺陷而引入的。引用 MSDN,
它的重载Add
采用字符串和对象已被弃用,因为可能与
SqlParameterCollection.Add
采用 aString
和SqlDbType
枚举值的重载存在歧义,其中将整数与字符串一起传递可能被解释为参数值或相应的
SqlDbType
值。AddWithValue
每当您想通过指定其名称和值来添加参数时使用。
类的构造函数重载SqlParameter
仅仅是设置实例属性的便利。它们缩短了代码,对性能的影响很小:构造函数可以绕过 setter 方法并直接对私有成员进行操作。如果有区别,那也不会太大。
我该怎么办?
请注意以下内容(来自 MSDN)
对于双向和输出参数以及返回值,您必须设置 的值Size
。这对于输入参数不是必需的,如果没有显式设置,则在执行参数化语句时从指定参数的实际大小推断该值。
默认类型是输入。但是,如果您允许像这样推断大小并且在循环中回收参数对象(您确实说过您关心性能),那么大小将由第一个值设置,任何后续值都将是剪辑。显然,这仅对可变长度值(例如字符串)有意义。
如果您在循环中重复传递相同的逻辑参数,我建议您在循环外创建一个 SqlParameter 对象并适当调整其大小。过大的 varchar 是无害的,所以如果它是一个 PITA 以获得确切的最大值,只需将其设置为比您期望的列更大。因为您正在回收对象而不是为每次迭代创建一个新对象,所以即使您对超大尺寸感到有点兴奋,循环期间的内存消耗也可能会下降。
说实话,除非您处理数千个电话,否则这些都不会产生太大影响。AddWithValue
创建一个新对象,回避大小问题。它简短而甜美,易于理解。如果您遍历数千个,请使用我的方法。如果您不这样做,请使用它AddWithValue
来保持您的代码简单且易于维护。
2008 年是很久以前的事了
自从我写这篇文章以来的几年里,世界发生了变化。有新的日期类型,还有一个问题没有出现在我的脑海中,直到最近的一个日期问题让我思考扩大的含义。
对于那些不熟悉这些术语的人来说,扩大和缩小是数据类型转换的特性。如果将 int 分配给 double,则不会损失精度,因为 double 更“宽”。这样做总是安全的,因此转换是自动的。这就是为什么你可以将一个 int 分配给一个双精度,但反过来你必须进行显式转换 - 双精度到 int 是一种缩小转换,可能会损失精度。
这可以应用于字符串:NVARCHAR 比 VARCHAR 更宽,因此您可以将 VARCHAR 分配给 NVARCHAR,但反之则需要强制转换。比较有效,因为 VARCHAR 隐式扩展到 NVARCHAR,但这会干扰索引的使用!
C# 字符串是 Unicode,因此 AddWithValue 将产生一个 NVARCHAR 参数。在另一端,VARCHAR 列值扩大到 NVARCHAR 以进行比较。这不会停止查询执行,但会阻止使用索引。这是不好的。
你能为这个做什么?您有两种可能的解决方案。
- 显式键入参数。这意味着不再有 AddWithValue
- 将所有字符串列类型更改为 NVARCHAR。
放弃 VARCHAR 可能是最好的主意。这是一个具有可预测后果的简单更改,它改善了您的本地化故事。但是,您可能没有此选项。
这些天我没有做很多直接的 ADO.NET。Linq2Sql 现在是我的首选武器,编写此更新的行为让我想知道它是如何处理这个问题的。我突然渴望通过 VARCHAR 列检查我的数据访问代码以进行查找。
2019 年,世界再次前行
Linq2Sql 在 dotnet Core 中不可用,所以我发现自己在使用 Dapper。[N]VARCHAR 问题仍然存在,但它不再被埋没。我相信人们也可以使用 ADO,所以这方面的事情已经完成了一个完整的循环。