如何利用EnteLib Unity Interception Extension 和PIAB实现Transaction
一.写作前提
之前在苏州的一家知名软件企业工作时,使用了他们提供的框架和类库,切实的感受到它们所带来的便利,它不仅提高了软件的开发速度,减少了代码的冗余,更重要的是提高了企业产品的开发效率及质量。而今换了工作环境(一家国外小软件公司),在缺少了这些有利的工具之后,发现公司之前的几乎所有项目都在重复的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); } } |
图5: Microsoft.Practices.Unity.InterceptionExtension. ICallHandler interface |
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(TransactionScopeOptionscopeOption) { this._scopeOption = scopeOption; }
public TransactionCallHandlerAttribute(IsolationLevel isolationLevel) { this._isolationLevel = isolationLevel; }
public TransactionCallHandlerAttribute(TransactionScopeOptionscopeOption, 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<TestDA>(); 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注入与拦截的了解。