通过上文WCF从理论到实践:事务的学习,我们了解了WCF中实现事务的一些基本常识,但WCF中的事务并不止那么简单,上文中我们欠缺了一个最重要的功能:事务投票,所谓事务投票就是一种灵活控制事务提交的方式,在上文中我们设置服务方法的TransactionAutoComplete为true,其实意味着方法在没有异常的情况下自动投赞成票,但有时我们希望当操作中只有某个数据满足具体条件的时候,才能赞同事务提交,这样上文的实现明显就不满足需求了,此时我们可以用OperationContext.Current.SetTransactionComplete();显示的进行投票。注意,WCF的事务必须在全票通过的时候才能得以提交。本文还是结合银行的例子 来演示一下事务投票,并且搭配一个漂亮的WPF客户端,可谓买一送一了,:)。
本文目的
本文适合WCF中级用户,至少需要了解事务的基本常识和简单实现,初学者可以先阅读WCF从理论到实践:事务
进一步学习WCF事务
本文中,我们要模拟的现实情境如下,搭建一个联盟银行服务自助系统,这个系统提供在各个银行之间进行自由转帐的功能,按照惯例,系统分为四个层次,分别如下:
服务契约
我们在此处定义一个IBank的服务契约,它包括四个操作契约,分别为:
服务端
服务端,由于我本地没有数据库,偷下懒,就用一个静态变量表示帐户余额了。大家在验证事务的效果的时候,应该将这里的数据存储到数据库中。整个服务端的代码为:

服务端
//======================================================================

//

// Copyright (C) 2007-2008 Jillzhang

// All rights reserved

// guid1: 4990573c-d834-47f4-a592-3543a9dca047

// guid2: 3c1ffbfe-40e7-48b5-9f83-572dbdcb3bdd

// guid3: 8dfde0dc-07a6-41a9-8fc1-89a11593aa04

// guid4: 61ab2778-e017-4552-b70f-dc772f5e56f5

// guid5: e8c89ed4-360a-417b-a5b2-8e3a459733e3

// CLR版本: 2.0.50727.1433

// 新建项输入的名称: Class1

// 机器名称: JILLZHANG-PC

// 注册组织名:

// 命名空间名称: $rootnamespace$

// 文件名: Class1

// 当前系统时间: 3/31/2008 10:32:01 PM

// 用户所在的域: jillzhang-PC

// 当前登录用户名: jillzhang

// 创建年份: 2008

//

// created by Jillzhang at 3/31/2008 10:32:01 PM

//
http://jillzhang.cnblogs.com

//

//======================================================================


using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using Jillzhang.Wcf.Transactions.Contracts;


namespace Jillzhang.Wcf.Transactions

{

[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete=false,InstanceContextMode=InstanceContextMode.PerSession)]

public class Bank:Contracts.IBank

{

static decimal _balance = 10M;


public Bank()

{


}

[OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)]

public decimal Receive(decimal money)

{

_balance += money;

Console.WriteLine("收到资金:"+money);

OperationContext.Current.SetTransactionComplete();

return _balance;

}

[OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)]

public decimal Send(decimal money)

{

if (money > _balance)

{

throw new FaultException("余额不足!");

}

_balance -= money;

Console.WriteLine("发送资金:" + money);

OperationContext.Current.SetTransactionComplete();

return _balance;

}

public decimal GetBalance()

{

return _balance;

}


public decimal SendOnServer(decimal money,string toBank)

{

WSDualHttpBinding bind = new WSDualHttpBinding();

bind.TransactionFlow = true;

BankProxy bank = new BankProxy(bind, new EndpointAddress(toBank));

using (System.Transactions.TransactionScope tx = new System.Transactions.TransactionScope())

{

if (money > _balance)

{

throw new FaultException("余额不足!");

}

_balance -= money;

Console.WriteLine("发送资金:" + money);

bank.Receive(money);

tx.Complete();

}

return _balance;

}

}

}


注意,本文的Receive和Send方法的TransactionAutoComplete设置的为false,这样就需要我们在事务包含的每个方法中均显示的进行事务投票,即调用OperationContext.Current.SetTransactionComplete();如果忽略此处,系统会出现如下的异常:

而且如果TransactionAutoComplete为false的时候,必须将InstanceContextMode设置为PerSession,并且服务契约(ServiceContract)也必须设置SessionMode为Required,否则分别 会出现如下异常:


而如果TransactionAutoComplete为True,则要求TransactionScopeRequired必须同时为True,否则会出现异常:

宿主程序
宿主程序非常简单,我们只是将服务寄宿到两个不同的进程中并且指定不同的服务地址便可,唯一值得注意的是因为服务契约需要事务支持,所以Binding的TransactionFlow也必须为True.
他们的代码分别为:
Jillzhang.Wcf.Transactions.ICBC
ICBC
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using Jillzhang.Wcf.Transactions.Contracts;


