• 转:解决(另一个 SqlParameterCollection 已包含带有 ParameterName“@UserName”的 SqlParameter。)同时讨论CopyTo和Clone


    最近在调试SqlHelper程序的时候发现,利用从SqlCommandBuilder.DerivedParameters(command)得到的command.Parameters(为SqlParameterCollection)传给ExecuteNonQuery中的SqlParameter[],代码如下,

    public static SqlParameter[] getParameters(SqlConnection conn,string spName,bool needsreturnvalue)
      {
       SqlCommand command=new SqlCommand(spName,conn);
       command.CommandType=CommandType.StoredProcedure;

       conn.Open();
       SqlCommandBuilder.DeriveParameters(command);
       conn.Close();
       if(!needsreturnvalue)
       {
                   command.Parameters.RemoveAt(0);
       }
       SqlParameter[] sqlparams=new SqlParameter[command.Parameters.Count];
              
       command.Parameters.CopyTo(sqlparams,0);

       return sqlparams;
       }

    传入ExecuteNonQuery

    ExecuteNonQuery(...)

    {

    ...

    if(SqlParams!=null)
        {
         for(int i=0;i     {
          command.Parameters.Add(SqlParams[i]);
         }
        }

    ...

    }

    出现The SqlParameter with ParameterName 'xxxx' is already contained by another SqlParameterCollection

    下面讨论解决办法

    对用command.Parameters.CopyTo和SqlParameter[]的Clone

    (1)SqlParameter[] sqlparams=new SqlParameter[command.Parameters.Count];

       command.Parameters.CopyTo(sqlparams,0);

    (2)SqlParameter[] sqlparams=new SqlParameter[command.Parameters.Count];

    command.Parameters.CopyTo(sqlparams,0);
       SqlParameter[] sqlparams1=(SqlParameter[])sqlparams.Clone();(这种方法只是为了说明clone的意思)

    CopyTo和数组的Clone都是浅备份(shadow copy)因此仍然和command.Parameters的数组元素指向相同的对象。而当调用ExecuteNonQuery时将得到的SqlParameter数组传给另外的command.Parameters ,而由于垃圾回收机制可能并未将上一个command包括parameters回收。因此理论上两个command.Parameters同时指向相同的对象。而由于framework机制这个是不允许的将会跑出异常。

    原因
    #region from dudu's blog

    既然异常是在执行command.Parameters.Add(p);产生的,那我们要首先分析一下这里为什么会抛出异常?
    用Reflector要查看一下SqlParameterCollection.Add的代码:

    public SqlParameter Add(SqlParameter value)
    ...{
     this.OnSchemaChanging();
    this.AddWithoutEvents(value);
    return value;
     
    }

    继续看看AddWithoutEvents的代码:

    private void AddWithoutEvents(SqlParameter value)
    ...{
     this.Validate(-1, value);
    value.Parent = this;
    this.ArrayList().Add(value);
     
    }

    这里的value.Parent = this;应该引起我们的注意,参数value的Parent属性在SqlParameterCollection.Add
    中被改变,这就使SqlParameter value与SqlParameterCollection关联起来,一个SqlParameter value只能
    同时属于一个SqlParameterCollection。那我们再看看Validate(-1, value):

    internal void Validate(int index, SqlParameter value)
    ...{
     if (value == null)
    ...{
     throw ADP.ParameterNull("value", this, this.ItemType);
     
    }
    if (value.Parent != null)
    ...{
     if (this != value.Parent)
    ...{
     throw ADP.ParametersIsNotParent(this.ItemType, value.ParameterName, this);
     
    }
    if (index != this.IndexOf(value))
    ...{
     throw ADP.ParametersIsParent(this.ItemType, value.ParameterName, this);
     
    }
     
    }
    string text1 = value.ParameterName;
    if (!ADP.IsEmpty(text1))
    ...{
     return;
     
    }
    index = 1;
    do
    ...{
     text1 = string.Concat("Parameter", index.ToString());
    index = (index + 1);
     
    }
    while ((-1 != this.IndexOf(text1)));
    value.ParameterName = text1;
     
    }
      从上面的代码就可以看出异常是如何产生的,如果value被另外一个SqlParameterCollection使用(this != value.Parent),就会引发异常。

    #endregion

    如果放置commnd.Parameters.Clear()则将Parameters设置为空引用,而framework机制只是限制两个SqlParameterCollection指向同一个对象,并没有限制数组和SqlParameterCollection指向同一个对象。此时单线程调用时不会出错。但因为web本身就是多线程,在多线程的情况下,只有通过deep copy才能避免SqlParameterCollection指向同一个对象,所以此种方法不可行

    解决的方法是

    SqlParameter[] clonedParameters = new SqlParameter[originalParameters.Length];

    for (int i = 0, j = originalParameters.Length; i < j; i++)
    {
      clonedParameters[i] = (SqlParameter)((ICloneable)originalParameters[i]).Clone();
    }
    这将执行一个深拷贝(deep copy)不会出现问题

    foreach(SqlParameter p in commandparams)
       {

         // block 1
                   command.Parameters.Add(p);

        // block 2

        if(p!=null)
        {
         if( (p.Direction==ParameterDirection.InputOutput||p.Direction==ParameterDirection.Input) && p.Value==null)
         {
                           p.Value=DBNull.Value;

         }
     
        }
       }

    在上面的一段代码中,block1和block2的顺序不重要,因为只是将p分配给ParametersCollection的一个引用,本质上是一个封箱的过程。

  • 相关阅读:
    5月18日InterlliJ IDea快捷键
    5月17日-集合构架Collection学习
    十一java作业1
    十一java作业2
    第一周,java模拟ATMdos界面程序源代码及感想
    8.27-9.2第八周
    8.20-8.26第七周
    8.13-8.19第六周
    8.6-8.12第五周
    7.30-8.5第四周
  • 原文地址:https://www.cnblogs.com/dadamoney/p/709169.html
Copyright © 2020-2023  润新知