1、C#用Attribute实现AOP事务刖言使用 Attribute 来实现方法级别事务一直是我的梦想, 浅谈 Attribute C# | Attribute | DefaultValueAttribute 有体现我的无奈,用Attribute 实现AOP事务 吧!关键性的CallContext 是在这里发现的象和需求有那么点出入,通过三天的努力, Google的陪伴,下面和大家一起分享我这三天的成果致谢文章1.Aspect-Oriented Programming Enables Better Code Encapsulation and Reuse2.C# Attribute 在.net编
2、程中的应用(转)这篇文章原文地址找不到了, DUDU的Attribute 在.net编程中的应用系列文章就是这 篇文章的分解,他写到了五,后面的大家可以从这篇文章里面提前看到了阅刖注意1.整篇文章的核心和突破点在于上下文 Context的使用,务必注意 CallContext 在整个程序中起到的作用2.本文中看到的SqlHelper 使用的是微软SqlHelper .cs。3.本文重点在于如何实现,并且已经测试通过,只贴关键性代码,所以请认真阅读,部分代码直接拷贝下来运行是会岀错的正文首先我们来看一段未加事务的代码:SqlDAL.cspublic abstract class SqlDAL#r
3、egion ConnectionStringprivate SqlConnectionStringBuilder _ConnectionString = null ;/ III 字符串连接/ public virtual SqlConnectionStringBuilder ConnectionStringgetif (_ConnectionString = null | string .lsNullOrEmpty(_ConnectionString.ConnectionString)_ConnectionString = new SqlConnectionStringBuilder(Conf
4、igurations.SQLSERVER_CONNECTION_STRING);return _ConnectionString;set _ConnectionString = value; #endregion#region ExecuteNonQuerypublic int ExecuteNonQuery( string cmdText)return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, CommandType.Text, cmdText);public int ExecuteNonQuery( strin
5、g 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, cmdParamet
6、ers);#endregion代码说明:1.本类对SqlHelper .cs进一步封装。2.Configurations.SQLSERVER_CONNECTION_STRING 替换成自己的连接字符串就行了。UserlnfoAction.cspublic class UserInfoAction : SqlDAL/ / 添加用户/ public void Add(UserInfo user)StringBuilder sb = new StringBuilder();sb.Append( UPDATE Userinfo SET Password );sb.Append(user.Passwor
7、d);sb.Append( WHERE UID=);sb.Append(user.UID);ExecuteNonQuery(sql);如果我们要加入事务,通常的办法就是在方法内 try、catch然后Commit、Rollback,缺点就不说了,下面我会边贴代码边讲解,力图大家也能掌握这种方法:)先贴前面两个被我修改的类SqlDAL.cspublic abstract class SqlDAL : ContextBoundObjectprivate SqlTransaction _SqlTrans;/ / 仅支持有事务时操作/ public SqlTransaction SqlTransget
8、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 ConnectionStringIll / 字符串连接Ill public virtual SqlConnectionStringBuilder Connection
9、stringgetif (_ConnectionString = null | string .lsNullOrEmpty(_ConnectionString.ConnectionString)_ConnectionString = new SqlConnectionStringBuilder(Configurations.SQLSERVER_CONNECTION_STRING);return _ConnectionString;set _ConnectionString = value; #endregion#region ExecuteNonQuerypublic int ExecuteN
10、onQuery( string cmdText)if (SqlTrans = null )return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, CommandType.Text, cmdText);elsereturn SqlHelper.ExecuteNonQuery(SqlTrans, CommandType.Text, cmdText);public int ExecuteNonQuery( string cmdText, CommandType type)if (SqlTrans = null )retu
11、rn SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText);elsereturn SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText);public int ExecuteNonQuery( string cmdText, CommandType type, params SqlParameter cmdParameters)if (SqlTrans = null )return SqlHelper.ExecuteNonQuery(Connectio
12、nString.ConnectionString, type, cmdText, cmdParameters);elsereturn SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText, cmdParameters);这样做是为后面#endregion代码说明:1.加了一个属性(Property)SqlTrans ,并且每个ExecuteNonQuery 执行前都加了判断是否以事务方式执行。从上下文中取事务做准备。2.类继承了 ContextBoundObject ,注意,是必须的, MSDN是这样描述的:定义所有上下文绑定类的基类。3.Tran
13、sactionAop 将在后面给出。UserlnfoAction.csTransactionpublic class UserInfoAction : SqlDALTransactionMethodpublic 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);代
14、码说明:1.很简洁、非侵入式、很少改动、非常方便 (想要事务就加2个标记,不想要就去掉)。2.两个Attribute 后面将给出。/ / 标注类某方法内所有数据库操作加入事务控制/ AttributeUsage(AttributeTargets.Class, AllowMultiple = false )public sealed class TransactionAttribute : ContextAttribute, IContributeObjectSink/ / 标注类某方法内所有数据库操作加入事务控制,请使用 TransactionMethodAttribute 同时标注/ pub
15、lic TransactionAttribute()base (Transaction )public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next)return new TransactionAop(next);/ / 标示方法内所有数据库操作加入事务控制/ AttributeUsage(AttributeTargets.Method, AllowMultiple = false )public sealed class TransactionMethodAttribute : Attribute/
16、/ 标示方法内所有数据库操作加入事务控制/ public TransactionMethodAttribute。代码说明:1.在上面两篇文章中都是把 IContextProperty, IContributeObjectSink 单独继承并实现的,其实我们发现 ContextAttribute已经继承了 IContextProperty ,所有这里我仅仅只需要再继承一下 IContributeObjectSink 就行了。关于这两个接口的说明, 上面文章中都有详细的说明。2.TransactionAop 将在后面给出。3.需要注意的是两个Attribute 需要一起用,并且我发现Attribu
17、te 如果标记在类上他会被显示的实例化, 但是放在方法上就不会,打断点可以跟踪到这一过程,要不然我也不会费力气弄两个来标注了。TransactionAop.cspublic sealed class TransactionAop : IMessageSinkprivate IMessageSink nextSink; / 保存下一个接收器/ / 构造函数/ / 接收器 public TransactionAop(IMessageSink nextSink)this .nextSink = nextSink;/ / IMessageSink 接口方法,用于异步处理,我们不实现异步处理,所以简单返
18、回 null,/ 不管是同步还是异步,这个方法都需要定义/ / / / public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)return null ;/ / 下一个接收器/ public IMessageSink NextSinkget return nextSink; / / / / public IMessage SyncProcessMessage(IMessage msg)IMessage retMsg = null ;IMethodCallMessage call = msg as
19、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();SqlTra
20、nsaction SqlTrans = Connect.BeginTransaction();/讲存储存储在上下文CallContext.SetData(TransactionAop.ContextName, SqlTrans);/传递消息给下一个接收器- 就是指执行你自己的方法retMsg = nextSink.SyncProcessMessage(msg);if (SqlTrans != null )IMethodReturnMessage methodReturn = retMsg as IMethodReturnMessage;Exception except = methodRetu
21、rn.Exception;if (except != null )SqlTrans.Rollback();/可以做日志及其他处理elseSqlTrans.Commit();SqlTrans.Dispose();SqlTrans = null ;return retMsg;/ / 用于提取、存储SqlTransaction/ public static string ContextNameget return TransactionAop ; 代码说明:2.主要关注SyncProcessMessage 方法内的代码,在这里创建事务,并存储在上下文中间,还记得上面SqlDAL 的 SqlTrans
22、属性么,里面就是从上下文中取得的3. 请注意了,这里能捕捉到错误,但是没有办法处理错误,所以错误会继续往外抛,但是事务的完整性我们实现了。你可以在Global.asax 可以做全局处理,也可以手动的 try 一下,但是我们不需要管理事务了,仅仅当普通的错误来处理了。结束大家可以看到,在被标注的方法里面所有的数据库操作都会被事务管理起来, 也算是了了我心愿,貌似我的Attribute 做权限又看到了一丝希望了,欢迎大家多提意见:)补充(2009-1-8)关于在评论中提到的性能的问题,如果要使用 AOP的方式来实现事务肯定比直接 try catch 然后Commit 和Rollback效率要低的,
23、但是很明显可维护性、使用方便性要高得多的,所以看个人需求了。这里补充的是关于 SqlDAL继承ContextBoundObject 的问题,以下是想到的解决办法:1.最简单、修改 UserInfoAction 最少的办法:把 SqlDAL复制一份改下类名,继承一下 ContextBoundObject ,然后把继承类改一下。很不推荐:(2.从一开始就不使用继承方法来访问数据层的方法,而是将 SqlDAL改成一个普通类,通过声明一个 SqlDAL方式来访问数据层:private SqlDAL _sqlDao;public SqlDAL SqlDaogetif (_sqlDao = null )_
24、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 的办法了,感谢欢迎大家提建议 :)
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1