namespace Jillzhang.Wcf.Transactions.ICBC
{

class Program

{

static void Main(string[] args)

{

Uri baseAddress = new Uri("http://127.0.0.1:8654/ICBC");

using (ServiceHost host = new ServiceHost(typeof(Bank), baseAddress))

{

WSDualHttpBinding bind = new WSDualHttpBinding();

bind.TransactionFlow = true;

host.AddServiceEndpoint(typeof(IBank), bind, "");

host.Open();

Console.WriteLine("中国工商银行开始营业!");

Console.Read();

}

}

}

}


Jillzhang.Wcf.Transactions.CCB
CCB
using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel;

using Jillzhang.Wcf.Transactions.Contracts;


namespace Jillzhang.Wcf.Transactions.CCB
{

class Program

{

static void Main(string[] args)

{

Uri baseAddress = new Uri("http://127.0.0.1:8655/CCB");

using (ServiceHost host = new ServiceHost(typeof(Bank), baseAddress))

{

WSDualHttpBinding bind = new WSDualHttpBinding();

bind.TransactionFlow = true;

host.AddServiceEndpoint(typeof(IBank), bind, "");

host.Open();

Console.WriteLine("中国建设银行开始营业!");

Console.Read();

}

}

}

}



客户端
一个WPF应用程序,可以调用服务来模拟银行转帐功能,因为本文着重介绍WCF,对此不作详细赘述,以后在一起学WPF系列中会逐步加以学习,本文只给出主要的xaml文件和.cs文件
Window1.xaml

Windows.xaml
<Window x:Class="Jillzhang.Wcf.BankClient.Window1"

xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"

WindowStyle="ToolWindow" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen"

Title="银行联盟自助服务系统" ResizeMode="NoResize" Initialized="Window_Initialized">

<Window.Resources>

<Style x:Key="styleBackground">

<Setter Property="Control.Background">

<Setter.Value>

<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1">

<GradientStop Color="#50000000" Offset="0.5"/>

<GradientStop Color="#ff999999" Offset="1"/>

</LinearGradientBrush>

</Setter.Value>

</Setter>

<Setter Property="Control.Width" Value="500">

</Setter>

</Style>


本文目的
- 进一步学习WCF事务
- 顺便体验一下WPF
本文适合WCF中级用户,至少需要了解事务的基本常识和简单实现,初学者可以先阅读WCF从理论到实践:事务
进一步学习WCF事务
本文中,我们要模拟的现实情境如下,搭建一个联盟银行服务自助系统,这个系统提供在各个银行之间进行自由转帐的功能,按照惯例,系统分为四个层次,分别如下:
| 层次 | 项目 |
| 服务契约 | Jillzhang.Wcf.Transactions.Contracts |
| 服务端 | Jillzhang.Wcf.Transactions |
| 宿主程序 | Jillzhang.Wcf.Transactions.ICBC-用于模拟工商银行 Jillzhang.Wcf.Transactions.CCB-用于模拟建设银行 |
| 客户端 | Jillzhang.Wcf.BankClient – 包括一个漂亮的WPF窗体 |
服务契约
我们在此处定义一个IBank的服务契约,它包括四个操作契约,分别为:
| 操作契约 | 契约说明 |
| [OperationContract(IsTerminating=false)] [TransactionFlow(TransactionFlowOption.Allowed)] decimal Receive(decimal money); | 接受其他银行的汇款,增加本行帐户余额 |
| [OperationContract(IsTerminating = false)] [TransactionFlow(TransactionFlowOption.Allowed)] decimal Send(decimal money); | 给其他银行帐户汇款,减少本行帐户余额 |
| [OperationContract(IsTerminating = false)] decimal GetBalance(); | 获取帐户余额 |
| [OperationContract(IsTerminating=false)] decimal SendOnServer(decimal money,string toBank); | 通过一个银行接口,完成汇款操作,其中事务是在服务端进行 |
服务端
服务端,由于我本地没有数据库,偷下懒,就用一个静态变量表示帐户余额了。大家在验证事务的效果的时候,应该将这里的数据存储到数据库中。整个服务端的代码为:
//====================================================================== 
// 
// Copyright (C) 2007-2008 Jillzhang 
// All rights reserved 
// guid1: 4990573c-d834-47f4-a592-3543a9dca047 
// guid2: 3c1ffbfe-40e7-48b5-9f83-572dbdcb3bdd 
// guid3: 8dfde0dc-07a6-41a9-8fc1-89a11593aa04 
// guid4: 61ab2778-e017-4552-b70f-dc772f5e56f5 
// guid5: e8c89ed4-360a-417b-a5b2-8e3a459733e3 
// CLR版本: 2.0.50727.1433 
// 新建项输入的名称: Class1 
// 机器名称: JILLZHANG-PC 
// 注册组织名: 
// 命名空间名称: $rootnamespace$ 
// 文件名: Class1 
// 当前系统时间: 3/31/2008 10:32:01 PM 
// 用户所在的域: jillzhang-PC 
// 当前登录用户名: jillzhang 
// 创建年份: 2008 
// 
// created by Jillzhang at 3/31/2008 10:32:01 PM 
//http://jillzhang.cnblogs.com

// 
//====================================================================== 

using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.ServiceModel; 
using Jillzhang.Wcf.Transactions.Contracts; 

