• [转]利用EnteLib Unity Interception Extension和PIAB实现Trans


    转【http://www.sunnybtoc.com/page/M0/S230/230473.html

    一.写作前提

    之前在苏州的一家知名软件企业工作时,使用了他们提供的框架和类库,切实的感受到它 们所带来的便利,它不仅提高了软件的开发速度,减少了代码的冗余,更重要的是提高了企 业产品的开发效率及质量。而今换了工作环境(一家国外小软件公司),在缺少了这些有利 的工具之后,发现公司之前的几乎所有项目都在重复的Copy代码,这不仅仅是延长项目的开 发周期,最麻烦的莫过于对项目的管理借来及大的困难,看了让我心里有些不是滋味。之后 ,我就开始尝试着写些高效、集成的代码(已经写了一部分了),我希望能够和大家分享和 交流,也希望得到一些指正和建议。

    本篇我们所要讨论的问题就是如何使用Enterprise Library的Unity Interception Extension  和Policy Injection Application Block(PIAB)实现以Attribute形式注入的 Transaction Call Handler。关于什么是事务,这里就不在多加叙述,我们把更多的篇幅用 来本文的主题进行讲解。

    二.将Transaction与业务逻辑分离

    Enterprise Library 是微软推出的一个Open Source框架,或者说是一个可扩展的类库, 最新Release版本是4.1,目录已经出了5.0人的Bate版本。Enterprise Library是由多个 Application Block组成的,例如:

    Caching Application Block Data Access Application Block Exception Handling Application Block Logging Application Block Unity Application Block Validation Application Block Policy Injection Application Block

    Policy Injection Application Block使得开发人员可以使用拦截策略执行一些比较 Common及非业务逻辑的特性,例如:Logging、Caching、Exception、Validation以及其他的 控制与操作,在PIAB中,它们以注入的形式提供给开发者,为用户的开发带来了及大的方便 ,所以我考虑以同样的形式来写了一个injection的Transaction Call Handler,它用来在对 实际方法调用之前进行拦截,然后开启Transaction,调用实际目标方法,最好实现提交 Transaction,并在Transaction中进行相应的扩展操作。

    OK,首先我们来看看传统的事务代码:

    图1:  Traditional Transaction Code

     

    public void AddUser(DataSetUser ds)
    {
         SqlConnection connection = new SqlConnection();
         connection.ConnectionString =  ConfigurationManager.ConnectionStrings ["TransactionDemoConnectionString"].ConnectionString;
         connection.Open();
         SqlTransaction transaction = connection.BeginTransaction();
         try
         {
             SqlCommand command = connection.CreateCommand();
             command.Transaction = transaction;
             command.CommandType = CommandType.Text;
             int i = 0;
             foreach (DataSetUser.T_USERRow row in ds.T_USER.Rows)
             {
                 //if (i == 1)
                 //    throw new Exception("Test");
                 command.CommandText = "insert into T_USER([Name], [Password]) values(@p_username,@p_password)";
                 command.Parameters.Clear();
                 command.Parameters.Add(new SqlParameter (@"@p_username", row.Name));
                 command.Parameters.Add(new SqlParameter (@"@p_password", row.Password));
                 command.ExecuteNonQuery();
                 i++;
             }
             transaction.Commit();
         }
         catch (Exception ex)
         {
             transaction.Rollback();
         }
         finally
         {
             if (connection != null)
                 connection.Close();
             connection.Dispose();
         }
    }

    读过上面的代码,其实我们要做的是对DB的某些数据做增加、更新或删除操作,我们可以 把这些操作称为对数据库数据操作的业务逻辑,而Transaction和对Database连接以及其它的 相关操作(对DB的操作以后有空会写一个,在这里的代码我们作为示例,直接写在代码里) ,在这里它们是与对这些data操作无关的非业务逻辑,因为他不涉及到对具体data的操作; 另外,往往在一个项目中对Transaction要求的代码是非常之多的,我们总不能把相同的代码 在不同的method中Copy来Copy去,因为这样会存在大量的代码冗余,并且不能保证代码的正 确性,增加代理的管理难度和可读性。所以我考虑如下:

    1.能否简化Transaction的代码,方便的操作、减少冗余;

    2.能否将Transaction从这些对数据操作中分离出来;

    3.能否对Transaction进行扩展,比如说通过Transaction在开启、执行、提交或回滚的 过程中记录所进行的操作或一些异常等。

    下面就来看看我提出的一些解决方案。

    三.创建Transaction Call Handler

    下面我们就来具体的创建这样的Call Handler以及实现对其操作。

    1)创建Solution

    先创建一个Solution文件,然后在这个项目文件中包含两个Project,一个是Transaction Call Handler Attribute实现的类库,我们这里叫CWS.Framework.TransactionCallHandler ,它实现了把Transaction分离出来后所以做的所有工作,包括对其进行的一个日志操作;另 外一个Project文件是用来测试的Web Application,我们这里叫WebTest,他实现了利用 Policy Injection Application Block下的PolicyInjection实现对Transaction的注入。

    CWS.Framework.TransactionCallHandler Project需要引用如下dll:

    图2:  Call Handler Project需要引用的dll

    Microsoft.Practices.EnterpriseLibrary.Common

    Microsoft.Practices.EnterpriseLibrary.Logging

    Microsoft.Practices.ObjectBuilder2

    Microsoft.Practices.Unity

    Microsoft.Practices.Unity.Interception

    WebTest Project需要引用如下dll:

    图3:  WebTest Project需要引用的dll

    CWS.Framework.TransactionCallHandler

    Microsoft.Practices.EnterpriseLibrary.PolicyInjection

    Microsoft.Practices.Unity.Interception

     

    2)CWS.Framework.TransactionCallHandler的实现

    我们要实现Transaction的Call Handler需要用到 Microsoft.Practices.Unity.InterceptionExtension. HandlerAttribute抽象类,它继承于 System.Attribute,是自定义HandlerAttribute的基类;以及 Microsoft.Practices.Unity.InterceptionExtension. ICallHandler 接口。每一个Call Handler的实现都需要继承所需要应用的对象上,并且实现ICallHandler 接口。下面提供了 HandlerAttribute及ICallHandler的原型。

    图4: 

    Microsoft.Practices.Unity.InterceptionExtension. HandlerAttribute abstract class

    using Microsoft.Practices.Unity;
    using System;
    
    namespace Microsoft.Practices.Unity.InterceptionExtension
    {
         public abstract class HandlerAttribute : Attribute
         {
             protected HandlerAttribute();
    
             public int Order { get; set; }
    
             public abstract ICallHandler CreateHandler(IUnityContainer  container);
         }
    }
    using System;
    
    namespace Microsoft.Practices.Unity.InterceptionExtension
    {
         public interface ICallHandler
         {
             int Order { get; set; }
             IMethodReturn Invoke(IMethodInvocation input,  GetNextHandlerDelegate getNext);
         }
    }

    首先需要对ICallHandler接口中的所有成员Method及成员Property进行实现,另外还需要 定义一些Method及Property来实现从原先Code中分离出来的Transaction功能,以及对分离出 来的Transaction进行扩展的一些功能(如Log记录,Exception捕获等),具体实现如图6所示 。

    图6:  自定义的Transaction处理类

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Transactions;
    using Microsoft.Practices.EnterpriseLibrary.Logging;
    using Microsoft.Practices.Unity.InterceptionExtension;
    
    namespace CWS.Framework.CallHandler.Transaction
    {
         public class TransactionCallHandler : ICallHandler
         {
             private TransactionScopeOption _scopeOption =  TransactionScopeOption.Required;
             private TransactionOptions _transactionOptions;
             private EnterpriseServicesInteropOption _interopOption =  EnterpriseServicesInteropOption.None;
    
             private string _functionName;
             private string _actionDescription;
    
             public TransactionCallHandler(string functionName, string  actionDescription, TransactionOptions transactionOptions)
             {
                 this._transactionOptions = transactionOptions;
                 this._functionName = functionName;
                 this._actionDescription = actionDescription;
             }
    
             public TransactionCallHandler(TransactionScopeOption  scopeOption, TransactionOptions transactionOptions
                 , EnterpriseServicesInteropOption interopOption)
             {
                 this._scopeOption = scopeOption;
                 this._transactionOptions = transactionOptions;
                 this._interopOption = interopOption;
             }
    
             public int Order { get; set; } //用来控制执行顺序 
    
             public IMethodReturn Invoke(IMethodInvocation input,  GetNextHandlerDelegate getNext)
             {
                 IMethodReturn result;
                 using (TransactionScope scope =  CreateTransactionScope())
                 {
                     Logger.Write(string.Format("Function Name{0},  Action Description:{1}.", _functionName, _actionDescription));
                     result = getNext()(input, getNext);
                     if (result.Exception == null)
                     {
                         Logger.Write("Action Done.");
                         scope.Complete();
                     }
                     else
                         Logger.Write(result.Exception);
                 }
                 return result;
             }
    
             protected virtual TransactionScope CreateTransactionScope ()
             {
                 if (_interopOption ==  EnterpriseServicesInteropOption.None)
                 {
                     return new TransactionScope(_scopeOption,  _transactionOptions);
    
                 }
                 else
                 {
                     return new TransactionScope(_scopeOption,  _transactionOptions, _interopOption);
                 }
             }
         }
    }


     

    我自定义了如下属性:

    1.TransactionScopeOption  _scopeOption:它是一个枚举类型,它定义了事务行为的 范围,我们在创建Transaction instance Object 的时候需要用到它。它具有三个值选项, 如下所示,默认值是Required:

    Required: 加入环境事务,或者在环境事务不存在时创建一个新的环境事务;

    RequiresNew:开始一个新的Transaction,并使该Transaction成为自己范围内的新环境 事务;

    Suppress:结果就是没有任何环境事务。

    2.TransactionOptions _transactionOptions:它可以用来设置Transaction的超时时间 及Transaction的隔离级别,这里我们需要介绍一下Transaction的隔离级别IsolationLevel ,Transaction的隔离级别确定在该Transaction完成之前,其他Transaction对可变数据所拥 有的访问级别;

    3.EnterpriseServicesInteropOption _interopOption:在Transaction创建的时候可以 用它指定与 COM+ 交互的方式;

    4.protected virtual TransactionScope CreateTransactionScope():这个方法主要的 作用是根据对上面三个属性的设置来创建我们所需要的TransactionScope,它可以自动选择 和管理环境事务,由于他的简单、易用和高效, 它Microsoft推荐的事务处理类。

    上面介绍完了创建事务的方法及所需参数的说明,下面我们看看对ICallHandler的方法是 如何实现的。ICallHandler中有一个成员变量Order和唯一的成员方法Invoke,Order是用来 指示Call Handler执行的顺序,这里我们保留没有使用。继承ICallHandler接口的类规定将 会自动执行Invoke方法,Invoke的参数input代表对调用类的实例,而参数getNext是Call Handler中对实现对象调用的委托(Delegate)。我们对Invoke实现如下内容:

    1.使用using创建Transaction实例,并定义了他的使用范围(或者说使用环境),即整 个using的有效区域;

    2.使用Enterprise Library 的Logger实现对操作方法的记录(具体如何配置和实现这里 暂不叙述,如果有时间下次再写一个关于Logger的内容);

    3.通过在Invoke方法中调用图7的语句实现对目标调用,值得我们注意的是,对他的调用 是在整个Transaction的有效作用范围内实现的,如果对目标对象的调用失败或目标对象执行 出现异常,那我们就不应该调用Invoke中事务范围的Complete方法,那么整个事务就将自动 进行回滚,反之亦然。

    图7: Invoke中对目标方法的调用

    result = getNext()(input, getNext);

    4.Exception的捕获,我们通过对图6中执行结果进行判断,查看是否是存在Exception, 如果存在Exception的相关信息,就将其记录下来(我们也可以把对Exception的操作从此示 例中分离出来,这里只是为了更加形象的表示我们可以在Call Handler中做更多的操作)。

    上面只是定义了一个类TransactionCallHandler,它对ICallHandler interface进行实现 ,但是它依然没有被任何方法所调用,同时我们也没有实现Transaction的Attribute。Ok, 现在我们就来实现Attribute的操作处理的Class,并且在这个Class对 Microsoft.Practices.Unity.InterceptionExtension namespace下抽象类 HandlerAttribute的抽象方法CreateHandler的实现过程中对我们自定义Class TransactionCallHandler进行调用。具体代码如图8所示。

    图8: Transaction Attribute的实现

     

    using System;
    using System.ComponentModel;
    using System.Transactions;
    using Microsoft.Practices.Unity;
    using Microsoft.Practices.Unity.InterceptionExtension;
    
    namespace CWS.Framework.CallHandler.Transaction
    {
         [AttributeUsage(AttributeTargets.Class | AttributeTargets.Method |  AttributeTargets.Property)]
         public class TransactionCallHandlerAttribute :  HandlerAttribute
         {
             private TransactionScopeOption _scopeOption;
             private TimeSpan _timeout = TimeSpan.FromMinutes(1);
             private IsolationLevel _isolationLevel =  IsolationLevel.ReadCommitted;
             private TransactionOptions _transactionOptions;
             private EnterpriseServicesInteropOption  _interopOption;
    
             private string _functionName;
             private string _actionDescription;
    
             public TransactionCallHandlerAttribute()
             {
             }
    
             public TransactionCallHandlerAttribute(TransactionScopeOption  scopeOption)
             {
                 this._scopeOption = scopeOption;
             }
    
             public TransactionCallHandlerAttribute(IsolationLevel  isolationLevel)
             {
                 this._isolationLevel = isolationLevel;
             }
    
             public TransactionCallHandlerAttribute(TransactionScopeOption  scopeOption, IsolationLevel isolationLevel)
             {
                 this._scopeOption = scopeOption;
                 this._isolationLevel = isolationLevel;
             }
    
             public TransactionCallHandlerAttribute(string functionName) 
                 : this(functionName, string.Empty)
             {
             }
    
             public TransactionCallHandlerAttribute(string functionName,  string actionDescription)
             {
                 this._functionName = functionName;
                 this._actionDescription = actionDescription;
             }
    
             public string FunctionName
             {
                 get { return this._functionName; }
                 set { this._functionName = value; }
             }
    
             public string ActionDescription
             {
                 get { return this._actionDescription; }
                 set { this._actionDescription = value; }
             }
    
             public TransactionScopeOption ScopeOption
             {
                 get { return _scopeOption; }
                 set { _scopeOption = value; }
             }
    
             public IsolationLevel IsolationLevel
             {
                 get { return _isolationLevel; }
                 set { _isolationLevel = value; }
             }
    
             public EnterpriseServicesInteropOption InteropOption
             {
                 get { return _interopOption; }
                 set { _interopOption = value; }
             }
    
             public TransactionOptions TransactionOptions
             {
                 get
                 {
                     if (_transactionOptions == null)
                         _transactionOptions = new  TransactionOptions();
                     _transactionOptions.Timeout = _timeout;
                     _transactionOptions.IsolationLevel =  _isolationLevel;
                     return _transactionOptions;
                 }
             }
    
             public override ICallHandler CreateHandler(IUnityContainer  container)
             {
                 _transactionOptions = TransactionOptions;
                 if (!string.IsNullOrEmpty(this._functionName))
                 {
                     return new TransactionCallHandler (_functionName, _actionDescription, _transactionOptions);
                 }
                 else
                 {
                     return new TransactionCallHandler (ScopeOption, TransactionOptions, InteropOption);
                 }
             }
         }
    }

     

    可以看到我们定义了一个TransactionCallHandlerAttribute,它继承了 HandlerAttribute,我们必需实现HandlerAttribute抽象类中的抽象方法CreateHandler,当 我们对Attribute进行引用的时候,他将会自动调用CreateHandler方法来创建Call Handler 对象,我们这里使用的Call Handler对象就是上面定义的TransactionCallHandler,在 TransactionCallHandler里我们知道他在创建TransactionScope对象的时候需要一些参数, 所我们创建了一些属性用于在引用Transaction Attribute 的使用符合项目需求的Custom Parameters。另外我们还提供几个构造函数,同样可以传递相应的Custom Parameters。

    注:在TransactionCallHandlerAttribute的上面有一个Attribute AttributeUsage,它 的作用是用来指定我们新创建的TransactionCallHandlerAttribute的使用范围。这里我们指 定他可以使用在Class、Method及Property上,除这三个以外,还有很多个使用范围的界定, 这里我们就不在一一说明了。

    OK,至此我们已经完成了对Transaction Call Handler和Attribute的类的实现,下面要 做的就是在我们创建的WebTest Project中应用这个Transaction Attribute。

     

    四.Transaction Attribute 的应用

    我们WebTest Project中加入一个Class,这里叫做TestDA。在这个类里我们提供了一个方 法叫Add,它会循环的把DataSet中的User信息插入到数据库中T_USER表中(这里只是做示例 ,不考虑其合理性及其它因素),我们对这个方法注入我们上面完成的 TransactionCallHandlerAttribute,如图9所示。这也就是说我们把这个方法纳入到 Transaction处理的范围中了,OK,现在我们让第一条数据成功插入,当在插入第二条数据的 时候,我们抛出一个Exception,以验证我们的Transaction注入是否成功,之后我们再把抛 出Exception删除掉,来确认我们的Transaction Commit是功能的。

    图9: 数据库操作类

     

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Data;
    using CWS.Framework.CallHandler.Transaction;
    using System.Data.SqlClient;
    using System.Configuration;
    
    namespace test
    {
         public class TestDA : MarshalByRefObject
         {
             [TransactionCallHandlerAttribute(FunctionName = "User",  ActionDescription = "Add User Information")]
             public void AddUser(DataSetUser ds)
             {
                 SqlConnection connection = new SqlConnection ();
                 connection.ConnectionString =  ConfigurationManager.ConnectionStrings ["TransactionDemoConnectionString"].ConnectionString;
                 connection.Open();
                 SqlCommand command = connection.CreateCommand ();
    
                 command.CommandType = CommandType.Text;
                 int i = 0;
                 foreach (DataSetUser.T_USERRow row in  ds.T_USER.Rows)
                 {
                     if (i == 1)
                         throw new Exception("throw this  exception when update second record.");
                     command.CommandText = "insert into T_USER ([Name],[Password]) values(@p_username,@p_password)";
                     command.Parameters.Clear();
                     command.Parameters.Add(new SqlParameter (@"@p_username", row.Name));
                     command.Parameters.Add(new SqlParameter (@"@p_password", row.Password));
                     command.ExecuteNonQuery();
                     i++;
                 }
             }
         }
    }

     

     

    下面我们来创建两条数据并且调用TestDA中的AddUser方法。我们在WebTest Project的 Default.cs的Page_Load中加入如下代码。

    图10: 创建测试数据,并调用TestDA的AddUser方法

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.UI;
    using System.Web.UI.WebControls;
    using Microsoft.Practices.EnterpriseLibrary.PolicyInjection;
    
    namespace test
    {
         public partial class _Default : System.Web.UI.Page
         {
             protected void Page_Load(object sender, EventArgs e)
             {
                 DataSetUser ds = new DataSetUser();
                 DataSetUser.T_USERRow user1 =  ds.T_USER.NewT_USERRow();
                 user1.Name = "admin";
                 user1.Password = "password";
                 ds.T_USER.Rows.Add(user1);
                 DataSetUser.T_USERRow user2 =  ds.T_USER.NewT_USERRow();
                 user2.Name = "tomery";
                 user2.Password = "password";
                 ds.T_USER.Rows.Add(user2);
                 //TestDA t = new TestDA();
                 TestDA t = PolicyInjection.Create ();
                 t.AddUser(ds);
             }
         }
    }

    我们通过PIAB的PolicyInjection的Create方法创建一个TestDA的实例对象,为什么要要 用这个方法去创建,而不直接New一个对象呢?这是因为PIAB需要将对方法的调用进行拦截, 然后执行你所需要调用的目标方法上面的注入操作,如我们这个例子中的 TransactionCallHandlerAttribute,然后再执行目标方法,如果你直接使用New的方式,他 不能拦截到对目标方法的调用,这就是我们使用这样方式的原因。

    五.总结

    通过对传统Transaction的分析与比较,提出对Transaction分离的想法,使用EnterLib实 现对Transaction的注入,然后通过它来拦截操作,完成Transaction的功能与扩展。通过上 边的具体了解了如下内容:

    1.Transaction及创建Transaction所需的参数进行了解;

    2.Attribute的实现;

    3.CallHandler处理类的实现;

    4.PIAB注入与拦截的了解。

  • 相关阅读:
    Redis集群搭建步骤
    JS性能优化
    javaweb中实现在线人数统计
    tomcat在linux中启动慢的解决方案
    Redis高可用架构
    bjpowernode课程体系及题库
    java 相关
    码农翻身全年文章精华
    Spring源码深度解析
    PHPSTROM快捷键备份
  • 原文地址:https://www.cnblogs.com/chenjiang/p/3113309.html
Copyright © 2020-2023  润新知