代码改变世界

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

2010-10-30 21:59  elivsit  阅读(1350)  评论(0编辑  收藏  举报

最近在调试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的一个引用,本质上是一个封箱的过程。 本篇文章使用aigaogao Blog软件发布, “我的Blog要备份”