在本次实验中,你将会和一个银行的程序打交道。通过这个程序,你将会看到如何加入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(); }
重复我们一开始的实验,你会发现帐户不正确时所有操作都会进行回滚。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。
发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/111032.html 原文链接:https://javaforall.net