正文
首先我们来看一段未加事务的代码:
SqlDAL.cs
{
#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);
}
#endregion
代码说明:
1. 本类对SqlHelper.cs 进一步封装。
2. Configurations.SQLSERVER_CONNECTION_STRING 替换成自己的连接字符串就行了。
UserInfoAction.cs
{
/// <summary>
/// 添加用户
/// </summary>
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
{
private SqlTransaction _SqlTrans;
/// <summary>
/// 仅支持有事务时操作
/// </summary>
public SqlTransaction SqlTrans
{
get
{
if (_SqlTrans == null )
{
// 从上下文中试图取得事务
object obj = CallContext.GetData(TransactionAop.ContextName);
if (obj != null && obj is SqlTransaction)
_SqlTrans = obj as SqlTransaction;
}
return _SqlTrans;
}
set { _SqlTrans = value; }
}
#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)
{
if (SqlTrans == null )
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, CommandType.Text, cmdText);
else
return SqlHelper.ExecuteNonQuery(SqlTrans, CommandType.Text, cmdText);
}
public int ExecuteNonQuery( string cmdText, CommandType type)
{
if (SqlTrans == null )
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText);
else
return SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText);
}
public int ExecuteNonQuery( string cmdText, CommandType type, params SqlParameter[] cmdParameters)
{
if (SqlTrans == null )
return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText, cmdParameters);
else
return SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText, cmdParameters);
}
#endregion
}
代码说明:
1. 加了一个属性(Property)SqlTrans,并且每个ExecuteNonQuery执行前都加了判断是否以事务方式执行。这样做是为后面从上下文中取事务做准备。
2. 类继承了ContextBoundObject,注意,是必须的,MSDN是这样描述的:定义所有上下文绑定类的基类。
3. TransactionAop将在后面给出。
UserInfoAction.cs
public class UserInfoAction : SqlDAL
{
[TransactionMethod]
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);
}
}
代码说明:
1. 很简洁、非侵入式、很少改动、非常方便(想要事务就加2个标记,不想要就去掉)。
2. 两个Attribute后面将给出。
/// 标注类某方法内所有数据库操作加入事务控制
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false )]
public sealed class TransactionAttribute : ContextAttribute, IContributeObjectSink
{
/// <summary>
/// 标注类某方法内所有数据库操作加入事务控制,请使用TransactionMethodAttribute同时标注
/// </summary>
public TransactionAttribute()
: base ( " Transaction " )
{ }
public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next)
{
return new TransactionAop(next);
}
}
/// <summary>
/// 标示方法内所有数据库操作加入事务控制
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false )]
public sealed class TransactionMethodAttribute : Attribute
{
/// <summary>
/// 标示方法内所有数据库操作加入事务控制
/// </summary>
public TransactionMethodAttribute()
{
}
}
代码说明:
1. 在上面两篇文章中都是把IContextProperty, IContributeObjectSink单独继承并实现的,其实我们发现ContextAttribute已经继承了IContextProperty,所有这里我仅仅只需要再继承一下IContributeObjectSink就行了。关于这两个接口的说明,上面文章中都有详细的说明。
2. TransactionAop将在后面给出。
3. 需要注意的是两个Attribute需要一起用,并且我发现Attribute如果标记在类上他会被显示的实例化,但是放在方法上就不会,打断点可以跟踪到这一过程,要不然我也不会费力气弄两个来标注了。
TransactionAop.cs
{
private IMessageSink nextSink; // 保存下一个接收器
/// <summary>
/// 构造函数
/// </summary>
/// <param name="next"> 接收器 </param>
public TransactionAop(IMessageSink nextSink)
{
this .nextSink = nextSink;
}
/// <summary>
/// IMessageSink接口方法,用于异步处理,我们不实现异步处理,所以简单返回null,
/// 不管是同步还是异步,这个方法都需要定义
/// </summary>
/// <param name="msg"></param>
/// <param name="replySink"></param>
/// <returns></returns>
public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
{
return null ;
}
/// <summary>
/// 下一个接收器
/// </summary>
public IMessageSink NextSink
{
get { return nextSink; }
}
/// <summary>
///
/// </summary>
/// <param name="msg"></param>
/// <returns></returns>
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);
else
{
// 此处换成自己的数据库连接
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;
}
/// <summary>
/// 用于提取、存储SqlTransaction
/// </summary>
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方式来访问数据层:
public SqlDAL SqlDao
{
get
{
if (_sqlDao == null )
{
_sqlDao = new SqlDAL();
object obj = CallContext.GetData(TransactionAop.ContextName);
if (obj != null && obj is SqlTransaction)
_sqlDao.SqlTrans = obj as SqlTransaction;
}
return _sqlDao;
}
}
这样相对于没有加事务类仅仅多一个取值过程和判断过程,效率应该还是比继承SqlDAL直接继承ContextBoundObject好很多。
个人感觉还是不是很好,继续探索,已经想到了减少一个Attribute的办法了,感谢欢迎大家提建议 :)
本文转自over140 51CTO博客,原文链接:http://blog.51cto.com/over140/586466,如需转载请自行联系原作者