程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> WF從入門到精通(第十五章):工作流和事務

WF從入門到精通(第十五章):工作流和事務

編輯:關於.NET

學習完本章,你將掌握:

1.了解傳統的事務模型以及這種模型在哪些地方適合去使用,哪些地方不適合使用

2.懂得在哪些地方不適合傳統的事務以及什麼時候是補償事務的恰當時機

3.看看怎樣回滾或補償事務

4.看看怎樣修改默認的補償順序

如果你是寫軟件的,你遲早需要去理解事務處理。事務處理(transactional processing)在這個意義上是指寫那些把信息記錄到一個持久化資源的軟件,這些持久化資源如數據庫、Microsoft消息隊列(它在底層使用了一個數據庫)、帶事務文件系統的Windows Vista以及注冊表存取或者甚至是其它一些支持事務處理的軟件系統。持久化資源不管發生什麼事情,一旦數據被記錄下來就會保留這些寫入的信息。

事務對於任何業務流程都是關鍵的,因為通過事務,你能確保你的應用程序中數據的一致性。假如業務流程維持一個錯誤但仍要持久化一些數據,那麼這些錯誤的數據很有可能會波及到整個系統,這就留給你一個問題:哪些數據是正確的,哪些數據是錯誤的。試想從一個在線商家訂購這本書,可是卻發現商家和你的信用卡交易“出了點小意外”,收取了你書的標價的100倍而不是他們的折扣價。當像這樣的錯誤可能發生時,事務處理就不再是一個可笑的或者避而不談的話題。

理解事務

事務處理,其核心就是管理你的應用程序狀態。對於狀態,我實際指的是應用程序的所有數據的狀況。當應用程序的所有數據是一致的,那麼該應用程序就處於一個確定狀態。假如你把一條新的客戶記錄添加到你的數據庫中,則此過程需要兩個插入(一是新增一條通常的包含有把地址和你的客戶聯系在一起的信息的行記錄,另一條是記錄真實地址信息),添加通常的行記錄成功後,但添加它的地址時卻失敗了,這就使你的應用程序處於一個不確定狀態。當某人試圖檢索該地址將發生什麼呢?系統會提示到地址應該在此,但真實的地址記錄已丟失。你的應用程序數據現在是不一致的。

為確保這兩個操作成功,事務起到了作用。一個事務本身是一個單一的工作單元,它要麼全部成功要麼全部失敗。這並不是說你不能更新兩個不同的數據庫表。它剛剛的意思是兩個表的更新被看作是一個單一的工作單元,兩個都必須被更新否則都失敗。假如其中一個或者兩個更新失敗,理想情況下是你想讓系統回到剛剛你試圖更新這些表之前的狀態。沒有跡象表明試圖更新這些表的操作是非成功的話,你的應用程序就應該繼續前進,但更重要的是,你不想有來自更新不成功的一個表裡有而另一個表裡卻沒有的數據。

備注:有整卷書全部寫和事務及事務處理相關知識的書籍。盡管我將描述的解釋Microsoft Windows Workflow Foundation(WF)是怎麼支持事務的相關概念足夠深,但我不可能在本書中以相當深的深度覆蓋事務處理的方方面面。假如你還沒有重新看看.NET 2.0中通常的事務支持的話,你應該這樣去做。WF的事務處理模式和.NET 2.0的事務支持非常緊密,你可以在下面的論文中找到有用的知識去理解WF的事務支持:msdn2.microsoft.com/en-us/library/ms973865.aspx。

傳統上,事務來自於一個單一的模式:XA或兩階段提交(two-phase commit)類型的事務。但是,隨著基於Internet通信的出現以及需要提交長時間運行的事務的要求,引進了新一些的稱作補償式事務的事務類型。WF支持這兩種類型。我們將首先討論傳統的事務,然後我們將提到使用這種類型的事務是一個低級的架構選擇,再後我們將討論補償式事務。

傳統(XA)事務

已知的第一個實現了事務處理的系統是一個航空公司的預訂系統。對於需要預訂多個航班的請求,假如任何一個單獨的航班不能預訂,則該預訂就不能進行。系統設計師知道這些並設計了一個我們今天稱為X/Open分布式事務處理模型,簡稱XA的事務化方式來進行處理。(看看en.wikipedia.org/wiki/X/Open_XA。)

XA事務涉及到XA協議,即我先前提到的兩階段提交和三個實體:應用程序、資源和事務管理器。應用程序也就是指你的應用程序。資源是一個設計的用來加入到XA類型的事務中去的軟件系統,也就是說它參與事務並懂得怎樣參與兩階段提交數據以及提供持久性(很快將進行討論)。事務管理器監視整個事務處理流程。

因此什麼是兩階段提交呢?最後,想像你的應用程序需要寫入數據,也就是說一個數據庫。假如寫入過程在事務下執行,數據庫就保持這些要被寫入的數據直到事務管理器發出一條准備(prepare)指令為止。在那時,數據庫以一個表決(vote)作為響應。假如該表決是要前進並把數據提交(寫入)到表中,那麼事務管理器則進入並參與資源,假如有的話。

