zoukankan      html  css  js  c++  java
  • 解决(另一个 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的一个引用,本质上是一个封箱的过程。

  • 相关阅读:
    08 linux文件检索和编辑
    Mybatis3详解(二十)——Mybatis中使用的9种设计模式(转)
    Mybatis3详解(十八)——Mybatis运行原理之Mapper接口的动态代理过程
    Mybatis3详解(十七)——Mybatis运行原理之SqlSession的构建过程
    Mybatis3详解(十六)——Mybatis运行原理之SqlSessionFactory的构建过程
    Mybatis3详解(十五)——Mybatis整合Spring框架
    Mybatis3详解(十四)——Mybatis的分页
    Mybatis3详解(十三)——Mybatis逆向工程
    Mybatis3详解(十二)——Mybatis缓存
    Mybatis3详解(十一)——延迟加载
  • 原文地址:https://www.cnblogs.com/only_J/p/1722192.html
Copyright © 2011-2022 走看看