SqlParameter已被另一个SqlParameterCollection包含-using(){}是否作弊?


85

using() {}如下所示使用(sic)块时,并假设该块cmd1没有超出第一个using() {}块的范围,为什么第二个块会在消息中引发异常

SqlParameter已被另一个SqlParameterCollection包含

这是否意味着在块末尾销毁的资源和/或句柄(包括参数(SqlParameterCollection))cmd1不会释放?

using (var conn = new SqlConnection("Data Source=.;Initial Catalog=Test;Integrated Security=True"))
{
    var parameters = new SqlParameter[] { new SqlParameter("@ProductId", SqlDbType.Int ) };

    using(var cmd1 = new SqlCommand("SELECT ProductName FROM Products WHERE ProductId = @ProductId"))
    {
        foreach (var parameter in parameters)
        {
            cmd1.Parameters.Add(parameter);                
        }
        // cmd1.Parameters.Clear(); // uncomment to save your skin!
    }

    using (var cmd2 = new SqlCommand("SELECT Review FROM ProductReviews WHERE ProductId = @ProductId"))
    {
        foreach (var parameter in parameters)
        {
            cmd2.Parameters.Add(parameter);
        }
    }
}

注意:在第一个using(){}块的最后一个括号之前执行cmd1.Parameters.Clear()可以使您免于异常(并可能带来尴尬)。

如果需要重现,可以使用以下脚本创建对象:

CREATE TABLE Products
(
    ProductId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    ProductName nvarchar(32) NOT NULL
)
GO

CREATE TABLE ProductReviews
(
    ReviewId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
    ProductId int NOT NULL,
    Review nvarchar(128) NOT NULL
)
GO

我也看到了这一点,但该修复程序没有用。令人沮丧 而且我仅使用单个cmd对象,而不重用。它包装在异步重试循环中,因此可能是相同的根本原因,只是无法以相同的方式避免。
Ed Williams

Answers:


109

我怀疑SqlParameter“知道”命令的一部分,并且该信息在处理该命令时不会清除,而在您调用时清除command.Parameters.Clear()

我个人认为我一开始会避免重用对象,但这取决于您:)


1
谢谢。我怀疑是这种情况。这也意味着SqlParameter正在将自身与已处置的对象相关联,但我不确定它是否是一件好事
John Gathogo 2011年

@JohnGathogo:好吧,它与一个对象相关联,该对象在关联形成之后就被丢弃了。当然,这并不理想。
乔恩·斯基特

11
给别人的笔记。我必须Clear在退出第一个程序using块之前执行。进入我的第二个程序using段时仍会引发此错误。
Snekse 2014年

9

使用块并不能确保对象被“销毁”,仅Dispose()是调用了该方法即可。实际执行的操作取决于特定的实现,在这种情况下,显然不会清空集合。这样做的目的是确保正确处理不会被垃圾收集器清除的非托管资源。由于Parameters集合不是非托管资源,因此它并不完全令人惊讶,而dispose方法并未清除它。


7

添加cmd.Parameters.Clear(); 执行后应该没问题。


3

using定义范围,并自动调用Dispose()我们喜欢的范围。

如果另一个对象具有引用,则超出范围的引用不会使该对象本身“消失”,在这种情况下,parameters具有的引用就是这种情况cmd1


2

我也遇到了同样的问题,谢谢@Jon,基于我举的例子。

当我调用下面的函数时,两次传递相同的sqlparameter。在第一次数据库调用中,它被正确调用,但是在第二次数据库调用中,出现了上述错误。

    public Claim GetClaim(long ClaimId)
    {
        string command = "SELECT * FROM tblClaim "
            + " WHERE RecordStatus = 1 and ClaimId = @ClaimId and ClientId =@ClientId";
        List<SqlParameter> objLSP_Proc = new List<SqlParameter>(){
                new SqlParameter("@ClientId", SessionModel.ClientId),
                new SqlParameter("@ClaimId", ClaimId)
            };

        DataTable dt = GetDataTable(command, objLSP_Proc);
        if (dt.Rows.Count == 0)
        {
            return null;
        }

        List<Claim> list = TableToList(dt);

        command = "SELECT * FROM tblClaimAttachment WHERE RecordStatus = 1 and ClaimId = @ClaimId and ClientId =@ClientId";

        DataTable dt = GetDataTable(command, objLSP_Proc); //gives error here, after add `sqlComm.Parameters.Clear();` in GetDataTable (below) function, the error resolved.


        retClaim.Attachments = new ClaimAttachs().SelectMany(command, objLSP_Proc);
        return retClaim;
    }

这是常见的DAL功能

       public DataTable GetDataTable(string strSql, List<SqlParameter> parameters)
        {
            DataTable dt = new DataTable();
            try
            {
                using (SqlConnection connection = this.GetConnection())
                {
                    SqlCommand sqlComm = new SqlCommand(strSql, connection);

                    if (parameters != null && parameters.Count > 0)
                    {
                        sqlComm.Parameters.AddRange(parameters.ToArray());
                    }

                    using (SqlDataAdapter da = new SqlDataAdapter())
                    {
                        da.SelectCommand = sqlComm;
                        da.Fill(dt);
                    }
                    sqlComm.Parameters.Clear(); //this added and error resolved
                }
            }
            catch (Exception ex)
            {                   
                throw;
            }
            return dt;
        }

2

我遇到此特定错误是因为我在SqlParameter集合中使用相同的SqlParameter对象多次调用过程。发生此错误恕我直言的原因是SqlParameter对象与特定的SqlParameter集合相关联,并且您不能使用相同的SqlParameter对象来创建新的SqlParameter集合。

因此,代替此:

