這兩天在對以前的系統進行重構。以前是在資料層發現sql異常後,就地進行處理,跳轉到出錯頁面。這樣處理的問題是資料層的代碼只能被使用者請求的頁面所涉及到的功能進行調用;而整個系統中中還包括一個windows服務項目,即使在網站項目內部也包括了一個後台背景工作執行緒來非同步處理一些複雜請求,它們也需要使用資料層。接下來,給出自己在異常處理這個部分的重構步驟:
1.建立一個自己的異常類。有一點是我比較困惑的,如果我通過SqlCommand來執行一個查詢或者更新,由於種種原因操作沒有成功,拋出一個SqlException,那麼在SqlException裡面應當有一個屬性來列印查詢或更新的sql命令文本。就我個人而言,我覺得這是對開發人員最有協助的資訊。雖說異常裡提供了調用棧,可以幫我定位到出錯的資料層、邏輯層或顯示層方法,但查看這些方法的原始碼、找出裡面執行資料庫操作的相關語句效率還是太低了。可我找了半天也沒找到,如果哪位同志清楚的話能不能跟我說一下?如果確實沒有這個屬性,請問又是為什麼呢?我自己的異常類非常簡單,除了一個SqlException類型的對象,就是一個表示sql命令的文本,代碼如下: 1public class SqlTextException : Exception
2 {
3 private SqlException baseException = null;
4 private string sqlText = null;
5
6 public SqlTextException(SqlException e, string sql)
7 {
8 baseException = e;
9 sqlText = sql;
10 }
11
12 public SqlException BaseException
13 {
14 get
15 {
16 return baseException;
17 }
18 }
19
20 public String SqlText
21 {
22 get
23 {
24 return sqlText;
25 }
26 }
27 }
2.接下來,在資料層的相關位置截獲sql異常,並封裝成SqlTextException類型繼續拋出,以資料層的一個方法舉例如下: 1public Object ExecuteScalar(string cmdText, SqlParameter[] sqlParams)
2{
3 if (cmdText == null)
4 return null;
5
6 Object result = null;
7 SqlCommand command = this.PrepareCommand(cmdText, sqlParams);
8
9 try {
10 connection.Open();
11 result = command.ExecuteScalar();
12 }
13 catch (SqlException e) {
14 SqlTextException myE = new SqlTextException(e, cmdText);
15 throw myE;
16 }
17 finally {
18 connection.Close();
19 }
20 return result;
21}
3.異常被建立了,也從資料層被拋出了,接下來就是對異常的處理了。正如隨筆開頭所說的那樣,資料層會在兩種運行環境中被用到,一種我們姑且稱之為同步Web環境,這種環境的特點有3:(1)程式的運行是由用戶端對伺服器端頁面的請求直接引發的;(2)當前線程位於w3wp背景工作處理序管理的線程池中;(3)使用者正在用戶端等待頁面重新整理。而另一種運行環境,要麼代碼是在伺服器端的後台運行(windows服務),要麼代碼是另起一個背景工作執行緒在幕後處理(任務隊列),我們姑且統稱為非Web環境。下面就對這兩種環境分別敘述。
同步Web環境在整個系統中佔主要地位,如果在所有可能拋出異常的方法裡都進行異常的捕獲和處理,工作量將十分巨大。還好,ASP.NET已經為我們定義了一個很好的異常處理結構(具體可參見msdn),我們可以在某個時刻(具體來說,就是Page_Error事件和Application_Error事件中)對頁面或者整個應用程式的未處理異常(異常拋出時,CLR會向上遍曆整個調用棧來查看與被拋出異常物件類型相匹配的catch塊;若未找到這樣的catch塊,就會出現一個unhandled exception)統一進行處理。最後我的實現是使用了Application_Error事件,即在Global.ascx中加入如下程式碼片段:
1void Application_Error(object sender, EventArgs e)
2{
3 Exception objErr = Server.GetLastError().GetBaseException();
4 GEA52.Rule.SqlBase.SqlTextException myExp = objErr as GEA52.Rule.SqlBase.SqlTextException;
5 if (myExp != null)
6 {
7 string err = "引發錯誤的sql語句為:\n" + myExp.SqlText +"\n; 系統返回的出錯資訊:"+myExp.BaseException.Message;
8 Server.ClearError();
9 GEA52.Rule.CommonRule.ShowError(err);
10 }
11 else
12 {
13 string err = "在Application_Error事件中捕獲異常:\n" +
14 "Error in:" + Request.Url.ToString() +
15 "\nError Message:" + objErr.Message.ToString() +
16 "\nStack Trace1:" + objErr.StackTrace.ToString();
17
18 Server.ClearError();
19 GEA52.Rule.CommonRule.ShowError(err);
20 }
21}
非Web環境可就沒那麼幸運了,我還沒能找到一個統一的未處理異常處理位置(哪位兄弟知道,望不吝賜教^_^)。好在這部分代碼不多,我在一些主要的方法調用處進行了異常捕獲,並把捕獲後的異常儲存資料庫(在資料庫裡建了ErrorLog表,表包含Message,Source,TargetSite,StackTrace等列來記錄異常的一些主要屬性),這一部分就不贅述了。