假如所有資源對提交數據都表決成功,則事務管理器發出一個提交(commit)指令然後每一個資源就把數據寫到它內部的數據存儲中。只有到那時指定要寫入你的表中的數據才真正地插入到數據庫中。

假如任何一個資源有問題並且沒有對提交數據進行表決,則事務管理器發出一個回滾(rollback)指令。所有參與進事務的資源就必須銷毀和事務相關的信息,沒有東西被永久的記錄保存下來。

一旦數據被提交,XA協議會保證事務結果的持久性。數據是一致的,並且應用程序也處在一個確定狀態中。

ACID屬性

當我們談到XA事務時是不可能不提到ACID的:原子性、一致性、隔離性和持久性(en.wikipedia.org/wiki/ACID)。

對於原子性(atomic),我們指的是資源參與了兩階段提交協議的事務支持中。要被處理的數據要麼全部被處理(更新、刪除或者其它任何操作)要麼都不處理。假如事務失敗,資源就返回到剛剛要試圖處理該數據之前的狀態。

一致性(Consistency)指的是數據保持完整性。對數據庫而言,典型的意思是指數據不應違反任何一致性約束,但對於其它資源而言,保持完整性可能有所區別或者有額外的含義。假如數據違反了任何規則或者一致性約束,它最後的結果會是應用程序處於一個不確定狀態中,資源必須回滾事務以防止不一致的數據被持久記錄進系統中。

隔離性(isolation)是指在事務進行中導致系統不能存取數據的一個事務屬性。在數據庫中,試圖寫入一個之前被鎖住的行或者讀取一個未提交數據的行是不允許的。僅當數據被提交後才能使用這些數據,或者是在你明確允許未提交讀時進行讀操作的情況(通常稱作“髒數據”)。

持久性(durable)資源保證數據被提交後將總是能以非易失性的方式獲取它。如果數據庫服務器的電源在數據被提交後的一毫秒被切斷,在數據庫服務器重新上線後數據應還在數據庫中為你的應用程序的使用隨時做好准備。在實際中做到這些比聽起來還更加困難,一個主要的原因是架構師使用數據庫來為關鍵的數據進行持久化的數據存儲而不是像XML之類的單一數據文件。(不可否認,Windows Vista中的事務文件系統有了一些改觀,但希望你能領會我的觀點。)

長時間運行的處理過程和應用程序狀態

記住,XA類型的事務的整個前提是假如事務回滾的話,你的應用程序將回到它的初始狀態。但是考慮一下這個情況:假如事務花費了過長的時間才提交的話你的應用程序會發生什麼呢? 

在我回答之前,假想你的在線交易系統收到了一個客戶的訂單,但是信用卡驗證的過程中被中斷了。無疑你的處理過程在一個事務中進行,因為假如某些地方失敗的話你就不想再去收取該客戶的款項。但是與此同時,其它客戶又正在發來訂單,假如你運氣不錯的話,這是大量的訂單。假如第一個客戶的交易失敗後,訂單在此期間發來將發生什麼呢?假如系統的目的不是為了隔離單獨的一個訂單處理失敗,那麼正確的做法是把系統完全回滾到它的原始狀態。但是考慮這種情況則意味著,我們不僅會丟失第一個客戶的訂單,也會丟失在此期間其它每一個客戶發來的訂單。即使這可能只有兩個訂單,但也不是很好。假如這是10,000份訂單呢...損失的收入數額是不能容忍的。

當然,我們將保留這10,000份訂單並把剛剛處理的第一份訂單放到一個孤立的事件中,但是在這種情況下我們同時也有可能會有意破壞事務屬性中的某一個來保留這些訂單收入。這是一個風險,需要進行估量,但通常在現實世界情況下我們必須接受這一風險。  被破壞的屬性其實是原子性,出於這個原因,寫事務處理系統的人會努力在盡可能短的時間內持有事務。你做的僅僅只能是你事務范圍內所必須要做的事,你要盡可能高效地做這些事以便事務快速地完成。

現在我們進入另外一種復雜情況:Internet。你的客戶正在線發送訂單,網絡速度是出了名的慢甚至中斷。因此,在Internet之上的事務處理是有疑慮的。

補償作為一個解決辦法

這正是創建所需要的補償事務的情況。假如我給你五個蘋果,過程使用XA-類型的事務,事務失敗時,時間會回滾到我開始給你蘋果的那一刻。在某種意義上說,歷史會被重寫以致這五個蘋果沒有被首先考慮到。但是假如我是在一個補償式事務中給你的五個蘋果的話,事務失敗進行補償(以便我們維護一個確定的應用程序狀態)時,你必須返回五個蘋果給我。看起來差別很小,但兩種類型的事務之間存在明顯的區別。

