最近發現了一個之前開發過的網站,大約兩、三天就會發生「已經開啟一個與這個 Command 相關的 DataReader,必須先將它關閉。」的錯誤訊息,整個網站可以正常編譯,代表語法沒問題,測試的時候可以正常執行也不會發生 Exception,但只要一到了客戶那邊的主機執行個幾天就會開始出現問題,但通常多 reload 幾次網頁就會正常執行就好了,完全是一個很詭異的狀況。
程式碼很簡單,如下:
public class Counter
{
protected static ExtensionDataSetTableAdapters.ClickBlogCounterTableAdapter
ClickBlogCounterTA = new ExtensionDataSetTableAdapters.ClickBlogCounterTableAdapter();
public static int getClickBlogCount()
{
return (int)ClickBlogCounterTA.getCount();
}
}
錯誤出現在 "return (int)ClickBlogCounterTA.getCount();" 這一行,其中 ClickBlogCounterTA 是在 Typed DataSet 中的一個 TableAdapter 物件,而這個靜態方法是在 App_Code 底下的一個類別中。
完整的錯誤訊息如下:
System.Web.HttpUnhandledException: 已發生類型 'System.Web.HttpUnhandledException' 的例外狀況。 ---> System.InvalidOperationException: 已經開啟一個與這個 Command 相關的 DataReader,必須先將它關閉。
於 System.Data.SqlClient.SqlInternalConnectionTds.ValidateConnectionForExecute(SqlCommand command)
於 System.Data.SqlClient.SqlConnection.ValidateConnectionForExecute(String method, SqlCommand command)
於 System.Data.SqlClient.SqlCommand.ValidateCommand(String method, Boolean async)
於 System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
於 System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
於 ExtensionDataSetTableAdapters.ClickBlogCounterTableAdapter.getCount() 於 c:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\Temporary ASP.NET Files\root\bcc246a4\3746b4ea\App_Code.eopvwaqp.10.cs: 行 8729
於 Counter.getClickBlogCount() 於 c:\AAA\BBB\App_Code\Counter.cs: 行 62
......
這段 Code 不是我寫的,加上最近有點忙,所以問題一直擱著,但我找了個幾個朋友讓他們看 Code 請他們找出問題所在,但他們也都覺得這段 Code 沒問題,大家也提出一些「想法」與「解決方案」,大多人提供兩點建議:
- 加上 try / catch 就好啦:基本上,這是最爛的答案了,這等於是逃避問題嘛!不過這也是最懶、最快、最省事、也最輕鬆的解決方案了,但這不是我要的選項。
- 這一看就知道是微軟的問題嘛,錯誤是在 Typed DataSet 自動產生的程式碼中出錯的啊:ㄟ...,我也曾經一度懷疑啦,不過就以往的經驗來說,通常都是自己寫的程式錯誤的機率比較高,這應該是排在最後的一個選項了,若真的是這樣,就只好用第一點的建議了。^_^
就因為錯誤不容易重現,程式碼又可以正常執行,我昨天花了些時間仔細推敲程式碼的結構,最後終於看出問題所在,並將這個問題徹底解決了。
首先有個很重要的觀念需要說明,若你在 ASP.NET 中使用靜態欄位(static field)的話,這個靜態欄位物件會一直留存在 ASP.NET 的應用程式中 (HttpApplication),也就是說當 ASP.NET 頁面處理完成之後,該物件還是一樣存在 HttpApplication 中,並且所有 HTTP Request 都會共用這個靜態欄位變數,所以只要網站流量變多的時候就很有可能會發生資源衝突的情況,導致 TableAdapter 在進行資料處理的時候因為只共用一條連線,而造成連線狀態彼此衝突而混亂的情況。
我個人在寫 ASP.NET 就是因為知道會有這種狀況,所以本身就很少用 static 宣告變數,除非你真的很清楚使用 static 變數的時機與潛在會發生的問題,就可以大膽去用啦。
講到這裡,讓我想到一個好玩的排行榜,就是當程式不會動或有問題的時候,通常程式設計師的回答如下:
- 第 20 名:這很奇怪喔。
- 第 19 名:以前從來不會這樣啊!
- 第 18 名:昨天明明會動的啊!
- 第 17 名:怎麼可能~
- 第 16 名:這一定是機器的問題。
- 第 15 名:你到底是打了什麼才讓程式當掉的?
- 第 14 名:一定是你的資料有問題。
- 第 13 名:我已經好幾個禮拜沒碰那一段程式了。
- 第 12 名:你一定是用到舊版了。
- 第 11 名:一定是巧合!為什麼這種壞運氣只讓你碰上。
- 第 10 名:我不可能什麼功能都測試到吧,有 bug 是正常的!
- 第 9 名:這個不可能是那個的原始碼!
- 第 8 名:這程式應該是會動的,只是我寫好後還沒做測試。
- 第 7 名:可惡!一定有人改了我的程式。
- 第 6 名:你有檢查過你的電腦有沒有病毒嗎?
- 第 5 名:儘管這功能還不能動啦,你覺得他如何?
- 第 4 名:在你的系統不能用那一個版本的程式啦!
- 第 3 名:你幹嘛要那樣操作,都是你的問題。
- 第 2 名:程式發生問題時你在哪裡?
- 第 1 名:在我的機器明明就可以動啊!
- 萬用答案:電腦請重開,應該就會好了!
另外,工程師常說的話還有以下幾項,你也可以看看你常說哪幾項:
- 25.都這樣了,還不work,搞什麼?
- 24.你可能中毒了喔。
- 23.一定是有人改了我的程式。
- 22.已經可以了,不過還沒測試過喔。
- 21.都好了啊,還沒測試過就是了。
- 20.我不是已經修好了嗎?
- 19.這個不能那個。(THIS can't do THAT.)
- 18.我一個人又測不完!
- 17.怎麼這麼衰!
- 16.沒問題,馬上好!
- 15.當然,當然,我再修一修就可以了。
- 14.快好了,快好了。
- 13.好啦,只不過一個小功能嘛!
- 12.你拿錯執行檔了。
- 11.可以,可以,來得及。
- 10.我可沒動過這個模組喔!
- 9.你的測試資料一定有錯!(我那邊不會啊!)
- 8.不可以這樣操作的啦!
- 7.你的作業系統(驅動程式)升級了沒有啊?
- 6.機器好像壞了。
- 5.怎麼可能?!
- 4.哦,這程式還要改一下。
- 3.昨天還好好的呀!
- 2.我從來不知道有這種事。
- 1.奇怪...
- .
- .
- .
- .
- .
- .
- .
- 最後,也是最常用的
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- .
- 0.更