我前两天看见同事用写了用AOP技术实现缓存的方案,于是好奇看了一下这是怎么实现的。原来是用了.NET中的一个类ContextBoundObject和Attribute相关技术。其实个类在.NET Framework很早就有,至今才认识它,是有点相见恨晚的感觉。网上一搜,已经有了很多使用ContextBoundObject类实现AOP的例子,其中我就看到一篇利用ContextBoundObject和Attribute实现AOP事务实现例子,我想应该和实现AOP缓存是一个道理。下面我就把这篇文章分享出来:

前言
     使用Attribute来实现方法级别事务一直是我的梦想,浅谈Attribute [C# | Attribute | DefaultValueAttribute]有体现我的无奈,Attribute确实是真真切切的非侵入式的东西(其实我是想侵入的: ) ),前有DUDU的Attribute在.net编程中的应用系列文章,但是总是离想象和需求有那么点出入,通过三天的努力,Google的陪伴,下面和大家一起分享我这三天的成果 用Attribute实现AOP事务 吧!

致谢文章
     1.     Aspect-Oriented Programming Enables Better Code Encapsulation and Reuse 关键性的CallContext是在这里发现的。

2.     C# Attribute在.net编程中的应用 (转) 这篇文章原文地址找不到了,DUDU的Attribute在.net编程中的应用系列文章就是这篇文章的分解,他写到了五,后面的大家可以从这篇文章里面提前看到了。

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

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

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

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

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);
}

#endregion
}
代码说明:

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

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

UserInfoAction.cs

public class UserInfoAction : SqlDAL
{
/// <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

public abstract class SqlDAL : ContextBoundObject
{

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

[Transaction]
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>
/// 标注类某方法内所有数据库操作加入事务控制
/// </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

public sealed class TransactionAop : IMessageSink
{
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方式来访问数据层:

private SqlDAL _sqlDao;
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的办法了,感谢欢迎大家提建议 :)

原文:http://www.cnblogs.com/over140/archive/2009/01/07/1371307.html

使用ContextBoundObject实现AOP总结:
1、要定义两个特性,一个是标记是用来标记要AOP的类(TransactionAttribute ),一个特性是用来标记要AOP的方法(TransactionMethodAttribute )。定义标记要AOP类要继承接口:ContextAttribute, IContributeObjectSink,定义用来标记要AOP的方法要继承接口Attribute,这里只是一个标示作用。在定义标记要AOP类(TransactionAttribute )要定义一个方法GetObjectSink,返回一个继承接口IMessageSink类(TransactionAop)的实例。

2、要定义一个继承接口IMessageSink类(TransactionAop),这个类就是要实现AOP是核心代码,你可以在执行真正代码之前或之后加入一些自己的代码,比如日志记录,缓存,事务等等。

3、在要使用的类必须满足以下条件:

1、继承类ContextBoundObject

2、类和方法都要分别加上我们之前定义的相应的特性(TransactionAttribute ,TransactionMethodAttribute)。

.net网站&系统开发技术学习交流群:533829726
本站文章除注明转载外,均为本站原创或翻译,欢迎任何形式的转载,但请务必注明出处,尊重他人劳动,共创和谐网络环境。
转载请注明:文章转载自:蓝狐软件工作室 » C#.NET利用ContextBoundObject和Attribute实现AOP技术--AOP事务实现例子
本文标题:C#.NET利用ContextBoundObject和Attribute实现AOP技术--AOP事务实现例子
本文地址:http://www.lanhusoft.com/Article/240.html

C#.NET利用ContextBoundObject和Attribute实现AOP技术--AOP事务实现例子的更多相关文章

  1. AOP技术应用于安全防御与漏洞修复

    本文用到的技术 AOP ESAPI 关于AOP技术 AOP(Aspect-Oriented Programming)面向切面编程.切面是什么?切面表示从业务逻辑分离出来的横切逻辑,比如性能监控.日志记 ...

  2. Spring框架的核心功能之AOP技术

     技术分析之Spring框架的核心功能之AOP技术 AOP的概述        1. 什么是AOP的技术?        * 在软件业,AOP为Aspect Oriented Programming的 ...

  3. 利用反射手写代码实现spring AOP