var param1 = new SqlParameter{ DbType = DbType.String, ParameterName = param1,Direction = ParameterDirection.Input , Value = "" };
var param2 = new SqlParameter{ DbType = DbType.Int64, ParameterName = param2, Direction = ParameterDirection.Input , Value = 100};

SqlParameter[] sqlParameter1 = new[] { param1, param2 };

ExecuteProc(sp_name, sqlParameter1);

/*ERROR : 
SqlParameter[] sqlParameter2 = new[] { param1, param2 };
ExecuteProc(sp_name, sqlParameter2);
*/ 

做这个:

var param3 = new SqlParameter{ DbType = DbType.String, ParameterName = param1, Direction = ParameterDirection.Input , Value = param1.Value };
var param4 = new SqlParameter{ DbType = DbType.Int64, ParameterName = param2, Direction = ParameterDirection.Input , Value = param2.Value};

SqlParameter[] sqlParameter3 = new[] { param3, param4 };

ExecuteProc(sp_name, sqlParameter3);

0

我遇到此异常是因为无法实例化参数对象。我以为它在抱怨两个具有相同名称参数的过程。它抱怨同一参数被添加两次。

            Dim aParm As New SqlParameter()
            aParm.ParameterName = "NAR_ID" : aParm.Value = hfCurrentNAR_ID.Value
            m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)
            aParm = New SqlParameter
            Dim tbxDriveFile As TextBox = gvNetworkFileAccess.Rows(index).FindControl("tbxDriveFolderFile")
            aParm.ParameterName = "DriveFolderFile" : aParm.Value = tbxDriveFile.Text
            m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)
            **aParm = New SqlParameter()**  <--This line was missing.
            Dim aDDL As DropDownList = gvNetworkFileAccess.Rows(index).FindControl("ddlFileAccess")
            aParm.ParameterName = "AccessGranted" : aParm.Value = aDDL.Text
            **m_daNetworkAccess.UpdateCommand.Parameters.Add(aParm)** <-- The error occurred here.

0

问题
遇到此问题时,我正在从C#执行SQL Server存储过程:

异常消息[另一个SqlParameterCollection已包含SqlParameter。]

原因
我将3个参数传递给存储过程。我添加了

param = command.CreateParameter();

总共只有一次。我应该为每个参数添加此行,这意味着总共3次。

DbCommand command = CreateCommand(ct.SourceServer, ct.SourceInstance, ct.SourceDatabase);
command.CommandType = CommandType.StoredProcedure;
command.CommandText = "[ETL].[pGenerateScriptToCreateIndex]";

DbParameter param = command.CreateParameter();
param.ParameterName = "@IndexTypeID";
param.DbType = DbType.Int16;
param.Value = 1;
command.Parameters.Add(param);

param = command.CreateParameter(); --This is the line I was missing
param.ParameterName = "@SchemaName";
param.DbType = DbType.String;
param.Value = ct.SourceSchema;
command.Parameters.Add(param);

param = command.CreateParameter(); --This is the line I was missing
param.ParameterName = "@TableName";
param.DbType = DbType.String;
param.Value = ct.SourceDataObjectName;
command.Parameters.Add(param);

dt = ExecuteSelectCommand(command);

解决方案
为每个参数添加以下代码行

param = command.CreateParameter();

0

这就是我的方法!

        ILease lease = (ILease)_SqlParameterCollection.InitializeLifetimeService();
        if (lease.CurrentState == LeaseState.Initial)
        {
            lease.InitialLeaseTime = TimeSpan.FromMinutes(5);
            lease.SponsorshipTimeout = TimeSpan.FromMinutes(2);
            lease.RenewOnCallTime = TimeSpan.FromMinutes(2);
            lease.Renew(new TimeSpan(0, 5, 0));
        }

0

如果您使用的是EntityFramework

我也有同样的例外。就我而言,我是通过EntityFramework DBContext调用SQL的。以下是我的代码以及如何修复它。

密码破损

string sql = "UserReport @userID, @startDate, @endDate";

var sqlParams = new Object[]
{
    new SqlParameter { ParameterName= "@userID", Value = p.UserID, SqlDbType = SqlDbType.Int, IsNullable = true }
    ,new SqlParameter { ParameterName= "@startDate", Value = p.StartDate, SqlDbType = SqlDbType.DateTime, IsNullable = true }
    ,new SqlParameter { ParameterName= "@endDate", Value = p.EndDate, SqlDbType = SqlDbType.DateTime, IsNullable = true }
};

IEnumerable<T> rows = ctx.Database.SqlQuery<T>(sql,parameters);

foreach(var row in rows) {
    // do something
}

// the following call to .Count() is what triggers the exception
if (rows.Count() == 0) {
    // tell user there are no rows
}

注意:上面的调用SqlQuery<T>()实际上返回DbRawSqlQuery<T>,它实现了IEnumerable

为什么调用.Count()会引发异常?

我尚未启动SQL Profiler进行确认,但我怀疑这.Count()正在触发对SQL Server的另一个调用,并且在内部它正在重用同一SQLCommand对象并尝试重新添加重复的参数。

解决方案/工作代码

我在里面添加了一个计数器foreach,这样我就可以保持行数而不必调用.Count()

int rowCount = 0;

foreach(var row in rows) {
    rowCount++
    // do something
}

if (rowCount == 0) {
    // tell user there are no rows
}

事后

我的项目可能正在使用EF的旧版本。较新的版本可能通过清除参数或处理SqlCommand对象来解决此内部错误。

或者,也许有明确的指令告诉开发人员.Count()在迭代之后不要调用DbRawSqlQuery,而我将其编码错误。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.