WF 从入门到精通(第十五章):工作流和事务

学习完本章,你将掌握:

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 事务的隔离级别

image.png

当你拖拽一个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 的状态图


image.png

该应用程序的用户界面如图15-2 所示。这是应用程序的初始状态,插入银行卡之前ATM

的状态都和这近似。当然,该示例不能处理真实的银行卡,因此点击B 键将把用户界面(和

应用程序状态)切换到PIN 验证状态(如图15-3 所示)。

图15-2 WorkflowATM 的初始用户接口

image.png

图15-3 WorkflowATM 的PIN 验证用户界面

image.png

你使用按键输入正确的PIN。一旦输入了四个数字代码,你可点击C 键来开始对数据库

进行查询以验证该PIN。假如PIN 经过验证(注意帐号在左 下角,该PIN 码必须是该帐号的),

用户界面便切换到如图15-4 所示的操作选择状态。这里你可选择从你的账户中存款或取款。

图15-4 WorkflowATM 的操作选择用户界面

image.png

对于存款和取款的应用程序用户界面是相似的,因此我只展示了存款的用户界面,如图

15-5 所示。你可再次使用按键输入金额然后点击一个命令键:D 键来进行存款或取款,或者

E 键来取消该交易。

图15-5 WorkflowATM 的存款交易用户界面

image.png

假如交易成功,你会看到如图15-6 所示的界面。假如失败,你将看到如图15-7 所示的

错误屏幕。这都没关系,点击C 键重新开始该工作流。

图15-6 WorkflowATM 交易成功的用户界面

image.png

图15-7 WorkflowATM 交易失败的用户界面

image.png

该应用程序需要一个数据库来完整地对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 有些相像吗?

image.png

4.为插入XA 类型的事务,首先双击DepositState 活动中的CmdPressed4 EventDriven

活动。

image.png

5.仔细看看左边,你会看到名称为makeDeposit1 的Code 活动。从工具箱中拖拽一个

TransactionScope 活动到makeDeposit1 活动和该Code 活动上面的ifElseBranchActivity11

标题之间。

image.png

6.拖拽你刚才插入的该transaction scope 活动下面的makeDeposit1 活动,把它放到

该transaction scope 活动中以便让makeDeposit1 Code 活动在事务中执行。

image.png

备注:随时检查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 活动。

image.png

2.和你之前处理事务的工作类似,从Visual Studio 的工具箱中拖拽一个

CompensatableTransactionScope 活动,把它放到makeWithdrawal1 活动和它上面的

ifElseBranchActivity13 标题的中间。

image.png

3.从compensatableTransactionScope1 活动的下面拖拽makeWithdrawal1 Code 活动,

把它放进事务的范围(transaction scope)之内。MakeWithdrawal 方法被绑定到

makeWithdrawal1 活动,现在它将在一个环境事务中执行它的ADO.NET 代码,就像存款

(deposit)活动做的一样。

image.png

4.但是,和存款功能不同,你必须提供补偿逻辑。传统意义上业务不能回退。相反,你

需要访问compensatable TransactionScope1 补偿处理程序并添加你自己的补偿功能。为此,

把鼠标移到compensatable TransactonScope1 的标题下面的智能标签上,一旦点击它则在它

下面将弹出和该活动相关的视图菜单。

image.png

5.点击最下面的“查看补偿处理程序”菜单,激活补偿处理程序视图。

image.png

6.从Visual Studio 拖拽一个Code 活动并把它放到该补偿处理活动中。

image.png

7.在该Code 活动的ExecuteCode 属性中输入CompensateWithdrawal。Visual Studio

会在你的源代码中自动插入该方法并为你切换到代码编辑器界面下。

image.png

8.在为你刚刚插入的CompensateWithdrawal 方法中添加下面的代码:

CompensateWithdrawal

9.把补偿代码添加到你的工作流中后,回到工作流视图设计器上来。遵循你刚刚插入

Code 活动的步骤,拖拽一个自定义的Failed 活动到补偿处理程序中。注意当你回到工作流

视图设计器上的时候,Visual Studio 可能会重新进行调整并把你带回到顶级状态活动布局

界面上来。假如这样的话,可简单地在WithdrawState 中再一次双击CmdPressed5 活动来访

问compensatableTransactionScope1 活动,并再一次从它的智能标签中选择补偿处理程序视

图。

image.png

10.在Failed 活动的error 属性中输入“Unable to withdraw funds”。

image.png

11.在刚才你插入进你工作流的Failed 活动的下面,拖入一个SetState 活动。在它的

TargetStateName 属性中选择CompletedState。

image.png

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://files.cnblogs.com/gyche/WF%20Step%20by%20Step/Chapter15.rar


--------------------- 

作者:hackpig

来源:www.skcircle.com

版权声明:本文为博主原创文章,转载请附上博文链接!


本文出自勇哥的网站《少有人走的路》wwww.skcircle.com,转载请注明出处!讨论可扫码加群:

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

会员中心
搜索
«    2024年4月    »
1234567
891011121314
15161718192021
22232425262728
2930
网站分类
标签列表
最新留言
    热门文章 | 热评文章 | 随机文章
文章归档
友情链接
  • 订阅本站的 RSS 2.0 新闻聚合
  • 扫描加本站机器视觉QQ群,验证答案为:halcon勇哥的机器视觉
  • 点击查阅微信群二维码
  • 扫描加勇哥的非标自动化群,验证答案:C#/C++/VB勇哥的非标自动化群
  • 扫描加站长微信:站长微信:abc496103864
  • 扫描加站长QQ:
  • 扫描赞赏本站:
  • 留言板:

Powered By Z-BlogPHP 1.7.2

Copyright Your skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864