正文
首先我們來看一段未加事務的代碼:
SqlDAL.cs
public abstract class SqlDAL
{
#region ConnectionString
private SqlConnectionStringBuilder _ConnectionString = null;
/// <summary>
/// 字元串連接配接
/// </summary>
public virtual SqlConnectionStringBuilder ConnectionString
{
get
{
if (_ConnectionString == null || string.IsNullOrEmpty(_ConnectionString.ConnectionString))
{
_ConnectionString = new SqlConnectionStringBuilder(Configurations.SQLSERVER_CONNECTION_STRING);
}
return _ConnectionString;
}
set { _ConnectionString = value; }
}
#endregion
#region ExecuteNonQuery
public int ExecuteNonQuery(string cmdText)
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, CommandType.Text, cmdText);
public int ExecuteNonQuery(string cmdText, CommandType type)
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText);
public int ExecuteNonQuery(string cmdText, CommandType type, params SqlParameter[] cmdParameters)
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText, cmdParameters);
代碼說明:
1. 本類對SqlHelper.cs 進一步封裝。
2. Configurations.SQLSERVER_CONNECTION_STRING 替換成自己的連接配接字元串就行了。
UserInfoAction.cs
public class UserInfoAction : SqlDAL
/// 添加使用者
public void Add(UserInfo user)
StringBuilder sb = new StringBuilder();
sb.Append("UPDATE [UserInfo] SET Password='");
sb.Append(user.Password);
sb.Append("' WHERE UID=");
sb.Append(user.UID);
ExecuteNonQuery(sql);
}
如果我們要加入事務,通常的辦法就是在方法内try、catch然後Commit、Rollback,缺點就不說了,下面我會邊貼代碼邊講解,力圖大家也能掌握這種方法: )
先貼前面兩個被我修改的類
SqlDAL.cs
public abstract class SqlDAL : ContextBoundObject
private SqlTransaction _SqlTrans;
/// 僅支援有事務時操作
public SqlTransaction SqlTrans
if (_SqlTrans == null)
//從上下文中試圖取得事務
object obj = CallContext.GetData(TransactionAop.ContextName);
if (obj != null && obj is SqlTransaction)
_SqlTrans = obj as SqlTransaction;
return _SqlTrans;
set { _SqlTrans = value; }
if (SqlTrans == null)
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, CommandType.Text, cmdText);
else
return SqlHelper.ExecuteNonQuery(SqlTrans, CommandType.Text, cmdText);
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText);
return SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText);
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText, cmdParameters);
return SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText, cmdParameters);
代碼說明:
1. 加了一個屬性(Property)SqlTrans,并且每個ExecuteNonQuery執行前都加了判斷是否以事務方式執行。這樣做是為後面從上下文中取事務做準備。
2. 類繼承了ContextBoundObject,注意,是必須的,MSDN是這樣描述的:定義所有上下文綁定類的基類。
3. TransactionAop将在後面給出。
[Transaction]
[TransactionMethod]
1. 很簡潔、非侵入式、很少改動、非常友善(想要事務就加2個标記,不想要就去掉)。
2. 兩個Attribute後面将給出。
/// <summary>
/// 标注類某方法内所有資料庫操作加入事務控制
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public sealed class TransactionAttribute : ContextAttribute, IContributeObjectSink
/// 标注類某方法内所有資料庫操作加入事務控制,請使用TransactionMethodAttribute同時标注
public TransactionAttribute()
: base("Transaction")
{ }
public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next)
return new TransactionAop(next);
/// 标示方法内所有資料庫操作加入事務控制
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public sealed class TransactionMethodAttribute : Attribute
/// 标示方法内所有資料庫操作加入事務控制
public TransactionMethodAttribute()
1. 在上面兩篇文章中都是把IContextProperty, IContributeObjectSink單獨繼承并實作的,其實我們發現ContextAttribute已經繼承了IContextProperty,所有這裡我僅僅隻需要再繼承一下IContributeObjectSink就行了。關于這兩個接口的說明,上面文章中都有詳細的說明。
2. TransactionAop将在後面給出。
3. 需要注意的是兩個Attribute需要一起用,并且我發現Attribute如果标記在類上他會被顯示的執行個體化,但是放在方法上就不會,打斷點可以跟蹤到這一過程,要不然我也不會費力氣弄兩個來标注了。
TransactionAop.cs
public sealed class TransactionAop : IMessageSink
private IMessageSink nextSink; //儲存下一個接收器
/// 構造函數
/// <param name="next">接收器</param>
public TransactionAop(IMessageSink nextSink)
this.nextSink = nextSink;
/// IMessageSink接口方法,用于異步處理,我們不實作異步處理,是以簡單傳回null,
/// 不管是同步還是異步,這個方法都需要定義
/// <param name="msg"></param>
/// <param name="replySink"></param>
/// <returns></returns>
public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
return null;
/// 下一個接收器
public IMessageSink NextSink
get { return nextSink; }
///
public IMessage SyncProcessMessage(IMessage msg)
IMessage retMsg = null;
IMethodCallMessage call = msg as IMethodCallMessage;
if (call == null || (Attribute.GetCustomAttribute(call.MethodBase, typeof(TransactionMethodAttribute))) == null)
retMsg = nextSink.SyncProcessMessage(msg);
//此處換成自己的資料庫連接配接
using (SqlConnection Connect = new SqlConnection(Configurations.SQLSERVER_CONNECTION_STRING))
Connect.Open();
SqlTransaction SqlTrans = Connect.BeginTransaction();
//講存儲存儲在上下文
CallContext.SetData(TransactionAop.ContextName, SqlTrans);
//傳遞消息給下一個接收器 - > 就是指執行你自己的方法
retMsg = nextSink.SyncProcessMessage(msg);
if (SqlTrans != null)
{
IMethodReturnMessage methodReturn = retMsg as IMethodReturnMessage;
Exception except = methodReturn.Exception;
if (except != null)
{
SqlTrans.Rollback();
//可以做日志及其他處理
}
else
SqlTrans.Commit();
SqlTrans.Dispose();
SqlTrans = null;
}
return retMsg;
/// 用于提取、存儲SqlTransaction
public static string ContextName
get { return "TransactionAop"; }
1. IMessageSink MSDN:定義消息接收器的接口。
2. 主要關注SyncProcessMessage方法内的代碼,在這裡建立事務,并存儲在上下文中間,還記得上面SqlDAL的SqlTrans屬性麼,裡面就是從上下文中取得的。
3. 請注意了,這裡能捕捉到錯誤,但是沒有辦法處理錯誤,是以錯誤會繼續往外抛,但是事務的完整性我們實作了。你可以在Global.asax可以做全局處理,也可以手動的try一下,但是我們不需要管理事務了,僅僅當普通的錯誤來處理了。
結束
大家可以看到,在被标注的方法裡面所有的資料庫操作都會被事務管理起來,也算是了了我心願,貌似我的Attribute做權限又看到了一絲希望了,歡迎大家多提意見:)
補充(2009-1-8)
關于在評論中提到的性能的問題,如果要使用AOP的方式來實作事務肯定比直接try catch 然後Commit 和 Rollback效率要低的,但是很明顯可維護性、使用友善性要高得多的,是以看個人需求了。這裡補充的是關于SqlDAL繼承ContextBoundObject的問題,以下是想到的解決辦法:
1. 最簡單、修改UserInfoAction最少的辦法:把SqlDAL複制一份改下類名,繼承一下ContextBoundObject,然後把繼承類改一下。很不推薦: (
2. 從一開始就不使用繼承方法來通路資料層的方法,而是将SqlDAL改成一個普通類,通過聲明一個SqlDAL方式來通路資料層:
private SqlDAL _sqlDao;
public SqlDAL SqlDao
if (_sqlDao == null)
_sqlDao = new SqlDAL();
_sqlDao.SqlTrans = obj as SqlTransaction;
return _sqlDao;
這樣相對于沒有加事務類僅僅多一個取值過程和判斷過程,效率應該還是比繼承SqlDAL直接繼承ContextBoundObject好很多。
個人感覺還是不是很好,繼續探索,已經想到了減少一個Attribute的辦法了,感謝歡迎大家提建議 :)
本文轉自over140 51CTO部落格,原文連結:http://blog.51cto.com/over140/586466,如需轉載請自行聯系原作者