當寫XA類型的事務時,負責回滾失敗事務的職責落在資源上,例如你的數據庫。相反,當一個補償式事務失敗時,作為事務參與者,你有責任通過提供一個事務補償功能來為你的事務部分提供補償。假如你扣除了某個在線客戶信用卡中的金額並在後來被告知要補償,你會立即向該客戶賬戶存入你起初扣除的相同數目的金額。在一個XA類型的事務中,該賬戶絕不會在一開始就被扣除。對於補償式事務來說,你發起兩個操作:一個是扣除賬戶,另一個是後來存入它。

備注:毫無疑問,它將是一個極好的系統,它能在Internet上成功執行XA類型的事務。但是要非常仔細地構思你的補償功能,注意各個細節。如果你不這樣做的話,你可能由於一錯再錯而使情況更加糟糕。寫出准確的補償功能通常並不容易。

向你的工作流中引入事務

總的來說,在WF中引入事務和拖拽一個基於事務的活動到你的工作流中一樣簡單。但是,如果你正使用事務活動話,你應該知道更多一些的東西。

工作流運行時和事務服務

當你在你的工作流中使用基於事務的活動時,需要兩個可插拔的工作流服務。首先,因為基於事務的WF活動需要標注PersistOnClose特性(在第6章“加載和卸載實例”中提過),因此你也必須啟動SqlWorkflowPersistenceService。假如你沒有的話,WF不會崩潰,但是你的事務也不會提交。

或許在本章更感興趣的是DefaultWorkflowTransactionService。 這個服務為你事務操作的啟動和提交負責。沒有這樣一個服務的話,工作流運行時內的事務是不可能實現的。

備注:你可以創建你自己的事務服務,盡管這超出了本章的范圍。所有WF事務服務都派生自WorkflowTransactionService,因此創建你自己的服務基本上就是重寫(override)你想改變的基類功能。實際上,WF用了一個自定義的事務服務:SharedConnectionWorkflowTransactionService來承載共用的Microsoft SQL Server連接。在msdn2.microsoft.com/en-us/library/ms734716.aspx可找到更詳細的信息。

失敗處理

對於事務失敗,盡管在你的工作流中並不需要你去處理失敗過程,但這是個好習慣。我不想提它的原因是它被認為是一個最佳做法。我提到它的原因是你可能去實現你自己的,在真正失敗之前能自動檢查異常並重新嘗試事務處理的事務服務。盡管對這些要怎麼做進行演示超出了本章的范圍,但你應該知道這是可以做到的。

環境事務(ambient transaction)

基於事務的活動都在被稱為環境事務的東西下工作。當你的工作流進入一個事務范圍內的時候,工作流事務服務會自動為你創建一個事務。這不需要去嘗試並建立你自己的事務。所有嵌入到事務范圍中的活動都屬於這一個環境事務,假如事務成功它們就都提交,否則就回滾(或者補償)。

使用TransactionScope活動

在WF中XA類型的事務由TransactionScope活動實現。這個活動和.NET的System.Transactions名稱空間的聯系密切,事實上當活動開始執行的時,它會創建一個Transaction來作為環境事務。該TransactionScope活動甚至與System.Transactions共享數據結構(TransactionOptions)。

使用基於組合活動的TransactionScope確實和把它拖到你的工作流中一樣容易。你放進TransactionScope活動中的任何活動都自動繼承該環境事務並像通常的使用.NET自己的System.Transactions事務時一樣進行操作。

備注:你不能在其它事務活動中放入TransactionScope活動。事務嵌套是不允許的。(這條規則也適用於CompensatableTransactionScope。)

事務選項更精確地指定了環境事務將怎樣進行操作。由System.Transactions.TransactionOptions結構支持的這些選項讓你能去設置環境事務將支持的隔離級別和延時時間。延時時間值可不需加以說明,但是隔離級別則不。

備注:超時值可以進行限制,它是可配置的。有一個機器范圍(machine-wide)的設置System.Transactions.Configuration.MachineSettingsSection.MaxTimeout,和一個局部范圍的設置System.Transactions.Confituration.DefaultSettingsSection.Timeout。它們能為超時值設置一個最大上限值。這些值會覆蓋你使用TransactionOptions所做的任何設置。

事務隔離級別在很大程度上確定了事務能處理哪些要進行事務處理的數據。例如,你或許想讓你的事務能去讀取未提交的數據(解除前面的事務數據庫頁面鎖)。或者你正寫入的數據可能很關鍵,因此你要讓事務只能讀取已提交的數據,甚至,當你的事務正執行時,你禁止讓其它事務在該數據上工作。你可以選擇使用的隔離級別如表15-1所示。使用TransactionScope活動的TransactionOptions屬性,你可同時設置隔離級別和超時值。

表15-1 事務的隔離級別

隔離級別 含義 Chaos 無法改寫隔離級別更高的事務中的掛起的更改。 ReadCommitted 在正在讀取數據時保持共享鎖,以避免髒讀,但是在事務結束之前可以更改數據,從而導致不可重復的讀取或幻像數據。即在事務期間未提交的數據不能讀出,但能被修改。 ReadUncommitted 可以進行髒讀,意思是說,不發布共享鎖,也不接受獨占鎖。即在事務期間未提交的數據既能讀出也能修改。要記住數據可以修改:不能保證該數據和隨後讀取的數據是相同的。 RepeatableRead 在查詢中使用的所有數據上放置鎖,以防止其他用戶更新這些數據。防止不可重復的讀取,但是仍可以有幻像行。即在事務期間未提交的數據能讀出但不能修改。但是,能插入新數據。 Serializable 在DataSet上放置范圍鎖,以防止在事務完成之前由其他用戶更新行或向數據集中插入行。未提交的數據能讀出但不能修改,在事務期間不能插入新數據。 Snapshot 通過在一個應用程序正在修改數據時存儲另一個應用程序可以讀取的相同數據版本來減少阻止。表示您無法從一個事務中看到在其他事務中進行的更改,即便重新查詢也是如此。即未提交的數據能讀出,但在事務真正修改數據之前,該事務會驗證在它最初讀取數據後其它事務沒有修改該數據。假如數據被修改了,該事務就激發員工錯誤。這樣做的目的是讓事務能讀取先前提交的數據值。 Unspecified 正在使用與指定隔離級別不同的隔離級別,但是無法確定該級別。當使用OdbcTransaction時,如果不設置IsolationLevel或者將IsolationLevel設置為Unspecied,事務將根據基礎ODBC驅動程序的默認隔離級別來執行。假如你試圖設置事務隔離級別為該值,那麼就會拋出一個異常。只有事務系統能設置該值。

當你拖拽一個TransactionScope活動到你的工作流中後,隔離級別會自動設置為Serializable。根據你的架構要求,你可不受限制地改變它。Serializable是最嚴格的隔離級別。但是它也在一定程度上限制了可擴展性。對要求吞吐量更大一些的系統而言,選擇ReadCommitted來作為隔離級別並不是稀罕事,但是這要根據你的個人需求來做決定。

提交事務

假如你正使用的是SQL Server事務,或者可能是COM+事務,你就知道一旦數據已被插入、更新或刪除,你就必須提交該事務。也就是說,你啟動兩階段提交協議和數據庫來永久記錄或移除這些數據。

但是,對於TransactionScope活動而言不是必要的。假如事務執行成功(當插入、更新、刪除數據時沒有出錯),當工作流執行過程離開該事務范圍時,該事務會為你自動提交。

回滾事務

怎麼樣回滾失敗的事務呢?哦,就像事務成功為你提交一樣,假如事務失敗數據也將被回滾。有趣的是回滾是悄無聲息的,至少就WF而言是很關注的。假如你需要檢查事務是成功還是失敗,你就需要親自加入邏輯代碼來完成這件事。假如事務失敗,TransactionScope是不會自動拋出異常的。它僅僅回滾數據然後繼續前進。

使用CompensatableTransactionScope活動

假如XA類型的事務不能勝任,你可以拖拽CompensatableTransactionScope活動到你的工作流中來提供補償式的事務處理過程。CompensatableTransactionScope活動和TransactionScope一樣是一個組合活動。但是,CompensatableTransactionScope實現了ICompensatableActivity接口,通過實現Compensate方法賦予了它能對失敗的事務進行補償的能力。

和TransactionScope相似,CompensatableTransactionScope活動也創建一個環境事務。包含進CompensatableTransactionScope的活動共享這個事務。假如它們操作都成功,數據就被提交。但是,如果它們中任何一個操作失敗,你通常通過執行一個Throw活動去啟動補償。

提示:補償式事務能支持像數據庫之類的傳統資源,當事務提交時,數據就像以XA類型的事務一樣被提交。但是,補償式事務的優點是你不必選擇一個XA類型的資源來存儲數據。對於不支持傳統資源的一個典型例子是使用一個Web服務來把數據發送到一個遠程站點。假如你把數據發送到遠程站點但是後來必須進行補償的話,你需要和數據不再有效的遠程站點間進行某種通信。(你怎麼實現這些有賴於各個遠程站點。)

Throw導致死亡失敗並為你的CompensatableTransactionScope活動去執行你的補償處理程序。你可通過CompensatableTransactionScope活動的智能標簽來訪問該補償處理程序,在許多地方和你添加一個FaultHandler是一樣的。

備注:盡管拋出一個異常啟動事務補償,但是Throw活動自身並不會進行處理。你也能在你的工作流中放入一個FaultHandler活動來防止工作流提前終止。

使用Compensate活動

當你通過CompensatableTransactionScope對失敗進行補償時,會調用補償處理程序。假如你有多個補償式事務時,事務將以默認的順序進行補償,從嵌套得最深的事務開始向外工作。(在下一節中你將看到這些怎麼實現。)當你的處理邏輯要求補償的時候,你可以在你的補償處理程序中放入一個Compensate活動來開始為所有已經完成的、支持ICompensatableActivity的活動進行補償。

