这篇文章中讨论的Bug是最近博客园频繁出现的两个异常:
1、"ArgumentException The SqlParameter with ParameterName '@EntryID' is already contained by another SqlParameterCollection."
2、"ArgumentException The SqlParameter with ParameterName
'@ItemCount' is already contained by another SqlParameterCollection."
    这个Bug我在.Text中的Bug 文章中已经讨论过,但当时并没有找出问题的真正原因,文章中的解决方法也没有解决问题。最后,在韩磊的指点下才消除了这个Bug。在这篇文章中, 我谈谈自己的一些心得。
    当出现上述两个异常时, 我首先想到的是找出异常抛出的位置。根据异常的内容,异常应该发生在数库访问代码中, 也就是SQLHelper.cs, .Text中所有的数据库访问都是通过SqlHelper。但在SqlHelper中并没有对异常进行捕获, 只有一处try...catch语句, 所做也只是在catch中关闭SqlConnection。为了发现异常在哪抛出的,需要增加捕获异常的代码,根据异常内容,可以判断出是在执行ExecuteReader过程中出现的异常。我就在ExecuteReader(SqlConnection connection, SqlTransaction transaction, CommandType commandType, string commandText, SqlParameter[] commandParameters, SqlConnectionOwnership connectionOwnership)中增加了try...catch代码,在catch中通过Logger.LogManager.CreateExceptionLog(e,"ExecuteReader Exception");将异常写入日志。在这里我犯一个错误:.Text中的日志是存在数据库中,CreateExceptionLog是要向数据库写入日志信息,但在catch中, 数据库访问已经出现了异常,再进行数据库写入操作,只会继续抛出异常。我这样寻找异常发生的位置显然是徒劳无获的,而且增加了新的异常,更不利于问题的解决。
    对于这两个异常,我自然而然认为问题出在SqlHelper中,所以我要在SqlHelper捕获到异常发生的位置。“        }

下面,我来分析一下原因,不对之处请大家指正。
既然异常是在执行command.Parameters.Add(p);产生的,那我们要首先分析一下这里为什么会抛出异常?
用Reflector要查看一下SqlParameterCollection.Add的代码:

.Text中SqlParameter引起的Bugpublic SqlParameter Add(SqlParameter value)
  解决这个问题的方法除了前面的每次调用DefaultEntryQueryParameter或
DefaultEntryParameters,重新创建SqlParameter[],也可以将DefaultEntryQueryParameter与DefaultEntryParameters变成非静态私有成员,但这种在\方法在多线程的情况下,也会出现同样的问题。最安全的方法就是每次使用SqlParameter,都重新创建SqlParameter的实例。 
  这个bug一直存在.Text中,那为什么现在才发现?而且有很多.Text的网站为什么没有发现这个Bug?因为这个Bug只会出
现在ExecuteReader中,所以即使发生异常,对系统没什么影响,只要重新刷新一下就行了。而且这个异常只会出现在SqlDataProvider的多个实例同时执行command.Parameters.Add(p)操作时,同时发生的概率与网站的访问量有关。以前博客园很少出现这个异常,最近因为博客园访问量变大,同时执行command.Parameters.Add(p)的概率变高了,所以异常出现的次数也变多了。

从这个Bug中,我们应该吸取两个教训:
1、慎用私有静态成员。
2、安全地使用SqlParameter,每次使用,每次新建。

非常感谢韩磊在解决这个问题中给予指点。

相关文章: