\t\tC# 用Attribute实现AOP事务

阅前注意

1. 整篇文章的核心和突破点在于上下文Context的使用,务必注意CallContext在整个程序中起到的作用

2. 本文中看到的SqlHelper使用的是微软SqlHelper.cs。

3. 本文重点在于如何实现,并且已经 测试通过,只贴关键性代码,所以请认真阅读,部分代码直接拷贝下来运行是会出错的!



正文

首先我们来看一段未加事务的代码:

SqlDAL.cs
  1. public abstract class SqlDAL
  2. {
  3. #region ConnectionString
  4. private SqlConnectionStringBuilder _ConnectionString = null;
  5. ///
  6. /// 字符串连接
  7. ///
  8. public virtual SqlConnectionStringBuilder ConnectionString
  9. {
  10. get
  11. {
  12. if (_ConnectionString == null || string.IsNullOrEmpty(_ConnectionString.ConnectionString))
  13. {
  14. _ConnectionString = new SqlConnectionStringBuilder(Configurations.SQLSERVER_CONNECTION_STRING);
  15. }
  16. return _ConnectionString;
  17. }
  18. set { _ConnectionString = value; }
  19. }
  20. #endregion
  21. #region ExecuteNonQuery
  22. public int ExecuteNonQuery(string cmdText)
  23. {
  24. return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, CommandType.Text, cmdText);
  25. }
  26. public int ExecuteNonQuery(string cmdText, CommandType type)
  27. {
  28. return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText);
  29. }
  30. public int ExecuteNonQuery(string cmdText, CommandType type, params SqlParameter[] cmdParameters)
  31. {
  32. return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText, cmdParameters);
  33. }
  34. #endregion
复制代码
代码说明:

1. 本类对SqlHelper.cs 进一步封装。

2. Configurations.SQLSERVER_CONNECTION_STRING 替换成自己的连接字符串就行了。

UserInfoAction.cs
  1. public class UserInfoAction : SqlDAL
  2. {
  3. ///
  4. /// 添加用户
  5. ///
  6. public void Add(UserInfo user)
  7. {
  8. StringBuilder sb = new StringBuilder();
  9. sb.Append("UPDATE [UserInfo] SET Password='");
  10. sb.Append(user.Password);
  11. sb.Append("' WHERE UID=");
  12. sb.Append(user.UID);
  13. ExecuteNonQuery(sql);
  14. }
  15. }
复制代码
如果我们要加入事务,通常的办法就是在方法内try、catch然后Commit、Rollback,缺点就不说了,下面我会边贴代码边讲解,力图大家也能掌握这种方法: )

先贴前面两个被我修改的类

SqlDAL.cs
  1. public abstract class SqlDAL : ContextBoundObject
  2. {
  3. private SqlTransaction _SqlTrans;
  4. ///
  5. /// 仅支持有事务时操作
  6. ///
  7. public SqlTransaction SqlTrans
  8. {
  9. get
  10. {
  11. if (_SqlTrans == null)
  12. {
  13. //从上下文中试图取得事务
  14. object obj = CallContext.GetData(TransactionAop.ContextName);
  15. if (obj != null && obj is SqlTransaction)
  16. _SqlTrans = obj as SqlTransaction;
  17. }
  18. return _SqlTrans;
  19. }
  20. set { _SqlTrans = value; }
  21. }
  22. #region ConnectionString
  23. private SqlConnectionStringBuilder _ConnectionString = null;
  24. ///
  25. /// 字符串连接
  26. ///
  27. public virtual SqlConnectionStringBuilder ConnectionString
  28. {
  29. get
  30. {
  31. if (_ConnectionString == null || string.IsNullOrEmpty(_ConnectionString.ConnectionString))
  32. {
  33. _ConnectionString = new SqlConnectionStringBuilder(Configurations.SQLSERVER_CONNECTION_STRING);
  34. }
  35. return _ConnectionString;
  36. }
  37. set { _ConnectionString = value; }
  38. }
  39. #endregion
  40. #region ExecuteNonQuery
  41. public int ExecuteNonQuery(string cmdText)
  42. {
  43. if (SqlTrans == null)
  44. return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, CommandType.Text, cmdText);
  45. else
  46. return SqlHelper.ExecuteNonQuery(SqlTrans, CommandType.Text, cmdText);
  47. }
  48. public int ExecuteNonQuery(string cmdText, CommandType type)
  49. {
  50. if (SqlTrans == null)
  51. return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText);
  52. else
  53. return SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText);
  54. }
  55. public int ExecuteNonQuery(string cmdText, CommandType type, params SqlParameter[] cmdParameters)
  56. {
  57. if (SqlTrans == null)
  58. return SqlHelper.ExecuteNonQuery(ConnectionString.ConnectionString, type, cmdText, cmdParameters);
  59. else
  60. return SqlHelper.ExecuteNonQuery(SqlTrans, type, cmdText, cmdParameters);
  61. }
  62. #endregion
  63. }
复制代码
代码说明:

1. 加了一个属性(Property)SqlTrans,并且每个ExecuteNonQuery执行前都加了判断是否以事务方式执行。这样做是为后面从上下文中取事务做准备。

2. 类继承了ContextBoundObject,注意,是必须的,MSDN是这样描述的:定义所有上下文绑定类的基类。

3. TransactionAop将在后面给出。