實際上異常的情況總是會引起補償,因此使用Compensate活動不是必須的。為什麼還要用它呢?因為你可能在一個CompensatableSequence活動中嵌入超過一個以上的補償式事務。假如一個事務失敗並去補償的話,你就能為其它事務開始補償工作,即使該事務先前已經成功完成。

備注:Compensate活動只有在補償處理程序(cmpensation handler)、取消處理程序(cancellation handler)和失敗處理程序(fault handler)中有效。

只有當你需要以某個其它的順序而不是默認的補償順序進行補償的時候,你才應該去使用Compensate活動。默認補償的順序和所有嵌入的ICompensatableActivity活動的完成順序正好相反。假如這個順序不適合你的工作流模型,或者假如你想要有選擇性地為已完成的可補償子活動進行補償的話,Compensate活動是一個可選擇的工具。

備注:Compensate活動使用它的TargetActivityName屬性來識別出哪個可補償式活動應該被補償。假如超過一個以上的可補償式活動要進行補償工作的話,你就需要使用一個以上的Compensate活動。假如你決定不去補償一個特定的事務的話,對於該事務或者包含其父活動中的補償處理程序中可不做任何事情。

通過讓你能決定你是否想立即對支持補償的子活動進行補償的這一手段,Compensate活動為你提供了在補償處理過程中進行控制的能力。這個能力能讓你的工作流按照你業務流程的需要在一個嵌套的補償式活動中明確無誤地執行補償工作。通過在Compensate活動中指定你想要去補償的可補償式活動,只要該可補償式活動先前已成功的提交,那麼該可補償式活動中的任何補償代碼都將被執行。

假如你想對超過一個以上的可補償式活動進行補償的話,你可在你的處理程序中為每一個你想去補償的可補償式活動添加一個Compensate活動。假如Compensate活動用在了一個可補償式活動的處理程序中,而該可補償式活動的又容納有嵌入的可補償式活動,並且假如為該Compensate活動指定的TargetActivityName是其父活動的話,在該父活動中所有已經成功提交的子(可補償式)活動的補償工作也都會被調用。為了強調這些共說了三次。

使用CompensatableSequence活動

前一節可能留給你一個疑問:Compensate活動以什麼為載體。畢竟,你不能嵌入補償式事務,你不能嵌入基於WF的任何一種類型的事務。

但是讓我們從不同的角度來看待它。你會怎樣把兩個可補償式事務捆在一起以便其中一個失敗時觸發另一個去補償,而且假如另一個已經成功完成了呢?答案是你成對地把補償式事務放進一個單一的CompensatableSequence活動中。然後,假如兩個子事務活動中的其中一個失敗的話,在CompensatableSequence活動的補償代碼或者失敗處理程序中,你就觸發這兩個子事務活動去進行補償。

甚至更有趣的是,在你把三個補償式事務一起捆進一個單一的CompensatableSequence活動中,並且允許即使其它兩個事務失敗並被補償的情況下,該事務也能成功。Compensate活動為你提供了這些控制能力。

這些突出了CompensatableSequence活動的作用。CompensatableSequence活動從本質上來說是一個Sequence活動,你使用CompensatableSequence活動的方式和任何順序活動一樣。主要的區別是你能在一個單一的CompensatableSequence活動中嵌入多個可補償式活動,實際上是把相關的事務捆在一起。讓CompensatableSequence活動結合CompensatableTransactionScope和Compensate活動能為你在你的工作流中提供強大的事務控制能力。

備注:CompensatableSequence活動能被嵌進其它的CompensatableSequence活動中,但是它們不能作為CompensatableTransactionScope活動的子活動。

提示:當在單一的一個可補償式序列中包含多個補償式事務的時候,你不需要為個別事務活動指定補償功能。如果需要的話補償會流向父活動,因此假如你需要的話,你能把你的補償活動都聚集到一個封閉的可補償式順序活動中。

創建一個事務型的工作流

我已創建了一個模擬自動櫃員機(ATM)的應用程序,你提供你的個人識別碼(也稱作PIN)後,就可從你的銀行賬戶中存款和取款。存款操作將被嵌進一個XA類型的事務中,而取款過程如果操作失敗的話將進行補償。為了對應用程序的事務性質進行練習,我在該應用程序中放入了一個“Force Transactional error”復選框(check box)。只要簡單地選中該復選框,接下來相關的數據庫操作將失敗。對於該應用程序的工作流來說是基於狀態的,它比你在前一章(第14章,“基於狀態的工作流”)中看到過的應用程序還要復雜。我在圖15-1中展示了該狀態機工作流。應用程序的大部分代碼我已經為你寫出了。接下來你將在練習中添加事務組件。

圖15-1 WorkflowATM的狀態圖

