今天來介紹下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協議。