【问题标题】:The SqlParameter is already contained by another SqlParameterCollection - Does using() {} cheat?SqlParameter 已被另一个 SqlParameterCollection 包含 - using() {} 作弊吗?
【发布时间】:2011-12-11 20:56:34
【问题描述】:

虽然使用如下所示的using() {}(原文如此)块,并假设cmd1 不会超出第一个using() {} 块的范围,那么为什么第二个块会抛出带有消息的异常

SqlParameter 已被另一个 SqlParameterCollection 包含

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

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 对象,没有重复使用。它被包裹在一个异步重试循环中,所以它可能是相同的根本原因,只是没有以相同的方式避免。

标签: c# .net sql-server ado.net


【解决方案1】:

我怀疑SqlParameter“知道”它属于哪个命令,并且在处理命令时不会清除该信息,但在您调用command.Parameters.Clear()时会清除。

我个人认为我首先会避免重复使用这些对象,但这取决于你:)

【讨论】:

  • 谢谢。我怀疑是这样的。这也意味着 SqlParameter 正在将自己与已处置的对象相关联,我不确定这是一件好事
  • @JohnGathogo:嗯,它与一个在关联形成之后 放置的对象相关联。当然,这并不理想。
  • 给其他人的备注。在退出第一个 using 块之前,我必须执行 Clear。进入我的第二个using 块时这样做仍然会引发此错误。
  • @JonSkeet 必须重新创建相同的参数集,只是为了进行另一个查询,这是愚蠢的。看起来像紧耦合
【解决方案2】:

使用块并不能确保对象被“销毁”,只是调用了Dispose() 方法。实际所做的取决于具体的实现,在这种情况下,它显然不会清空集合。这样做的目的是确保正确处置不会被垃圾收集器清理的非托管资源。由于 Parameters 集合不是非托管资源,因此它不会被 dispose 方法清除。

【讨论】:

    【解决方案3】:

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

    【讨论】:

      【解决方案4】:

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

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

      【讨论】:

        【解决方案5】:

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

        当我调用以下函数时,其中传递了 2 次相同的 sql 参数。第一次调用数据库,调用正常,第二次报上述错误。

            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;
                }
        

        【讨论】:

          【解决方案6】:

          我遇到了这个特殊错误,因为我使用相同的 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);
          

          【讨论】:

            【解决方案7】:

            我遇到此异常是因为我未能实例化参数对象。我以为这是在抱怨两个具有同名参数的程序。它抱怨同一个参数被添加了两次。

                        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.
            

            【讨论】:

              【解决方案8】:

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

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

              原因
              我将 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();
              

              【讨论】:

                【解决方案9】:

                我就是这样做的!

                        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));
                        }
                

                【讨论】:

                  【解决方案10】:

                  如果您使用的是 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&lt;T&gt;()的调用实际上返回了一个DbRawSqlQuery&lt;T&gt;,它实现了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 对象来修复此内部错误。

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

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2017-10-17
                    • 1970-01-01
                    • 2012-06-14
                    • 2011-09-11
                    • 1970-01-01
                    • 2019-04-10
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多