解决(另一个 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的一个引用,本质上是一个封箱的过程。

posted @ 2010-04-27 16:37  巡山二代王  阅读(3017)  评论(3编辑  收藏  举报