转【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注入与拦截的了解。