好久没更新,总感觉自己欠了什么一样的,所以今天迫不及待地来更新了,因为后面还有好几个系列准备些,还有很多东西需要学习总结的。今天就来介绍下WCF对事务的支持。
首先,大家在学习数据库的时候就已经接触到事务这个概念了。所谓事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单元。例如,银行转账功能,这个功能涉及两个逻辑操作
现实生活中,这两个操作需要要么都执行,要么都不执行。所以在实现转账功能时,这两个操作就可以作为一个事务来进行提交,这样才能够保证转账功能的正确执行。
上面通过银行转账的例子来解释了事务的概念了,也可以说非常容易理解。然后在数据库的相关书籍里面都会介绍事务的特性。一个逻辑工作单元要成为事务,必须满足四个特性,这四个特性包括原子性、一致性、隔离性和持久性。这四个特性也简称为ACID(ACID是四个特性英文单词首字母的缩写)。
WCF支持分布式事务,也就是说WCF中的事务可以跨越服务边界、进程、机器和网络,在多个客户端和服务之间存在。即WCF中事务可以被传播的。既然WCF支持事务,则自然就有对应传输事务信息的相关协议。所以也就有了事务协议。
WCF使用不同的事务协议来控制事务的执行范围,事务协议是为了实现分布式环境中事务的传播。WCF支持以下三种事务协议:
因为轻量级协议不能跨越服务边界传播事务,所有没有绑定支持轻量级协议。WCF预定义的绑定中实现了标准的WS-Atomic 协议和Microsoft专有的OleTx协议,我们可以通过编程或配置文件来设置事务协议。具体设置方法如下所示:
1 <bindings> 2 <netTcpBinding> 3 <!--通过transactionProtocol属性来设置事务协议--> 4 <binding name="transactionTCP" transactionFlow="true" transactionProtocol="WSAtomicTransactionOctober2004"/> 5 </netTcpBinding> 6 </bindings> 7 // 通过编程设置 8 NetTcpBinding tcpBinding = new NetTcpBinding(); 9 // 注意: 事务协议的配置只有在事务传播的情况下才有意义10 tcpBinding.TransactionFlow = true; 11 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契约和契约的实现,具体的实现代码如下所示:
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服务。
第二步:宿主的实现。创建一个空的Web的项目,并添加WCF服务文件,具体内容如下所示:
<%@ ServiceHost Language="C#" Debug="true" Service="WCFContractAndService.OrderService" CodeBehind="OrdersService.svc.cs" %>
对应的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 try50 {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 transaction63 }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 try81 {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脚本来创建程序中的使用的数据库,具体的脚本如下所示:
生成程序使用的数据库之后,按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协议。在一一篇博文将分享WCF对消息队列的支持。
本文所有源代码:WCFTransaction.zip
联系客服