該應用程序的用戶界面如圖15-2所示。這是應用程序的初始狀態,插入銀行卡之前ATM的狀態都和這近似。當然,該示例不能處理真實的銀行卡,因此點擊B鍵將把用戶界面(和應用程序狀態)切換到PIN驗證狀態(如圖15-3所示)。

圖15-2WorkflowATM的初始用戶接口

圖15-3WorkflowATM的PIN驗證用戶界面

你使用按鍵輸入正確的PIN。一旦輸入了四個數字代碼,你可點擊C鍵來開始對數據庫進行查詢以驗證該PIN。假如PIN經過驗證(注意帳號在左下角,該PIN碼必須是該帳號的),用戶界面便切換到如圖15-4所示的操作選擇狀態。這裡你可選擇從你的賬戶中存款或取款。

圖15-4WorkflowATM的操作選擇用戶界面

對於存款和取款的應用程序用戶界面是相似的,因此我只展示了存款的用戶界面,如圖15-5所示。你可再次使用按鍵輸入金額然後點擊一個命令鍵:D鍵來進行存款或取款,或者E鍵來取消該交易。

圖15-5WorkflowATM的存款交易用戶界面

假如交易成功,你會看到如圖15-6所示的界面。假如失敗,你將看到如圖15-7所示的錯誤屏幕。這都沒關系,點擊C鍵重新開始該工作流。

圖15-6WorkflowATM交易成功的用戶界面

圖15-7WorkflowATM交易失敗的用戶界面

該應用程序需要一個數據庫來完整地對WF的事務能力進行測試。因此,我創建了一個簡單的數據庫來保存包含有PIN和賬戶余額的用戶帳戶信息。幾個存儲過程也被用來和數據庫進行配合。所有涉及數據庫更新的存儲過程都必須在一個事務中執行:我要對@@trancount檢查,假如它為0,我就從該存儲過程中返回一個錯誤。

假如我錯誤地使用一些ADO.NET代碼來初始化我自己的SQL Server事務的話,這些就能證實環境事務正被使用。這些也意味著你需要創建一個數據庫實例,但是這很容易實現,因為你在前面的章節中已經學過了怎樣在SQL Server Management Studio Express中執行查詢語句。實際上,我們將從這個任務起步因為我們將很快需要數據庫來對應用程序進行開發和測試。

備注:之前我忘了提到,該數據庫的創建腳本只創建了一個賬戶:11223344,PIN為1234。該應用程序允許你去改變賬戶以及你想使用的任何PIN值,但是你要麼使用該賬戶(11223344)以及它的PIN(1234),要麼創建你自己的賬戶記錄,否則將不允許你去進行存取款。

創建Woodgrove ATM數據庫

1.在本章源代碼中你會找到“Create Woodgrove Database.sql”數據庫生成腳本。找到它然後啟動SQL Server Management Studio Express。

備注:當然SQL Server完全版也可以。

2.當SQL Server Management Studio Express打開後,把“Create Woodgrove Database.sql”文件拖拽到SQL Server Management Studio Express中。再打開該腳本文件後執行它。

3.該腳本會創建Woodgrove數據庫和全部數據。該腳本的第五和第七行指明了數據庫的目錄和文件名。假如你不能在該默認目錄(C:\Program Files\Microsoft SQL Server)下加載SQL Server,你可能需要修改將被生成的數據庫的默認目錄。你可以根據需要隨意修改。在大多數情況下,你不需要作出修改。點擊“執行”按鈕運行該腳本並生成數據庫。

4.當你在使用SQL Server Management Studio Express中,假如你沒有進行第6章“加載和卸載實例”中的“為持久化創建SQL Server”這一節的話,需要去安裝工作流持久化數據庫,現在就這樣做。在完成了這四個步驟的話,你將有兩個數據庫:Woodgrove數據庫用來保存銀行業務信息,WorkflowStore數據庫用來進行工作流的持久化:現在我們就來寫一些工作流事務代碼。

添加XA類型事務到工作流中

1.下載本章源代碼,在WorkflowATM目錄中你會找到WorkflowATM應用程序(WorkflowATM Completed目錄中是本解決方案的最終完成版),打開WorkflowATM目錄中的解決方案。你可能需要在App.Config文件中修改SQL Server的連接字符串。

2.為了讓自定義活動在Visual Studio工具箱中顯示出來,需要編譯整個解決方案。

3.盡管WorkflowATM應用程序比較復雜,但它遵循的模式我們貫穿本書都在使用。

該Windows Forms應用程序自身和工作流的通信通過一個本地通信服務實現,它使用了我用wca.exe創建的一些自定義活動。該服務在BankingServer項目中,但是該工作流卻在BankingFlow項目中。我們只關注工作流自身的代碼。在BankingFlow項目中找到Workflow1.cs文件,然後在工作流視圖設計器中打開它准備進行編輯。該工作流會顯示出你在這裡看到的界面。它看起來和圖15-1有些相像嗎?

4.為插入XA類型的事務,首先雙擊DepositState活動中的CmdPressed4 EventDriven活動。

5.仔細看看左邊,你會看到名稱為makeDeposit1的Code活動。從工具箱中拖拽一個TransactionScope活動到makeDeposit1活動和該Code活動上面的ifElseBranchActivity11標題之間。

6.拖拽你剛才插入的該transaction scope活動下面的makeDeposit1活動,把它放到該transaction scope活動中以便讓makeDeposit1 Code活動在事務中執行。

備注:隨時檢查MakeDeposit方法中包含的代碼,它被綁定到makeDeposit1活動。你會發現這些代碼有通常的ADO.NET數據庫訪問代碼。一個有趣的事情是你可看到在該代碼中沒有發起SQL Server事務。相反,當該代碼被執行時將使用環境事務。

7.編譯整個解決方案。

8.按下F5或者從Visual Studio的“調試”菜單中選擇“啟動調試”去測試該應用程序。該賬戶應該已經設置好了。點擊B鍵進入密碼驗證界面,然後鍵入1234(PIN碼)。點擊C鍵驗證該PIN碼並進入業務選擇界面。

備注:假如該應用程序驗證PIN失敗,並且你鍵入的是正確的PIN碼,則可能是因為Woodgrove數據庫的連接字符串不正確。(我進行了錯誤處理是為了讓應用程序不會崩潰。)驗證連接字符串是正確的後再一次運行該應用程序。第5章有一些針對創建連接字符串的建議。

9.因為你為存款邏輯添加了事務,因此點擊C鍵進行一次存款。

10.輸入10去存入$100($10.00的10倍),然後點擊D鍵去啟動該業務。這個業務應該成功並且界面現在會指出該業務已成功完成。因為Woodgrove數據庫創建腳本加載了一個有$1234.56的虛擬銀行賬戶,因此現在顯示的余額為$1334.56。注意你能從應用程序的左下角看到該余額。點擊C鍵回到初始界面。

11.現在我們來強制讓該業務執行失敗。Deposit存儲過程帶有一個會引起該存儲過程返回一個錯誤的參數值。選擇“Fore Transactional error”多選框會為Deposit(存儲過程)指定一個產生錯誤的值。因此點擊B鍵再一次進入PIN驗證界面,然後輸入1234,點擊C鍵進入銀行業務選擇界面。

12.再一次點擊C鍵進行存款,然後輸入10再去存入$100,但是這次在點擊D鍵之前選中“Fore Transactional error”多選框。

13.點擊D鍵後,應用程序會顯示業務執行失敗,但要注意余額。它顯示當前余額仍然是$1334.56,這是該事務執行前的余額。成功的事務(步驟10)和失敗的事務(步驟12)兩者都由TransactionScope活動處理,它是在你第5步放進工作流中的。

這非常強大!通過包括一個單一的WF活動,我們獲得了在數據庫更新之上的自動的事務(XA-style)控制能力。執行一個補償事務也能一樣容易嗎?碰巧,它需要更多的工作,但是把一個補償事務添加到你的工作流中仍然不難。

向你的工作流中添加補償事務

1.打開WorkflowATM解決方案,在工作流視圖設計器中再次打開Workflow1.cs文件。找到WithdrawState活動,然後雙擊CmdPress5活動。這會打開CmdPressed5活動進行編輯,一旦它被打開後,你會在工作流的左邊看到makeWithdrawal1 Code活動。

2.和你之前處理事務的工作類似,從Visual Studio的工具箱中拖拽一個CompensatableTransactionScope活動,把它放到makeWithdrawal1活動和它上面的ifElseBranchActivity13標題的中間。

3.從compensatableTransactionScope1活動的下面拖拽makeWithdrawal1 Code活動,把它放進事務的范圍(transaction scope)之內。MakeWithdrawal方法被綁定到makeWithdrawal1活動,現在它將在一個環境事務中執行它的ADO.NET代碼,就像存款(deposit)活動做的一樣。

4.但是,和存款功能不同,你必須提供補償邏輯。傳統意義上業務不能回退。相反,你需要訪問compensatable TransactionScope1補償處理程序並添加你自己的補償功能。為此,把鼠標移到compensatable TransactonScope1的標題下面的智能標簽上,一旦點擊它則在它下面將彈出和該活動相關的視圖菜單。

5.點擊最下面的“查看補償處理程序”菜單,激活補償處理程序視圖。

6.從Visual Studio拖拽一個Code活動並把它放到該補償處理活動中。

7.在該Code活動的ExecuteCode屬性中輸入CompensateWithdrawal。Visual Studio會在你的源代碼中自動插入該方法並為你切換到代碼編輯器界面下。

8.在為你剛剛插入的CompensateWithdrawal方法中添加下面的代碼:

CompensateWithdrawal
// Here, you "undo" whatever was done that did succeed. The
// code that withdrew the money from the account was actually
// successful (there is no catch block), so this compensation
// is forced. Therefore, we're safe in depositing the amount
// that was withdrawn. Note we can't use MakeDeposit since
// we require a SQL Server transaction and this method is
// called within the compensation handler (i.e., We can't drop
// a TransactionScope activity into the compensation to kick
// off the SQL Server transaction). We'll create the transaction
// ourselves here.
//
// Craft your compensation handlers carefully. Be sure you know
// what was successfully accomplished so that you can undo it
// correctly.
string connString =
   ConfigurationManager.ConnectionStrings["BankingDatabase"].
                        ConnectionString;

if (!String.IsNullOrEmpty(connString))
{
  SqlConnection conn = null;
  SqlTransaction trans = null;
  try
  {
    // Create the connection
    conn = new SqlConnection(connString);

    // Create the command object
    SqlCommand cmd = new SqlCommand("dbo.Deposit", conn);
    cmd.CommandType = CommandType.StoredProcedure;

    // Create and add parameters
    SqlParameter parm = new SqlParameter("@AccountNo", SqlDbType.Int);
    parm.Direction = ParameterDirection.Input;
    parm.Value = _account;
    cmd.Parameters.Add(parm);
    parm = new SqlParameter("@ThrowError", SqlDbType.SmallInt);
    parm.Direction = ParameterDirection.Input;
    parm.Value = 0;
    cmd.Parameters.Add(parm);
    parm = new SqlParameter("@Amount", SqlDbType.Money);
    parm.Direction = ParameterDirection.Input;
    parm.Value = CurrentMoneyValue;
    cmd.Parameters.Add(parm);
    SqlParameter outParm =
            new SqlParameter("@Balance", SqlDbType.Money);
    outParm.Direction = ParameterDirection.Output;
    outParm.Value = 0; // initialize to invalid
    cmd.Parameters.Add(outParm);

    // Open the connection
    conn.Open();

    // Initiate the SQL transaction
    trans = conn.BeginTransaction();
    cmd.Transaction = trans;

    // Execute the command
    cmd.ExecuteNonQuery();

    // Commit the SQL transaction
    trans.Commit();

    // Pull the output parameter and examine
    CurrentBalance = (decimal)outParm.Value;
  }
  catch
  {
    // Rollback Note we could issue a workflow exception here
    // or continue trying to compensate (by writing a transactional
    // service). It would be wise to notify someone
    if (trans != null) trans.Rollback();
  }
  finally
  {
    // Close the connection
    if (conn != null) conn.Close();
  }
}

9.把補償代碼添加到你的工作流中後,回到工作流視圖設計器上來。遵循你剛剛插入Code活動的步驟,拖拽一個自定義的Failed活動到補償處理程序中。注意當你回到工作流視圖設計器上的時候,Visual Studio可能會重新進行調整並把你帶回到頂級狀態活動布局界面上來。假如這樣的話,可簡單地在WithdrawState中再一次雙擊CmdPressed5活動來訪問compensatableTransactionScope1活動,並再一次從它的智能標簽中選擇補償處理程序視圖。

10.在Failed活動的error屬性中輸入“Unable to withdraw funds”。

11.在剛才你插入進你工作流的Failed活動的下面,拖入一個SetState活動。在它的TargetStateName屬性中選擇CompletedState。

12.按下F5或者選擇“調試”菜單中的“啟動調試”來再次測試該應用程序。在該應用程序開始執行後,點擊B鍵進入PIN驗證界面,然後輸入1234(PIN碼)。點擊C鍵對PIN進行驗證並進入業務選擇界面。

13.點擊D鍵進行取款。

14.輸入10取$100($10.00的十倍),然後點擊D鍵開始交易。假如沒有你的干預的話,該業務應該會成功完成,並且屏幕現在會告訴你業務完成了,賬戶余額是$1234.56。

15.現在我們再次讓該業務執行失敗。點擊C鍵重新啟動ATM,然後點擊B鍵再次進入PIN驗證界面。輸入1234,然後點擊C鍵進入銀行業務選擇界面。

16.輸入10再次取出$100,並且選中“Fore Transactional error”復選框。然後點擊D鍵啟動該業務。

17.在你點擊D鍵後,應用程序會指出業務執行失敗並顯示當前的賬戶余額($1234.56)。由於在MakeWithdrawal方法中沒有catch語句,因此我們知道進行了取款。(假如不是如此的話,應用程序會由於一個重大的錯誤而被終止。)這意味著該賬戶實際上是取出了$100,並且補償功能也執行了,它為該賬戶又添加回$100。  注意:也有其它辦法看到賬戶的取款和存款。你可以在補償功能模塊中設置一個斷點,或者假如你熟悉SQL Server Profiler並且你使用的是完整零售版本的SQL Server話,你甚至可以執行SQL Server Profiler進行查看。

出處 : http://www.cnblogs.com/gyche/archive/2008/11/14/1301480.html

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