C# 用Attribute实现AOP事务

本文涉及的产品
云数据库 RDS SQL Server,基础系列 2核4GB
RDS SQL Server Serverless,2-4RCU 50GB 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的办法了,感谢欢迎大家提建议 :)



本文转自over140 51CTO博客,原文链接:http://blog.51cto.com/over140/586466,如需转载请自行联系原作者

相关实践学习
使用SQL语句管理索引
本次实验主要介绍如何在RDS-SQLServer数据库中,使用SQL语句管理索引。
SQL Server on Linux入门教程
SQL Server数据库一直只提供Windows下的版本。2016年微软宣布推出可运行在Linux系统下的SQL Server数据库,该版本目前还是早期预览版本。本课程主要介绍SQLServer On Linux的基本知识。 相关的阿里云产品:云数据库RDS&nbsp;SQL Server版 RDS SQL Server不仅拥有高可用架构和任意时间点的数据恢复功能,强力支撑各种企业应用,同时也包含了微软的License费用,减少额外支出。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/sqlserver
相关文章
|
XML C# 数据格式
C#报错 The ‘xmins‘ attribute is not supported in this context
System.Xml.Schema.XmlSchemaException:“The ‘xmins’ attribute is not supported in this context.”异常的解决百度翻译:System.Xml.Schema.XmlSchemaException:“此上下文中不支持”xmins“属性。”这是在使用System.Data.DataSet的ReadXml读取x...
79 0
C#报错 The ‘xmins‘ attribute is not supported in this context
|
XML Java 数据格式
13Spring - Spring转账Demo(了解事务及AOP)
13Spring - Spring转账Demo(了解事务及AOP)
42 0
|
4月前
|
开发框架 .NET 编译器
总结一下 C# 如何自定义特性 Attribute 并进行应用
总结一下 C# 如何自定义特性 Attribute 并进行应用
113 1
|
7月前
|
Java 数据库连接 数据库
AOP&事务
AOP&事务
51 0
|
7月前
|
XML Java 数据库连接
【Spring】JDBC、AOP、事务
【Spring】JDBC、AOP、事务
|
7月前
|
Java Spring
使用spring的aop实现全局的事务控制
使用spring的aop实现全局的事务控制
147 0
|
存储 SpringCloudAlibaba Java
Spring基于AOP事务控制实现原理
对于一个系统应用而言,使用数据库进行数据存储是必然的,意味着开发过程中事务的使用及控制也是必不可少的,当然事务是数据库层面的知识点并不是`Spring`框架所提出的。使用JDBC开发时,我们使用`connnection`对事务进行控制,使用`MyBatis`时,我们使用`SqlSession`对事务进行控制,缺点显而易见,当我们切换数据库访问技术时,事务控制的方式总会变化,所以`Spring` 就在这些技术基础上,提供了统一的控制事务的接口。Spring的事务分为:编程式事务控制和声明式事务控制
31351 10
Spring基于AOP事务控制实现原理
|
XML Java 编译器
Spring的AOP和事务
Spring的AOP和事务
46 0
c#之Attribute特性的原理
c#之Attribute特性的原理
69 0
|
SQL 存储 数据库
C#三十三 事务
C#三十三 事务
54 0