重用SqlDataRecord是否安全?

时间:2016-05-03 14:30:53

标签: c# sql-server table-valued-parameters

在实施表值参数时,生成IEnumerable<SqlDataRecord>以供参数使用的最常用方法之一是这样的代码(例如https://stackoverflow.com/a/10779567/18192):

public static IEnumerable<SqlDataRecord> Rows(List<int> simpletable)
{
    var smd = new []{ new SqlMetaData("id", SqlDbType.Int)};
    var sqlRow = new SqlDataRecord(smd);
    foreach (int i in simpletable)
    {
        sqlRow.SetInt32(0, i);
        yield return sqlRow;
    }
}
//...
var param = sqlCmd.Parameters.AddWithValue("@retailerIDs", Rows(mydata)); 
param.SqlDbType = SqlDbType.Structured;
param.TypeName = "myTypeName";

此代码似乎确实有效。虽然重用SqlMetaData并没有引发太多警钟,但在foreach循环之外声明SqlDataRecord让我感到非常怀疑:

修改了一个可变对象,然后反复出现。

作为解决这个问题的一个例子,在LinqPad中调用var x = Rows(new[] { 100, 200}.ToList()).ToList().Dump()会吐出200,200。这种方法似乎依赖于实现细节(行是单独处理的),但我没有看到任何承诺这样做的文档。

是否有一些减轻因素使这种方法安全?

3 个答案:

答案 0 :(得分:2)

如果你根本不需要foreach循环,我不明白你为什么要重复使用它。

我发现这个问题Is there a reason for C#'s reuse of the variable in a foreach?在另一个问题Is it better coding practice to define variables outside a foreach even though more verbose?中链接到这个答案,其中Jon Skeet回答说:

  

在循环外声明变量没有任何好处,除非你想在迭代之间保持它们的值。

     

(请注意,通常这不会产生任何行为差异,但如果变量是由lambda表达式或匿名方法捕获的,则情况并非如此。)

答案 1 :(得分:2)

不,重用变量是不安全的。代码一遍又一遍地修改同一个对象。它是BAD代码,应该返回一个新对象。这是一个快速的linqpad示例,显示了上述代码的问题:

void Main()
{
    //This code proves that the object is being modified.
   Thing prevRow = null;
    foreach (var curRow in Rows(new List<int>() { 1, 2, 3 }))
    {
        Console.WriteLine(curRow);
        Console.WriteLine(prevRow);
        prevRow = curRow;
    }

    //Because the object is modified instead of a new object being returned,
    // this code does something unexpected; it returns the same object 3
    // times! Instead of three unique objects representing the values 1, 2, 3.
    var rowsAsList = Rows(new List<int>() { 1, 2, 3 }).ToList();
    foreach (var curRow in rowsAsList)
    {
        Console.WriteLine(curRow);
    }
}

public class Thing
{
    public int i;
}

IEnumerable<Thing> Rows(List<int> simpletable)
{
    var sqlRow = new Thing() {i=-1};
    foreach (int i in simpletable)
    {
        sqlRow.i = i;
        yield return sqlRow;
    }
}

答案 2 :(得分:2)

  

这种方法似乎依赖于实现细节(行是   单独处理),但我没有看到任何文件   承诺这个。

     

是否有一些减轻因素使这种方法安全?

正如user1249190指出的那样,https://msdn.microsoft.com/en-us/library/microsoft.sqlserver.server.sqldatarecord.aspx的备注部分明确建议重用SQLDataRecord:

  

此类与SqlPipe一起用于将结果集发送到   来自托管代码存储过程的客户端。写作时常见   语言运行时(CLR)应用程序,您应该重用现有的   SqlDataRecord对象而不是每次都创建新对象。   创建许多新的SqlDataRecord对象可能会严重耗尽内存   并对业绩产生不利影响。

显然,此建议不适用于跨线程的使用:文档还明确警告&#34;不保证所有实例成员都是线程安全的。&#34;