yunkunyang

在本次实验中,你将会和一个银行的程序打交道。通过这个程序,你将会看到如何加入transaction。首先你需要创建一个数据库。打开Transactions文件夹,使用Bank.sql脚本创建数据库。

打开Bank.sln解决方案。想往常一样,解决方案中包含了服务端和客户端的程序。我们先来看服务端。服务端包含了AccountService和AccountManger两个服务。AccountService实现了IAccount接口,用于完成借贷功能:

[ServiceContract]
interface
 IAccount
{
   [OperationContract]
 
void Credit(int accountNumber,decimal
 amount);

   [OperationContract]
   
void Debit(int accountNumber,decimal
 amount);
}

[ServiceBehavior(InstanceContextMode 
= InstanceContextMode.PerCall)]
class
 AccountService : IAccount
{
   
public void Credit(int accountNumber,decimal
 amount)
 
{
      BankAccountsTableAdapter adapter 
= new
 BankAccountsTableAdapter();
      BankDataSet.BankAccountsDataTable accounts 
=
 adapter.GetData();

      BankDataSet.BankAccountsRow account 
=
 accounts.FindByNumber(accountNumber);
      account.Balance 
+=
 amount;
      adapter.Update(accounts);
   }

 
public void Debit(int accountNumber,decimal amount)
 
{
      BankAccountsTableAdapter adapter 
= new
 BankAccountsTableAdapter();
      BankDataSet.BankAccountsDataTable accounts 
=
 adapter.GetData();

      BankDataSet.BankAccountsRow account 
=
 accounts.FindByNumber(accountNumber);

      
if(account.Balance >=
 amount)
      
{
         account.Balance 
-=
 amount;
      }

      
else
      
{
         
throw new InvalidOperationException("Debit amount is greater than balance in account #" +
 accountNumber);
      }

      adapter.Update(accounts);
   }

}


代码不是很复杂,这里就不讲解了。
配置文件对AccountService暴露了两个endpoint,一个使用TCP、一个使用HTTP:

<service name = "AccountService">
<endpoint
   
address  = "net.tcp://localhost:8001/AccountService/"

   binding  
= "netTcpBinding"
   contract 
= "IAccount"
/>
<endpoint
   
address  = "http://localhost:8002/AccountService"

   binding  
= "wsHttpBinding"
   contract 
= "IAccount"
/>
</service>

AccountManger类实现了IAccountManger接口,用来查询帐户:

[DataContract]
class
 Account
{
   [DataMember]
   
public string
 Name;

   [DataMember]
   
public decimal
 Balance;

   [DataMember]
   
public int
 Number;
}


[ServiceContract]
interface IAccountManager
{
   [OperationContract]
   Account[] GetAccounts();
}

我们再来看客户端。客户端使用了一个winform程序来模拟银行的操作:
 
点击Transfer按钮将会做转帐的操作。在代码上,client端会对第一个帐户创建一个TCP代理类来完成贷款动作。接下来会对第二个帐户创建一个HTTP代理类来完成借款动作。完成转帐动作后会重新获取帐户信息显示到grid中。

using(AccountClient account1 = new AccountClient("TCP"))
using(AccountClient account2 = new AccountClient("HTTP"
))
{
   account1.Credit(destinationAccount,amount);
   account2.Debit(sourceAccount,amount);
}

目前client端没有任何事务控制,也没有错误处理。程序的架构如下图所示:
 

在没有事务控制的情况下,如果帐户号码是正确的,那么不会出现任何问题。比如我们将100元从帐户123转到456。但是如果帐户输入错误了,那么就会有问题了。比如我们将100元从帐户777转到456。点击Transfer后我们会收到异常(因为程序没有错误处理),不用管这个错误,刷新grid后我们会发现456帐户上多了100元!
 
接下来我们就加入事务控制吧。

加入事务
为AccountService加入operation behavior:

class AccountService : IAccount
{
    [OperationBehavior(TransactionScopeRequired 
= true
)]
    
public void Credit(int accountNumber, decimal
 amount)
    
{……}


    [OperationBehavior(TransactionScopeRequired 
= true)]
    
public void Debit(int accountNumber, decimal
 amount)
    
{……}

}


为了让事务能传播到服务端,我们需要在服务端加上TransactionFlow的属性。同样也需要在client端的contract定义上加入相同的属性:

[ServiceContract]
interface
 IAccount
{
    [OperationContract]
    [TransactionFlow(TransactionFlowOption.Allowed)]
    
void Credit(int accountNumber, decimal
 amount);

    [OperationContract]
    [TransactionFlow(TransactionFlowOption.Allowed)]
    
void Debit(int accountNumber, decimal
 amount);
}


同时还需要在配置文件中对bingding加入允许事务的属性,服务端:

<services>
 
<service name = "AccountService">
    
<endpoint
       
address  = "net.tcp://localhost:8001/AccountService/"

       binding  
= "netTcpBinding"
       contract 
= "IAccount"
    bindingConfiguration
="TransactionalTCP"
    
/>
    
<endpoint
       
address  = "http://localhost:8002/AccountService"

       binding  
= "wsHttpBinding"
       contract 
= "IAccount"
    bindingConfiguration
="TransactionalHTTP"
    
/>
 
</service>
 ……
</services>
<bindings>
   
<netTcpBinding>
    
<binding name="TransactionalTCP" transactionFlow="true" />
   
</netTcpBinding>
   
<wsHttpBinding>
    
<binding name="TransactionalHTTP" transactionFlow="true" />
   
</wsHttpBinding>
</bindings>


客户端:

<client>
 
<endpoint name = "TCP"
    address  
= "net.tcp://localhost:8001/AccountService/"
    binding  
= "netTcpBinding"
    contract 
= "IAccount"
     bindingConfiguration
="TransactionalTCP"
 
/>
 
<endpoint name = "HTTP"
    address  
= "http://localhost:8002/AccountService/"
    binding  
= "wsHttpBinding"
    contract 
= "IAccount"
     bindingConfiguration
="TransactionalHTTP"
 
/>
……
</client>
<bindings>
 
<netTcpBinding>
  
<binding name="TransactionalTCP" transactionFlow="true" />
 
</netTcpBinding>
 
<wsHttpBinding>
  
<binding name="TransactionalHTTP" transactionFlow="true" />
 
</wsHttpBinding>
</bindings>

对client项目添加对System.Transactions.dll的引用。打开BankClientForm.cs文件,添加using语句:using System.Transactions。
下面,我们将在client端使用transaction scope将它调用的两个服务包到一个事务中:
 
使用TrasactionScope来包住两个调用:

using(TransactionScope scope = new TransactionScope())
using (AccountClient account1 = new AccountClient("TCP"
))
using (AccountClient account2 = new AccountClient("HTTP"
))
{
    account1.Credit(destinationAccount, amount);
    account2.Debit(sourceAccount, amount);
    scope.Complete();
}


重复我们一开始的实验,你会发现帐户不正确时所有操作都会进行回滚。

 

分类:

技术点:

相关文章: