[置顶]坚持学习WF文章索引
在关系型数据库中支持事务已经有10几年了,在关系型数据库中如果你要有一组的更新要执行,你会先执行第一组更新,在执行第二组更新。。。。。。,如果最后所有的更新都成功,我们就将事务提交完成,如果中间发生了错误,我们会让事务回滚,到最初始的位置。我们使用事务来解决数据的完整性、一致性等等问题。
在.NET中我们可以使用SqlConnection和SqlTransaction来对关系型数据库管理事务,SqlTransaction包含Commit和Rollback方法来执行和回滚事务。我们也可以添加TransactionAttribute或AutoCompleteAttribute属性给一个企业服务组件(继承自ServicedComponent类并注册为企业服务),使用这个方式,会自动的以事务的方式来执行。在.NET2.0中又有了新的事务框架System.Transactions 。使用该命名空间包含的类可以编写自己的事务应用程序和资源管理器。具体地说,可以创建和参与(与一个或多个参与者)本地或分布式事务,如其中的Transaction和TransactionScope类。
在WF中提供了TransactionScopeActivity活动用来支持事务,当该活动执行时一个System.Transactions.Transaction实例就被创建了,如果TransactionScopeActivity中的子活动有一个有异常,就会执行回滚操作。
TransactionScopeActivity活动有一个TransactionOptions属性(WorkflowTransactionOptions的实例),包含两个属性来控制事务,其中一个是IsolationLevel,用来指定事务的隔离级别,具体如下:
成员名称 | 说明 |
Serializable | 可以在事务期间读取可变数据,但是不可以修改,也不可以添加任何新数据 |
RepeatableRead | 可以在事务期间读取可变数据,但是不可以修改。可以在事务期间添加新数据。 |
ReadCommitted | 不可以在事务期间读取可变数据,但是可以修改它。 |
ReadUncommitted | 可以在事务期间读取和修改可变数据。 |
Snapshot | 可以读取可变数据。在事务修改数据之前,它验证在它最初读取数据之后另一个事务是否更改过这些数据。如果数据已被更新,则会引发错误。这样使事务可获取先前提交的数据值。 |
Chaos | 无法覆盖隔离级别更高的事务中的挂起的更改。 |
Unspecified | 正在使用与指定隔离级别不同的隔离级别,但是无法确定该级别。如果设置了此值,则会引发异常。 |
另一个是TimeoutDuration用于获取或设置表示事务超时期限的 TimeSpan。
当执行的工作流包含TransactionScopeActivity活动时,持久化服务也是必不可少的。因为当TransactionScopeActivity结束的时候会自动做持久化存储。除了持久化外还需要WorkflowCommitWorkBatchService,如果你不手动添加系统会默认给你添加一个默认的即DefaultWorkflowCommitBatchService。
我们在谈论的事务的时候举的最多的一个例子就是转账问题,下面我们也来实现一个转账的例子来说明WF中事务的使用,我们从A帐户转账到B帐户,我们首先要从A帐户中减去转账的数额,然后在将该数额加到B帐户上。如果前一个步骤完成了,而后一个步骤失败了,这个时候我们就需要事务来进行回滚操作。在做这个例子之前我们先做些准备工作,首先要建立持久化数据库,然后我们在建立一个数据库用来存储帐户余额的情况,一张表account有三个字段accountId,description,balance。下面先自定义一个转账活动AccountAdjustmentActivity.cs,代码如下:
public partial class AccountAdjustmentActivity : Activity
{
public static DependencyProperty AmountProperty
= System.Workflow.ComponentModel.DependencyProperty.Register(
"Amount", typeof(Decimal), typeof(AccountAdjustmentActivity));
[Description("The amount of the balance adjustment")]
[Category("ProWorkflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Decimal Amount
{
get
{
return ((Decimal)(base.GetValue(
AccountAdjustmentActivity.AmountProperty)));
}
set
{
base.SetValue(AccountAdjustmentActivity.AmountProperty, value);
}
}
public static DependencyProperty AccountIdProperty
= System.Workflow.ComponentModel.DependencyProperty.Register(
"AccountId", typeof(Int32), typeof(AccountAdjustmentActivity));
[Description("Identifies the account")]
[Category("ProWorkflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Int32 AccountId
{
get
{
return ((Int32)(base.GetValue(
AccountAdjustmentActivity.AccountIdProperty)));
}
set
{
base.SetValue(AccountAdjustmentActivity.AccountIdProperty, value);
}
}
public static DependencyProperty IsCreditProperty
= System.Workflow.ComponentModel.DependencyProperty.Register(
"IsCredit", typeof(Boolean), typeof(AccountAdjustmentActivity));
[Description("True if this is a credit, false for a debit")]
[Category("ProWorkflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Boolean IsCredit
{
get
{
return ((Boolean)(base.GetValue(
AccountAdjustmentActivity.IsCreditProperty)));
}
set
{
base.SetValue(AccountAdjustmentActivity.IsCreditProperty, value);
}
}
protected override ActivityExecutionStatus Execute(
ActivityExecutionContext executionContext)
{
using (SqlConnection connection = new SqlConnection(
ConfigurationManager.ConnectionStrings
["ProWorkflow"].ConnectionString))
{
connection.Open();
if (!IsCredit)
{
Decimal currentBal = GetCurrentBalance(
connection, AccountId);
if (currentBal < Amount)
{
throw new ArgumentException(
"余额不足,无法处理");
}
}
UpdateBalance(connection, AccountId, Amount, IsCredit);
connection.Close();
}
return base.Execute(executionContext);
}
private Decimal GetCurrentBalance(
SqlConnection connection, Int32 accountId)
{
Decimal balance = 0;
String sql =
@"select balance from account where accountId = @AccountId";
SqlCommand command = new SqlCommand(sql);
SqlParameter p = new SqlParameter("@AccountId", accountId);
command.Parameters.Add(p);
command.Connection = connection;
Object result = command.ExecuteScalar();
if (result != null)
{
balance = (Decimal)result;
}
return balance;
}
private void UpdateBalance(SqlConnection connection,
Int32 accountId, Decimal adjAmount, Boolean isCredit)
{
String sql;
if (isCredit)
{
sql =
@"update account set balance = balance + @AdjAmount
where accountId = @AccountId";
}
else
{
sql =
@"update account set balance = balance - @AdjAmount
where accountId = @AccountId";
}
SqlCommand command = new SqlCommand(sql);
SqlParameter p = new SqlParameter("@AccountId", accountId);
command.Parameters.Add(p);
p = new SqlParameter("@AdjAmount", adjAmount);
command.Parameters.Add(p);
command.Connection = connection;
command.ExecuteNonQuery();
}
}
增加App.config配置文件,代码如下:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<connectionStrings>
<!--connection string for workflow persistence database-->
<add name="WorkflowPersistence" connectionString=
"Integrated Security=SSPI;Initial Catalog=WorkflowPersistence;
Data Source=localhost\SQLEXPRESS;Integrated Security=SSPI" />
<!--connection string for the testing database-->
<add name="ProWorkflow" connectionString=
"Integrated Security=SSPI;Initial Catalog=ProWorkflow;
Data Source=localhost\SQLEXPRESS;Integrated Security=SSPI" />
</connectionStrings>
</configuration>
实现工作流(AccountTransferWorkflow.cs)如下图:
我们要将creditActivity和debitActivity的AccountId和Amount属性绑定到工作流的三个依赖属性中,另外creditActivity的IsCredit属性设置为True,debitActivity的IsCredit属性设置为False,工作流代码如下:
public sealed partial class AccountTransferWorkflow: SequentialWorkflowActivity
{
public static DependencyProperty AmountProperty
= System.Workflow.ComponentModel.DependencyProperty.Register(
"Amount", typeof(Decimal), typeof(AccountTransferWorkflow));
[Description("The amount of the balance adjustment")]
[Category("ProWorkflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Decimal Amount
{
get
{
return ((Decimal)(base.GetValue(
AccountTransferWorkflow.AmountProperty)));
}
set
{
base.SetValue(AccountTransferWorkflow.AmountProperty, value);
}
}
public static DependencyProperty FromAccountIdProperty
= System.Workflow.ComponentModel.DependencyProperty.Register(
"FromAccountId", typeof(Int32), typeof(AccountTransferWorkflow));
[Description("Identifies the account")]
[Category("ProWorkflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Int32 FromAccountId
{
get
{
return ((Int32)(base.GetValue(
AccountTransferWorkflow.FromAccountIdProperty)));
}
set
{
base.SetValue(AccountTransferWorkflow.FromAccountIdProperty, value);
}
}
public static DependencyProperty ToAccountIdProperty
= System.Workflow.ComponentModel.DependencyProperty.Register(
"ToAccountId", typeof(Int32), typeof(AccountTransferWorkflow));
[Description("Identifies the account")]
[Category("ProWorkflow")]
[Browsable(true)]
[DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)]
public Int32 ToAccountId
{
get
{
return ((Int32)(base.GetValue(
AccountTransferWorkflow.ToAccountIdProperty)));
}
set
{
base.SetValue(AccountTransferWorkflow.ToAccountIdProperty, value);
}
}
public AccountTransferWorkflow()
{
InitializeComponent();
}
}
实现宿主程序
我们在宿主中进行了两次转账操作,两个帐户的初始化余额都是100,第一次是成功的,第二次由于转账的数额大于帐户的余额,发生了异常,这个时候转账不成功,我们进行回滚操作,宿主程序的主要代码如下:
public class AccountTransferTest
{
public static void Run()
{
using (WorkflowRuntimeManager manager
= new WorkflowRuntimeManager(new WorkflowRuntime()))
{
AddServices(manager.WorkflowRuntime);
manager.WorkflowRuntime.StartRuntime();
Console.WriteLine("第一次转账开始");
DisplayTestData(1001, 2002, "转账前");
Dictionary<String, Object> wfArguments = new Dictionary<string, object>();
wfArguments.Add("FromAccountId", 1001);
wfArguments.Add("ToAccountId", 2002);
wfArguments.Add("Amount", (Decimal)25.00);
WorkflowInstanceWrapper instance = manager.StartWorkflow(
typeof(CaryTransactionAP.AccountTransferWorkflow), wfArguments);
manager.WaitAll(5000);
if (instance.Exception != null)
{
Console.WriteLine("EXCEPTION: {0}",instance.Exception.Message);
}
DisplayTestData(1001, 2002, "转帐后");
Console.WriteLine("第一次转账结束\n\r");
Console.WriteLine("第二次转账开始");
DisplayTestData(1001, 2002, "转账前");
wfArguments = new Dictionary<string, object>();
wfArguments.Add("FromAccountId", 1001);
wfArguments.Add("ToAccountId", 2002);
wfArguments.Add("Amount", (Decimal)200.00);
instance = manager.StartWorkflow(
typeof(CaryTransactionAP.AccountTransferWorkflow), wfArguments);
manager.WaitAll(5000);
if (instance.Exception != null)
{
Console.WriteLine("发生异常: {0}", instance.Exception.Message);
}
DisplayTestData(1001, 2002, "转账后");
Console.WriteLine("第二次转账结束\n\r");
}
}
private static void AddServices(WorkflowRuntime instance) { SqlWorkflowPersistenceService persistence = new SqlWorkflowPersistenceService( ConfigurationManager.ConnectionStrings["WorkflowPersistence"].ConnectionString, true, new TimeSpan(0, 2, 0), new TimeSpan(0, 0, 5)); instance.AddService(persistence); } private static void DisplayTestData(Int32 acctId1, Int32 acctId2, String desc) { using (SqlConnection connection = new SqlConnection(ConfigurationManager.ConnectionStrings ["ProWorkflow"].ConnectionString)) { connection.Open(); Decimal balance = GetCurrentBalance(connection, acctId1); Console.WriteLine(" {0} AccountId为 {1}的金额为: {2}",desc, acctId1, balance); balance = GetCurrentBalance(connection, acctId2); Console.WriteLine(" {0} AccountId为 {1}的金额为: {2}", desc, acctId2, balance); connection.Close(); } } private static Decimal GetCurrentBalance(SqlConnection connection, Int32 accountId) { Decimal balance = 0; String sql =@"select balance from account where accountId = @AccountId"; SqlCommand command = new SqlCommand(sql); SqlParameter p = new SqlParameter("@AccountId", accountId); command.Parameters.Add(p); command.Connection = connection; Object result = command.ExecuteScalar(); if (result != null) { balance = (Decimal)result; } return balance; }
}
运行后的结果如下图: