在本次实验中,你将会和一个银行的程序打交道。通过这个程序,你将会看到如何加入transaction。首先你需要创建一个数据库。打开Transactions文件夹,使用Bank.sql脚本创建数据库。A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
打开Bank.sln解决方案。想往常一样,解决方案中包含了服务端和客户端的程序。我们先来看服务端。服务端包含了AccountService和AccountManger两个服务。AccountService实现了IAccount接口,用于完成借贷功能:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
[ServiceContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
interface
IAccountA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  [OperationContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
void Credit(int accountNumber,decimal
amount);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  [OperationContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
void Debit(int accountNumber,decimal
amount);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
[ServiceBehavior(InstanceContextMode
= InstanceContextMode.PerCall)]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
class
AccountService : IAccountA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
public void Credit(int accountNumber,decimal
amount)A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      BankAccountsTableAdapter adapter
= new
BankAccountsTableAdapter();A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      BankDataSet.BankAccountsDataTable accounts
=
adapter.GetData();A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      BankDataSet.BankAccountsRow account
=
accounts.FindByNumber(accountNumber);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      account.Balance
+=
amount;A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      adapter.Update(accounts);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  }
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
public void Debit(int accountNumber,decimal amount)A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      BankAccountsTableAdapter adapter
= new
BankAccountsTableAdapter();A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      BankDataSet.BankAccountsDataTable accounts
=
adapter.GetData();A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      BankDataSet.BankAccountsRow account
=
accounts.FindByNumber(accountNumber);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
     
if(account.Balance >=
amount)A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
     
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
        account.Balance
-=
amount;A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      }
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
     
elseA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
     
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
       
throw new InvalidOperationException("Debit amount is greater than balance in account #" +
accountNumber);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      }
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      adapter.Update(accounts);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  }
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
代码不是很复杂,这里就不讲解了。A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
配置文件对AccountService暴露了两个endpoint,一个使用TCP、一个使用HTTP:
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<service name = "AccountService">A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<endpointA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
address  = "net.tcp://localhost001/AccountService/"
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  binding 
= "netTcpBinding"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  contract
= "IAccount"/>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<endpointA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
address  = "http://localhost002/AccountService"
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  binding 
= "wsHttpBinding"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  contract
= "IAccount"/>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
</service>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
AccountManger类实现了IAccountManger接口,用来查询帐户:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
[DataContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
class
AccountA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  [DataMember]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
public string
Name;A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  [DataMember]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
public decimal
Balance;A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  [DataMember]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
public int
Number;A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
[ServiceContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
interface IAccountManagerA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  [OperationContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  Account[] GetAccounts();A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
我们再来看客户端。客户端使用了一个winform程序来模拟银行的操作:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
点击Transfer按钮将会做转帐的操作。在代码上,client端会对第一个帐户创建一个TCP代理类来完成贷款动作。接下来会对第二个帐户创建一个HTTP代理类来完成借款动作。完成转帐动作后会重新获取帐户信息显示到grid中。
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
using(AccountClient account1 = new AccountClient("TCP"))A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
using(AccountClient account2 = new AccountClient("HTTP"
))A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  account1.Credit(destinationAccount,amount);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
  account2.Debit(sourceAccount,amount);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
目前client端没有任何事务控制,也没有错误处理。程序的架构如下图所示:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
在没有事务控制的情况下,如果帐户号码是正确的,那么不会出现任何问题。比如我们将100元从帐户123转到456。但是如果帐户输入错误了,那么就会有问题了。比如我们将100元从帐户777转到456。点击Transfer后我们会收到异常(因为程序没有错误处理),不用管这个错误,刷新grid后我们会发现456帐户上多了100元!A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
接下来我们就加入事务控制吧。
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
加入事务A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
为AccountService加入operation behavior:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
class AccountService : IAccountA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    [OperationBehavior(TransactionScopeRequired
= true
)]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
public void Credit(int accountNumber, decimal
amount)A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
{}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    [OperationBehavior(TransactionScopeRequired
= true)]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
public void Debit(int accountNumber, decimal
amount)A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
{}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
为了让事务能传播到服务端,我们需要在服务端加上TransactionFlow的属性。同样也需要在client端的contract定义上加入相同的属性:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
[ServiceContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
interface
IAccountA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    [OperationContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    [TransactionFlow(TransactionFlowOption.Allowed)]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
void Credit(int accountNumber, decimal
amount);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    [OperationContract]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    [TransactionFlow(TransactionFlowOption.Allowed)]A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
void Debit(int accountNumber, decimal
amount);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
同时还需要在配置文件中对bingding加入允许事务的属性,服务端:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<services>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<service name = "AccountService">A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
<endpointA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
     
address  = "net.tcp://localhost001/AccountService/"
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      binding 
= "netTcpBinding"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      contract
= "IAccount"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    bindingConfiguration
="TransactionalTCP"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
/>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
<endpointA˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
     
address  = "http://localhost002/AccountService"
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      binding 
= "wsHttpBinding"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
      contract
= "IAccount"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    bindingConfiguration
="TransactionalHTTP"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
/>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
</service>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
</services>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<bindings>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
<netTcpBinding>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
<binding name="TransactionalTCP" transactionFlow="true"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
/>
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
</netTcpBinding>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
<wsHttpBinding>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
   
<binding name="TransactionalHTTP" transactionFlow="true"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
/>
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
</wsHttpBinding>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
</bindings>
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
客户端:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<client>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<endpoint name = "TCP"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    address 
= "net.tcp://localhost001/AccountService/"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    binding 
= "netTcpBinding"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    contract
= "IAccount"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    bindingConfiguration
="TransactionalTCP"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
/>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<endpoint name = "HTTP"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    address 
= "http://localhost:8002/AccountService/"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    binding 
= "wsHttpBinding"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    contract
= "IAccount"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    bindingConfiguration
="TransactionalHTTP"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
/>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
</client>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<bindings>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<netTcpBinding>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
<binding name="TransactionalTCP" transactionFlow="true"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
/>
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
</netTcpBinding>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
<wsHttpBinding>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
 
<binding name="TransactionalHTTP" transactionFlow="true"A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
/>
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
</wsHttpBinding>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
</bindings>A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
对client项目添加对System.Transactions.dll的引用。打开BankClientForm.cs文件,添加using语句:using System.Transactions。A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
下面,我们将在client端使用transaction scope将它调用的两个服务包到一个事务中:A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
使用TrasactionScope来包住两个调用:
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
using(TransactionScope scope = new TransactionScope())A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
using (AccountClient account1 = new AccountClient("TCP"
))A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
using (AccountClient account2 = new AccountClient("HTTP"
))A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
{A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    account1.Credit(destinationAccount, amount);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    account2.Debit(sourceAccount, amount);A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
    scope.Complete();A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
}
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ
重复我们一开始的实验,你会发现帐户不正确时所有操作都会进行回滚。A˜Pľð”ûwww.netcsharp.cnp;2€ÍA›žtÁ