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 {
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}
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}
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等列来记录异常的一些主要属性),这一部分就不赘述了。