程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> 利用EnteLib Unity Interception Extension和PIAB實現Transaction的Call Hand

利用EnteLib Unity Interception Extension和PIAB實現Transaction的Call Hand

編輯:關於.NET

一.寫作前提

之前在蘇州的一家知名軟件企業工作時,使用了他們提供的框架和類庫,切實的感受到它 們所帶來的便利,它不僅提高了軟件的開發速度,減少了代碼的冗余,更重要的是提高了企 業產品的開發效率及質量。而今換了工作環境(一家國外小軟件公司),在缺少了這些有利 的工具之後,發現公司之前的幾乎所有項目都在重復的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(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<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注入與攔截的了解。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved