(轉自:http://www.sunnybtoc.com/page/M0/S230/230473.html)html
一.寫做前提數據庫
以前在蘇州的一家知名軟件企業工做時,使用了他們提供的框架和類庫,切實的感覺到它們所帶來的便利,它不只提升了軟件的開發速度,減小了代碼的冗餘,更重要的是提升了企業產品的開發效率及質量。而今換了工做環境(一家國外小軟件公司),在缺乏了這些有利的工具以後,發現公司以前的幾乎全部項目都在重複的Copy代碼,這不只僅是延長項目的開發週期,最麻煩的莫過於對項目的管理借來及大的困難,看了讓我內心有些不是滋味。以後,我就開始嘗試着寫些高效、集成的代碼(已經寫了一部分了),我但願可以和你們分享和交流,也但願獲得一些指正和建議。框架
本篇咱們所要討論的問題就是如何使用Enterprise Library的Unity Interception Extension 和Policy Injection Application Block(PIAB)實現以Attribute形式注入的Transaction Call Handler。關於什麼是事務,這裏就不在多加敘述,咱們把更多的篇幅用來本文的主題進行講解。ide
二.將Transaction與業務邏輯分離函數
Enterprise Library 是微軟推出的一個Open Source框架,或者說是一個可擴展的類庫,最新Release版本是4.1,目錄已經出了5.0人的Bate版本。Enterprise Library是由多個Application Block組成的,例如:工具
Policy Injection Application Block使得開發人員可使用攔截策略執行一些比較Common及非業務邏輯的特性,例如:Logging、Caching、Exception、Validation以及其餘的控制與操做,在PIAB中,它們以注入的形式提供給開發者,爲用戶的開發帶來了及大的方便,因此我考慮以一樣的形式來寫了一個injection的Transaction Call Handler,它用來在對實際方法調用以前進行攔截,而後開啓Transaction,調用實際目標方法,最好實現提交Transaction,並在Transaction中進行相應的擴展操做。測試
OK,首先咱們來看看傳統的事務代碼:ui
圖1: Traditional Transaction Codethis |
public void AddUser(DataSetUser ds)spa { 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, GetNextHandlerDelegategetNext); } } |
首先須要對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, GetNextHandlerDelegategetNext) { 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:
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, stringactionDescription) { 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注入與攔截的瞭解。