今天来介绍下WCF对事务的支持。web
首先,你们在学习数据库的时候就已经接触到事务这个概念了。所谓事务,它是一个操做序列,这些操做要么都执行,要么都不执行,它是一个不可分割的工做单元。例如,银行转帐功能,这个功能涉及两个逻辑操做数据库
现实生活中,这两个操做须要要么都执行,要么都不执行。因此在实现转帐功能时,这两个操做就能够做为一个事务来进行提交,这样才可以保证转帐功能的正确执行。编程
上面经过银行转帐的例子来解释了事务的概念了,也能够说很是容易理解。而后在数据库的相关书籍里面都会介绍事务的特性。一个逻辑工做单元要成为事务,必须知足四个特性,这四个特性包括原子性、一致性、隔离性和持久性。这四个特性也简称为ACID(ACID是四个特性英文单词首字母的缩写)。网络
WCF支持分布式事务,也就是说WCF中的事务能够跨越服务边界、进程、机器和网络,在多个客户端和服务之间存在。即WCF中事务能够被传播的。既然WCF支持事务,则天然就有对应传输事务信息的相关协议。因此也就有了事务协议。并发
WCF使用不一样的事务协议来控制事务的执行范围,事务协议是为了实现分布式环境中事务的传播。tcp
WCF支持如下三种事务协议:分布式
由于轻量级协议不能跨越服务边界传播事务,因此没有绑定支持轻量级协议。WCF预约义的绑定中实现了标准的WS-Atomic 协议和Microsoft专有的OleTx协议,咱们能够经过编程或配置文件来设置事务协议。具体设置方法以下所示:ide
<bindings>
<netTcpBinding>
<!--经过transactionProtocol属性来设置事务协议-->
<binding name="transactionTCP" transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004"/>
</netTcpBinding>
</bindings>
// 经过编程设置
NetTcpBinding tcpBinding = new NetTcpBinding(); // 注意: 事务协议的配置只有在事务传播的状况下才有意义
tcpBinding.TransactionFlow = true; tcpBinding.TransactionProtocol = TransactionProtocol.WSAtomicTransactionOctober2004;
这里须要注意,事务协议的配置只有在容许事务传播的状况下才有意义。而且NetTcpBinding和NetNamedPipeBinding都提供了TransactionProtocol属性。因为TCP和IPC绑定只能在内网使用,将它们设置为WSAT协议并没有实际意义,对于WS绑定(如WSHttpBinding、WSDualHttpBinding和WSFederationHttpBinding)并无TransactionProtocol属性,它们设计的目的在于当涉及多个使用WAST协议的事务管理器时,可以跨越Internet。但若是只有一个事务协调器,OleTx协议将是默认的协议,没必要也不能为它配置一个特殊的协议。
分布式事务的实现要依靠第三方事务管理器来实现。它负责管理一个个事务的执行状况,最后根据所有事务的执行结果,决定提交或回滚整个事务。WCF提供了三个不一样的事务管理器,它们分别是轻量级事务管理器(LTM)、核心事务管理器(KTM)和分布式事务协调器(DTC)。WCF根据平台使用的公共应用程序事务执行的任务、调用的服务以及所消耗的资源分配合适的事务管理器。经过自动地分配事务管理器,WCF将事务管理从服务代码和用到的事务协议中解耦出来,开发者没必要为事务管理器而苦恼。下面分别介绍下这三种事务管理器。性能
事务使用哪一个事务由绑定的事务流属性( TransactionFlow 属性)、操做契约中的事务流选项( TransactionFlowOption ) 以及操做行为特性中的事务范围属性( TransactionScopeRequired )共同决定。TransactionFlow属性有2个值,true 或false,TransactionFlowOption有三个值,NotAllowed、Allowed和Mandatory,TransactionScopeRequired有两个值,true或false。因此一共有12种(2*3*2)可能的配置设置。在这些配置设置中,有4种不知足要求的,例如在绑定中设置TransactionFlow属性为false,却设置TransactionFlowOption为Mandatory。下图列出了剩下的8种状况:学习
上图中的8中排列组合实际最终只产生了四种事务传播模式,这4种传播模式为:Client/Service、Client、Service和None。上图黑体字指出各类模式推荐的配置设置。在设计应用程序时,每种模式都有它本身的适用场景。对于除None模式的其余三种模式的推荐配置详细介绍以下所示:
(1) 选择一个支持事务的Binding,设置 TransactionFlow = true。
(2) 设置 TransactionFlow(TransactionFlowOption.Allowed)。
(3) 设置 OperationBehavior(TransactionScopeRequired=true)。
(1) 选择一个支持事务的Binding,设置 TransactionFlow = true。
(2) 设置 TransactionFlow(TransactionFlowOption.Mandatory)。
(3) 设置 OperationBehavior(TransactionScopeRequired=true)。
(1) 选择任何一种Binding,设置 TransactionFlow = false(默认)。
(2) 设置 TransactionFlow(TransactionFlowOption.NotAllowed)。
(3) 设置 OperationBehavior(TransactionScopeRequired=true)。
上面内容对WCF中事务进行了一个详细的介绍,下面具体经过一个实例来讲明WCF中如何实现对事务的支持。首先仍是按照前面博文中介绍的步骤来实现该实例。
第一步:建立WCF契约和契约的实现,新建一个WCF服务应用程序项目并添加WCF服务文件,具体的实现代码以下所示:
namespace WCFContractAndService { // 服务契约
[ServiceContract(SessionMode= SessionMode.Required)] //[ServiceBehavior(ReleaseServiceInstanceOnTransactionComplete = false)]
public interface IOrderService { // 操做契约
[OperationContract] // 控制客户端的事务是否传播到服务 // TransactionFlow的值会包含在服务发布的元数据上
[TransactionFlow(TransactionFlowOption.NotAllowed)] List<Customer> GetCustomers(); [OperationContract] [TransactionFlow(TransactionFlowOption.NotAllowed)] List<Product> GetProducts(); [OperationContract] [TransactionFlow(TransactionFlowOption.Mandatory)] string PlaceOrder(Order order); [OperationContract] [TransactionFlow(TransactionFlowOption.Mandatory)] string AdjustInventory(int productId, int quantity); [OperationContract] [TransactionFlow(TransactionFlowOption.Mandatory)] string AdjustBalance(int customerId, decimal amount); } [DataContract] public class Customer { [DataMember] public int CustomerId { get; set; } [DataMember] public string CompanyName { get; set; } [DataMember] public decimal Balance { get; set; } } [DataContract] public class Product { [DataMember] public int ProductId { get; set; } [DataMember] public string ProductName { get; set; } [DataMember] public decimal Price { get; set; } [DataMember] public int OnHand { get; set; } } [DataContract] public class Order { [DataMember] public int CustomerId { get; set; } [DataMember] public int ProductId { get; set; } [DataMember] public decimal Price { get; set; } [DataMember] public int Quantity { get; set; } [DataMember] public decimal Amount { get; set; } } } namespace WCFContractAndService { // 服务实现
[ServiceBehavior( TransactionIsolationLevel = IsolationLevel.Serializable, TransactionTimeout= "00:00:30", InstanceContextMode = InstanceContextMode.PerSession, TransactionAutoCompleteOnSessionClose = true)] public class OrderService :IOrderService { private List<Customer> customers = null; private List<Product> products = null; private int orderId = 0; private string conString = Properties.Settings.Default.TransactionsConnectionString; public List<Customer> GetCustomers() { customers = new List<Customer>(); using (var cnn = new SqlConnection(conString)) { using (var cmd = new SqlCommand("SELECT * " + "FROM Customers ORDER BY CustomerId", cnn)) { cnn.Open(); using (SqlDataReader CustomersReader = cmd.ExecuteReader()) { while (CustomersReader.Read()) { var customer = new Customer(); customer.CustomerId = CustomersReader.GetInt32(0); customer.CompanyName = CustomersReader.GetString(1); customer.Balance = CustomersReader.GetDecimal(2); customers.Add(customer); } } } } return customers; } public List<Product> GetProducts() { products = new List<Product>(); using (var cnn = new SqlConnection(conString)) { using (var cmd = new SqlCommand( "SELECT * " +
"FROM Products ORDER BY ProductId", cnn)) { cnn.Open(); using (SqlDataReader productsReader = cmd.ExecuteReader()) { while (productsReader.Read()) { var product = new Product(); product.ProductId = productsReader.GetInt32(0); product.ProductName = productsReader.GetString(1); product.Price = productsReader.GetDecimal(2); product.OnHand = productsReader.GetInt16(3); products.Add(product); } } } } return products; } // 设置服务的环境事务 // 使用Client模式,即便用客户端的事务
[OperationBehavior(TransactionScopeRequired =true, TransactionAutoComplete = false)] public string PlaceOrder(Order order) { using (var conn = new SqlConnection(conString)) { var cmd = new SqlCommand( "Insert Orders (CustomerId, ProductId, " +
"Quantity, Price, Amount) " + "Values( " +
"@customerId, @productId, @quantity, " +
"@price, @amount)", conn); cmd.Parameters.Add(new SqlParameter( "@customerId", order.CustomerId)); cmd.Parameters.Add(new SqlParameter( "@productid", order.ProductId)); cmd.Parameters.Add(new SqlParameter( "@price", order.Price)); cmd.Parameters.Add(new SqlParameter( "@quantity", order.Quantity)); cmd.Parameters.Add(new SqlParameter( "@amount", order.Amount)); try { conn.Open(); if (cmd.ExecuteNonQuery() <= 0) { return "The order was not placed"; } cmd = new SqlCommand( "Select Max(OrderId) From Orders " +
"Where CustomerId = @customerId", conn); cmd.Parameters.Add(new SqlParameter( "@customerId", order.CustomerId)); using (SqlDataReader reader = cmd.ExecuteReader()) { while (reader.Read()) { orderId = Convert.ToInt32(reader[0].ToString()); } } return string.Format("Order {0} was placed", orderId); } catch (Exception ex) { throw new FaultException(ex.Message); } } } // 使用Client模式,即便用客户端的事务
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)] public string AdjustInventory(int productId, int quantity) { using (var conn = new SqlConnection(conString)) { var cmd = new SqlCommand( "Update Products Set OnHand = " +
"OnHand - @quantity " +
"Where ProductId = @productId", conn); cmd.Parameters.Add(new SqlParameter( "@quantity", quantity)); cmd.Parameters.Add(new SqlParameter( "@productid", productId)); try { conn.Open(); if (cmd.ExecuteNonQuery() <= 0) { return "The inventory was not updated"; } else { return "The inventory was updated"; } } catch (Exception ex) { throw new FaultException(ex.Message); } } } // 使用Client模式,即便用客户端的事务
[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)] public string AdjustBalance(int customerId, decimal amount) { using (var conn = new SqlConnection(conString)) { var cmd = new SqlCommand( "Update Customers Set Balance = " +
"Balance - @amount " +
"Where CustomerId = @customerId", conn); cmd.Parameters.Add(new SqlParameter( "@amount", amount)); cmd.Parameters.Add(new SqlParameter( "@customerId", customerId)); try { conn.Open(); if (cmd.ExecuteNonQuery() <= 0) { return "The balance was not updated"; } else { return "The balance was updated"; } } catch (Exception ex) { throw new FaultException(ex.Message); } } } } }
上面的服务契约和服务实现与传统的实现没什么区别。这里使用IIS来宿主WCF服务。
第二步:修改WCF服务端对应的Web.config的内容以下所示:
<configuration>
<system.web>
<compilation debug="true" targetFramework="4.5" />
<httpRuntime targetFramework="4.5" />
</system.web>
<system.serviceModel>
<behaviors>
<serviceBehaviors>
<behavior name="OrderServiceBehavior">
<serviceMetadata httpGetEnabled="true" httpsGetEnabled="true" />
<serviceDebug includeExceptionDetailInFaults="false" />
</behavior>
</serviceBehaviors>
</behaviors>
<bindings>
<wsHttpBinding>
<!--经过设置transactionFlow属性为true来使绑定支持事务传播;对于wsHttpBinding契约事务传播-->
<binding name="wsHttpBinding" transactionFlow="true">
<!--启用消息可靠性选项-->
<!--<reliableSession enabled="true"/>-->
</binding>
</wsHttpBinding>
</bindings>
<services>
<service name="WCFContractAndService.OrderService" behaviorConfiguration="OrderServiceBehavior">
<endpoint address="" binding="wsHttpBinding" bindingConfiguration="wsHttpBinding" contract="WCFContractAndService.IOrderService"/>
</service>
</services>
<serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true" />
</system.serviceModel>
</configuration>
这里采用了wsHttpBinding绑定,并设置其transactionFlow属性为true使其支持事务传播。接下来看看客户端的实现。
第三步:WCF客户端的实现,经过添加服务引用的方式来生成代理类。这里的客户端是WinForm程序。
1 public partial class Form1 : Form 2 { 3 public Form1() 4 { 5 InitializeComponent(); 6 } 7
8 private Customer customer = null; 9 private List<Customer> customers = null; 10 private Product product = null; 11 private List<Product> products = null; 12 private OrderServiceClient proxy = null; 13 private Order order = null; 14 private string result = String.Empty; 15
16 private void Form1_Load(object sender, EventArgs e) 17 { 18 proxy = new OrderServiceClient("WSHttpBinding_IOrderService"); 19 GetCustomersAndProducts(); 20 } 21
22 private void GetCustomersAndProducts() 23 { 24 customers = proxy.GetCustomers().ToList<Customer>(); 25 customerBindingSource.DataSource = customers; 26
27 products = proxy.GetProducts().ToList<Product>(); 28 productBindingSource.DataSource = products; 29 } 30
31 private void placeOrderButton_Click(object sender, EventArgs e) 32 { 33 customer = (Customer)this.customerBindingSource.Current; 34 product = (Product)this.productBindingSource.Current; 35 Int32 quantity = Convert.ToInt32(quantityTextBox.Text); 36
37 order = new Order(); 38 order.CustomerId = customer.CustomerId; 39 order.ProductId = product.ProductId; 40 order.Price = product.Price; 41 order.Quantity = quantity; 42 order.Amount = order.Price * Convert.ToDecimal(order.Quantity); 43
44 // 事务处理
45 using (var tranScope = new TransactionScope()) 46 { 47 proxy = new OrderServiceClient("WSHttpBinding_IOrderService"); 48 { 49 try
50 { 51 result = proxy.PlaceOrder(order); 52 MessageBox.Show(result); 53
54 result = proxy.AdjustInventory(product.ProductId, quantity); 55 MessageBox.Show(result); 56
57 result = proxy.AdjustBalance(customer.CustomerId, 58 Convert.ToDecimal(quantity) * order.Price); 59 MessageBox.Show(result); 60
61 proxy.Close(); 62 tranScope.Complete(); // Cmmmit transaction
63 } 64 catch (FaultException faultEx) 65 { 66 MessageBox.Show(faultEx.Message +
67 "\n\nThe order was not placed"); 68
69 } 70 catch (ProtocolException protocolEx) 71 { 72 MessageBox.Show(protocolEx.Message +
73 "\n\nThe order was not placed"); 74 } 75 } 76 } 77
78 // 成功提交后强制刷新界面
79 quantityTextBox.Clear(); 80 try
81 { 82 proxy = new OrderServiceClient("WSHttpBinding_IOrderService"); 83 GetCustomersAndProducts(); 84 } 85 catch (FaultException faultEx) 86 { 87 MessageBox.Show(faultEx.Message); 88 } 89 } 90 }
从上面代码能够看出,WCF事务的实现是利用 TransactionScope 事务类来完成的。下面让咱们看看程序的运行结果。在运行程序以前,咱们必须运行SQL脚原本建立程序中的使用的数据库,具体的脚本以下所示:
1 USE [TransactionsDemo]
2 GO
3 /****** Object: Table [dbo].[Customers] Script Date: 01/15/2009 08:14:25 ******/
4 SET ANSI_NULLS ON
5 GO
6 SET QUOTED_IDENTIFIER ON
7 GO
8 CREATE TABLE [dbo].[Customers](
9 [CustomerId] [int] IDENTITY(1,1) NOT NULL,
10 [Name] [nvarchar](20) NOT NULL,
11 [Balance] [smallmoney] NOT NULL, check(Balance >= 0),
12 CONSTRAINT [PK_Customers] PRIMARY KEY CLUSTERED
13 (
14 [CustomerId] ASC
15 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
16 ) ON [PRIMARY]
17 GO
18 /****** Object: Table [dbo].[Products] Script Date: 01/15/2009 08:14:25 ******/
19 SET ANSI_NULLS ON
20 GO
21 SET QUOTED_IDENTIFIER ON
22 GO
23 CREATE TABLE [dbo].[Products](
24 [ProductId] [int] IDENTITY(1,1) NOT NULL,
25 [Name] [nvarchar](20) NOT NULL,
26 [Price] [smallmoney] NOT NULL,
27 [OnHand] [smallint] NOT NULL, check(OnHand >= 0),
28 CONSTRAINT [PK_Products] PRIMARY KEY CLUSTERED
29 (
30 [ProductId] ASC
31 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
32 ) ON [PRIMARY]
33 GO
34 /****** Object: Table [dbo].[Orders] Script Date: 01/15/2009 08:14:25 ******/
35 SET ANSI_NULLS ON
36 GO
37 SET QUOTED_IDENTIFIER ON
38 GO
39 CREATE TABLE [dbo].[Orders](
40 [OrderId] [int] IDENTITY(1,1) NOT NULL,
41 [CustomerId] [int] NOT NULL,
42 [ProductId] [int] NOT NULL,
43 [Quantity] [smallint] NOT NULL,
44 [Price] [smallmoney] NOT NULL,
45 [Amount] [smallmoney] NOT NULL,
46 CONSTRAINT [PK_Orders] PRIMARY KEY CLUSTERED
47 (
48 [OrderId] ASC
49 )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
50 ) ON [PRIMARY]
51 GO
52 INSERT Customers (Name, Balance) VALUES ('Contoso', 10000)
53 INSERT Customers (Name, Balance) VALUES ('Northwind', 25000)
54 INSERT Customers (Name, Balance) VALUES ('Litware', 50000)
55 INSERT Products (Name, Price, OnHand) VALUES ('Wood', 100, 1000)
56 INSERT Products (Name, Price, OnHand) VALUES ('Wallboard', 200, 2500)
57 INSERT Products (Name, Price, OnHand) VALUES ('Pipe', 500, 5000)
58 GO
View Code
生成程序使用的数据库以后,按F5运行WCF客户端程序,并在出现的界面中购买Wood材料100,运行结果以下图所示:
单击Place order按钮后,即执行下订单操做,若是订单成功后,将会更新产品的库存和用户的余额,你将看到以下图所示的运行结果:
到这里,关于WCF中事务的介绍就结束了。WCF支持四种事务模式,Client/Service、Client、Service和None,对于每种模式都有其不一样的配置。
通常尽可能使用Client/Service或Client事务模式。WCF事务的实现借助于已有的System.Transaction实现本地事务的编程,而分布式事务则借助MSDTC分布式事务协调机制来实现。
WCF提供了支持事务传播的绑定协议包括:wsHttpBinding、WSDualHttpBinding、WSFederationBinding、NetTcpBinding和NetNamedPipeBinding,最后两个绑定容许选择WS-AT协议或OleTx协议,而其余绑定都使用标准的WS-AT协议。