    前言:上一篇博客自己动手编写spring IOC源码受到了大家的热情关注,在这里博客十分感谢.特别是给博主留言建议的@玛丽的竹子等等.本篇博客我们继续,还是在原有的基础上进行改造.下面请先欣赏一下博主 ...

  4. 【Java疑难杂症】利用Java核心库实现简单的AOP

    Spring是一个十分火热开源框架,而AOP(面向切面编程)则是Spring最重要的概念之一,为了更好的理解和学习AOP的思想,使用核心库来实现一次不失为一个好方法. 首先介绍一下AOP的概念,AOP ...

  5. 利用cglib包实现Spring中aop的<aop:advisor>功能

    一:前言 还有<aop:before>/<aop:after>/<aop:around>的没有实现,不过根<aop:advisor>是差不多的,就是要额 ...

  6. 面向侧面的程序设计AOP-------《三》.Net平台AOP技术概览

    本文转载自张逸:晴窗笔记 .Net平台与Java平台相比,由于它至今在服务端仍不具备与unix系统的兼容性,也不具备类似于Java平台下J2EE这样的企业级容器,使得.Net平台在大型的企业级应用上, ...

  7. AOP技术介绍--(.Net平台AOP技术研究)

    4.1.Net平台AOP技术概览 .Net平台与Java平台相比,由于它至今在服务端仍不具备与unix系统的兼容性,也不具备类似于Java平台下J2EE这样的企业级容器,使得.Net平台在大型的企业级 ...

  8. JAVA平台AOP技术研究

    3.1 Java平台AOP技术概览 3.1.1 AOP技术在Java平台中的应用 AOP在实验室应用和商业应用上,Java平台始终走在前面.从最初也是目前最成熟的AOP工具--AspectJ,到目前已 ...

  9. spring.net tx:advice 和 aop:config 配置事务 匹配名字的方法管理事务

    在网上找到的都是java里的配置方式,后来认真读了下spring.net的帮助文档,解决了这个问题:现在把我的server层的配置文件copy出来: <?xml version="1. ...

随机推荐

  1. /sbin/ifup: configuration for eth0 not found解决

    /sbin/ifup: configuration for eth0 not found. Usage: ifup <device name> 那么应该是在/etc/sysconfig/n ...

  2. SIP学习之网络链接

    风清扬的CSDN博客  文章分类SIP http://blog.csdn.net/ppy521/article/category/1227390

  3. c语言typedef之数组运用

    #include <stdio.h> #include <stdlib.h> typedef ];//int arr[4]取一个别名 arr b c就是int b[4] int ...

  4. jQuery获取select option

    jQuery的一些方法理出一些常用的方法: //获取第一个option的值 $('#test option:first').val(); //最后一个option的值 $('#test option: ...

  5. oracle 同样数据删除(仅仅留一条)

    DELETE FROM reg_user t1 WHERE user_name='9527008' and rowid > ( SELECT min(rowid) FROM location t ...

  6. Android Fragment详解(六):Fragement示例

    把条目添加到动作栏 你的fragment们可以向activity的菜单(按Manu键时出现的东西)添加项,同时也可向动作栏(界面中顶部的那个区域)添加条目,这都需通过实现方法onCreateOptio ...

  7. Leetcode_num3_Same Tree

    题目: Given two binary trees, write a function to check if they are equal or not. Two binary trees are ...

  8. Microsoft Dynamics CRM 2016 增强版的解决方案(CRM新特性,开发者的福利)

    CRM在以前的版本中,如果你改变了一个字段(组织A)然后打算导入到其他组织(组织B),你必须创建一个解决方案,包括完整的实体,并导出导入.然而,如果其他团队成员正在相同的实体做了自定义但不想让这些变化 ...

  9. oracle数据库连接

    ///宁采花 8:37:39 /// <summary> /// 获取数据链接 /// </summary> /// <returns></returns&g ...

  10. Ubuntu 12.04.5 LTS 上安装hadoop 2.6.0后运行自带的例程wordcount

    注:我所有的操作均通过Xshell 5远程连接Ubuntu进行实施 第一步:启动hadoop,利用jps查看hadoop是否已经启动,如果没有启动用start-dfs.sh脚本启动(hadoop2.X ...