UserInfoAction.cs
  1. [Transaction]
  2. public class UserInfoAction : SqlDAL
  3. {
  4. [TransactionMethod]
  5. public void Add(UserInfo user)
  6. {
  7. StringBuilder sb = new StringBuilder();
  8. sb.Append("UPDATE [UserInfo] SET Password='");
  9. sb.Append(user.Password);
  10. sb.Append("' WHERE UID=");
  11. sb.Append(user.UID);
  12. ExecuteNonQuery(sql);
  13. }
  14. }
复制代码
代码说明:

1. 很简洁、非侵入式、很少改动、非常方便(想要事务就加2个标记,不想要就去掉)。

2. 两个Attribute后面将给出。
  1. ///
  2. /// 标注类某方法内所有数据库操作加入事务控制
  3. ///
  4. [AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
  5. public sealed class TransactionAttribute : ContextAttribute, IContributeObjectSink
  6. {
  7. ///
  8. /// 标注类某方法内所有数据库操作加入事务控制,请使用TransactionMethodAttribute同时标注
  9. ///
  10. public TransactionAttribute()
  11. : base("Transaction")
  12. { }
  13. public IMessageSink GetObjectSink(MarshalByRefObject obj, IMessageSink next)
  14. {
  15. return new TransactionAop(next);
  16. }
  17. }
  18. ///
  19. /// 标示方法内所有数据库操作加入事务控制
  20. ///
  21. [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
  22. public sealed class TransactionMethodAttribute : Attribute
  23. {
  24. ///
  25. /// 标示方法内所有数据库操作加入事务控制
  26. ///
  27. public TransactionMethodAttribute()
  28. {
  29. }
  30. }
复制代码
代码说明:

1. 在上面两篇文章中都是把IContextProperty, IContributeObjectSink单独继承并实现的,其实我们发现ContextAttribute已经继承了IContextProperty,所有这里我仅仅只需要再继承一下IContributeObjectSink就行了。关于这两个 接口的说明,上面文章中都有详细的说明。

2. TransactionAop将在后面给出。

3. 需要注意的是两个Attribute需要一起用,并且我发现Attribute如果标记在类上他会被显示的实例化,但是放在方法上就不会,打断点可以跟踪到这一过程,要不然我也不会费力气弄两个来标注了。

TransactionAop.cs
  1. public sealed class TransactionAop : IMessageSink
  2. {
  3. private IMessageSink nextSink; //保存下一个接收器
  4. ///
  5. /// 构造函数
  6. ///
  7. /// 接收器
  8. public TransactionAop(IMessageSink nextSink)
  9. {
  10. this.nextSink = nextSink;
  11. }
  12. ///
  13. /// IMessageSink接口方法,用于异步处理,我们不实现异步处理,所以简单返回null,
  14. /// 不管是同步还是异步,这个方法都需要定义
  15. ///
  16. ///
  17. ///
  18. ///
  19. public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)
  20. {
  21. return null;
  22. }
  23. ///
  24. /// 下一个接收器
  25. ///
  26. public IMessageSink NextSink
  27. {
  28. get { return nextSink; }
  29. }
  30. ///
  31. ///
  32. ///
  33. ///
  34. ///
  35. public IMessage SyncProcessMessage(IMessage msg)
  36. {
  37. IMessage retMsg = null;
  38. IMethodCallMessage call = msg as IMethodCallMessage;
  39. if (call == null || (Attribute.GetCustomAttribute(call.MethodBase, typeof(TransactionMethodAttribute))) == null)
  40. retMsg = nextSink.SyncProcessMessage(msg);
  41. else
  42. {
  43. //此处换成自己的数据库连接
  44. using (SqlConnection Connect = new SqlConnection(Configurations.SQLSERVER_CONNECTION_STRING))
  45. {
  46. Connect.Open();
  47. SqlTransaction SqlTrans = Connect.BeginTransaction();
  48. //讲存储存储在上下文
  49. CallContext.SetData(TransactionAop.ContextName, SqlTrans);
  50. //传递消息给下一个接收器 - > 就是指执行你自己的方法
  51. retMsg = nextSink.SyncProcessMessage(msg);
  52. if (SqlTrans != null)
  53. {
  54. IMethodReturnMessage methodReturn = retMsg as IMethodReturnMessage;
  55. Exception except = methodReturn.Exception;
  56. if (except != null)
  57. {
  58. SqlTrans.Rollback();
  59. //可以做日志及其他处理
  60. }
  61. else
  62. {
  63. SqlTrans.Commit();
  64. }
  65. SqlTrans.Dispose();
  66. SqlTrans = null;
  67. }
  68. }
  69. }
  70. return retMsg;
  71. }
  72. ///
  73. /// 用于提取、存储SqlTransaction
  74. ///
  75. public static string ContextName
  76. {
  77. get { return "TransactionAop"; }
  78. }
  79. }
复制代码
代码说明:

1. IMessageSink MSDN:定义消息接收器的接口。

2. 主要关注SyncProcessMessage方法内的代码,在这里创建事务,并存储在上下文中间,还记得上面SqlDAL的SqlTrans属性么,里面就是从上下文中取得的。

3. 请注意了,这里能捕捉到错误,但是没有办法处理错误,所以错误会继续往外抛,但是事务的完整性我们实现了。你可以在Global.asax可以做全局处理,也可以手动的try一下,但是我们不需要管理事务了,仅仅当普通的错误来处理了。


结束

大家可以看到,在被标注的方法里面所有的数据库操作都会被事务管理起来

你可能感兴趣的:(c#,研究)