namespace Jillzhang.Wcf.Transactions 
{ 
[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete=false,InstanceContextMode=InstanceContextMode.PerSession)] 
public class Bank:Contracts.IBank 
{ 
static decimal _balance = 10M; 

public Bank() 
{ 

} 
[OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)] 
public decimal Receive(decimal money) 
{ 
_balance += money; 
Console.WriteLine("收到资金:"+money); 
OperationContext.Current.SetTransactionComplete(); 
return _balance; 
} 
[OperationBehavior(TransactionAutoComplete = false, TransactionScopeRequired = true)] 
public decimal Send(decimal money) 
{ 
if (money > _balance) 
{ 
throw new FaultException("余额不足!"); 
} 
_balance -= money; 
Console.WriteLine("发送资金:" + money); 
OperationContext.Current.SetTransactionComplete(); 
return _balance; 
} 
public decimal GetBalance() 
{ 
return _balance; 
} 

public decimal SendOnServer(decimal money,string toBank) 
{ 
WSDualHttpBinding bind = new WSDualHttpBinding(); 
bind.TransactionFlow = true; 
BankProxy bank = new BankProxy(bind, new EndpointAddress(toBank)); 
using (System.Transactions.TransactionScope tx = new System.Transactions.TransactionScope()) 
{ 
if (money > _balance) 
{ 
throw new FaultException("余额不足!"); 
} 
_balance -= money; 
Console.WriteLine("发送资金:" + money); 
bank.Receive(money); 
tx.Complete(); 
} 
return _balance; 
} 
} 
} 

注意,本文的Receive和Send方法的TransactionAutoComplete设置的为false,这样就需要我们在事务包含的每个方法中均显示的进行事务投票,即调用OperationContext.Current.SetTransactionComplete();如果忽略此处,系统会出现如下的异常:

而且如果TransactionAutoComplete为false的时候,必须将InstanceContextMode设置为PerSession,并且服务契约(ServiceContract)也必须设置SessionMode为Required,否则分别 会出现如下异常:


而如果TransactionAutoComplete为True,则要求TransactionScopeRequired必须同时为True,否则会出现异常:

宿主程序
宿主程序非常简单,我们只是将服务寄宿到两个不同的进程中并且指定不同的服务地址便可,唯一值得注意的是因为服务契约需要事务支持,所以Binding的TransactionFlow也必须为True.
他们的代码分别为:
Jillzhang.Wcf.Transactions.ICBC
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.ServiceModel; 
using Jillzhang.Wcf.Transactions.Contracts; 

namespace Jillzhang.Wcf.Transactions.ICBC
{ 
class Program 
{ 
static void Main(string[] args) 
{ 
Uri baseAddress = new Uri("http://127.0.0.1:8654/ICBC"); 
using (ServiceHost host = new ServiceHost(typeof(Bank), baseAddress)) 
{ 
WSDualHttpBinding bind = new WSDualHttpBinding(); 
bind.TransactionFlow = true; 
host.AddServiceEndpoint(typeof(IBank), bind, ""); 
host.Open(); 
Console.WriteLine("中国工商银行开始营业!"); 
Console.Read(); 
} 
} 
} 
} 

Jillzhang.Wcf.Transactions.CCB
using System; 
using System.Collections.Generic; 
using System.Linq; 
using System.Text; 
using System.ServiceModel; 
using Jillzhang.Wcf.Transactions.Contracts; 

namespace Jillzhang.Wcf.Transactions.CCB
{ 
class Program 
{ 
static void Main(string[] args) 
{ 
Uri baseAddress = new Uri("http://127.0.0.1:8655/CCB"); 
using (ServiceHost host = new ServiceHost(typeof(Bank), baseAddress)) 
{ 
WSDualHttpBinding bind = new WSDualHttpBinding(); 
bind.TransactionFlow = true; 
host.AddServiceEndpoint(typeof(IBank), bind, ""); 
host.Open(); 
Console.WriteLine("中国建设银行开始营业!"); 
Console.Read(); 
} 
} 
} 
} 


客户端
一个WPF应用程序,可以调用服务来模拟银行转帐功能,因为本文着重介绍WCF,对此不作详细赘述,以后在一起学WPF系列中会逐步加以学习,本文只给出主要的xaml文件和.cs文件
Window1.xaml
<Window x:Class="Jillzhang.Wcf.BankClient.Window1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
WindowStyle="ToolWindow" SizeToContent="WidthAndHeight" WindowStartupLocation="CenterScreen" 
Title="银行联盟自助服务系统" ResizeMode="NoResize" Initialized="Window_Initialized"> 
<Window.Resources> 
<Style x:Key="styleBackground"> 
<Setter Property="Control.Background"> 
<Setter.Value> 
<LinearGradientBrush StartPoint="0.5,0" EndPoint="0.5,1"> 
<GradientStop Color="#50000000" Offset="0.5"/> 
<GradientStop Color="#ff999999" Offset="1"/> 
</LinearGradientBrush> 
</Setter.Value> 
</Setter> 
<Setter Property="Control.Width" Value="500"> 
</Setter> 
</Style> 
