Distributed transaction using WCF

Exclusive offer: get 50% off this eBook here
WCF 4.0 Multi-tier Services Development with LINQ to Entities

WCF 4.0 Multi-tier Services Development with LINQ to Entities — Save 50%

Build SOA applications on the Microsoft platform with this hands-on WCF 4.0 book and eBook guide updated for VS2010

$29.99    $15.00
by Mike Liu | June 2010 | Enterprise Articles Microsoft

WCF is the acronym for Windows Communication Foundation. It is Microsoft's latest technology that enables applications in a distributed environment to communicate with each other.

In this article series, we will first verify that the LINQNorthwind WCF service does not support distributed transaction processing. We will then explain how to enhance this WCF service to support distributed transaction processing and how to configure all related computers to enable distributed transaction support. To demonstrate this, we will propagate a transaction from the client to the WCF service and verify that all sequential calls to the WCF service are within one single distributed transaction. We will also explain the multiple database support of the WCF service and discuss how to configure MSDTC and the firewall for the distributed WCF service.

In this article by Mike Liu, author of  WCF 4.0 Multi-tier Services Development with LINQ to Entities We will cover the following topics in this article:

  • Creating the solution files
  • Testing the transaction behavior of the DistNorthwind WCF service
  • Enabling transaction flow in the service bindings
  • Modifying the service operation contract to allow transaction flow
  • Modifying the service operation implementation to require a transaction scope
  • Configuring the Distributed Transaction Coordinator for the distributed WCF service

(Read more interesting articles on WCF 4.0 here.)

Creating the DistNorthwind solution

In this article, we will create a new solution based on the LINQNorthwind solution.We will copy all of the source code from the LINQNorthwind directory to a new directory and then customize it to suit our needs. The steps here are very similar to the steps in the previous chapter when we created the LINQNorthwind solution.Please refer to the previous chapter for diagrams.

Follow these steps to create the new solution:

  1. Create a new directory named DistNorthwind under the existing C:\SOAwithWCFandLINQ\Projects\ directory.
  2. Copy all of the files under the C:\SOAwithWCFandLINQ\Projects\LINQNorthwind directory to the C:\SOAwithWCFandLINQ\Projects\DistNorthwind directory.
  3. Remove the folder, LINQNorthwindClient. We will create a new client for this solution.
  4. Change all the folder names under the new folder, DistNorthwind, from LINQNorthwindxxx to DistNorthwindxxx.
  5. Change the solution files' names from
  6. LINQNorthwind.sln to DistNorthwind.sln, and also from LINQNorthwind.suo to DistNorthwind.suo.

Now we have the file structures ready for the new solution but all the file contents and the solution structure are still for the old solution. Next we need to change them to work for the new solution.

We will first change all the related WCF service files. Once we have the service up and running we will create a new client to test this new service.

  1. Start Visual Studio 2010 and open this solution: C:\SOAWithWCFandLINQ\Projects\DistNorthwind\DistNorthwind.sln.
  2. Click on the OK button to close the projects were not loaded correctly warning dialog.
  3. From Solution Explorer, remove all five projects (they should all be unavailable).
  4. Right-click on the solution item and select Add | Existing Projects… to add these four projects to the solution. Note that these are the projects under the DistNorthwind folder, not under the LINQNorthwind folder: LINQNorthwindEntities.csproj, LINQNorthwindDAL.csproj, LINQNorthwindLogic.csproj, and LINQNorthwindService.csproj.
  5. In Solution Explorer, change all four projects' names from LINQNorthwindxxx to DistNorthwindxxx.
  6. In Solution Explorer, right-click on each project, select Properties (or select menu Project | DistNorthwindxxx Properties), then change the Assembly name from LINQNorthwindxxx to DistNorthwindxxx, and change the Default namespace from MyWCFServices.LINQNorthwindxxx to MyWCFServices.DistNorthwindxxx.
  7. Open the following files and change the word LINQNorthwind to DistNorthwind wherever it occurs: ProductEntity.cs, ProductDAO.cs, ProductLogic.cs, IProductService.cs, and ProductService.cs.
  8. Open the file, app.config, in the DistNorthwindService project and change the word LINQNorthwind to DistNorthwind in this file.

The screenshot below shows the final structure of the new solution, DistNorthwind:

Distributed transaction using WCF

Now we have finished modifying the service projects. If you build the solution now you should see no errors. You can set the service project as the startup project, run the program.

Hosting the WCF service in IIS

The WCF service is now hosted within WCF Service Host.We had to start the WCF Service Host before we ran our test client.Not only do you have to start the WCF Service Host, you also have to start the WCF Test client and leave it open. This is not that nice. In addition, we will add another service later in this articleto test distributed transaction support with two databases and it is not that easy to host two services with one WCF Service Host.So, in this article, we will first decouple our WCF service from Visual Studio to host it in IIS.

You can follow these steps to host this WCF service in IIS:

  1. In Windows Explorer, go to the directory C:\SOAWithWCFandLINQ\Projects\DistNorthwind\DistNorthwindService.
  2. Within this folder create a new text file, ProductService.svc, to contain the following one line of code:
  3. <%@ServiceHost Service="MyWCFServices.DistNorthwindService.
    ProductService"%>

  4. Again within this folder copy the file, App.config, to Web.config and remove the following lines from the new Web.config file:
  5. <host>
    <baseAddresses>
    <add baseAddress="http://localhost:8080/
    Design_Time_Addresses/MyWCFServices/
    DistNorthwindService/ProductService/" />
    </baseAddresses>
    </host>

  6. Now open IIS Manager, add a new application, DistNorthwindService, and set its physical path to C:\SOAWithWCFandLINQ\Projects\DistNorthwind\DistNorthwindService. If you choose to use the default application pool, DefaultAppPool, make sure it is a .NET 4.0 application pool.If you are using Windows XP you can create a new virtual directory, DistNorthwindService, set its local path to the above directory, and make sure its ASP.NET version is 4.0.
  7. From Visual Studio, in Solution Explorer, right-click on the project item,DistNorthwindService, select Properties, then click on the Build Events tab, and enter the following code to the Post-build event command line box: copy .\*.* ..\With this Post-build event command line, whenever DistNorthwindService is rebuilt the service binary files will be copied to the C:\SOAWithWCFandLINQ\Projects\DistNorthwind\DistNorthwindService\bin directory so that the service hosted in IIS will always be up-to-date.
  8. From Visual Studio, in Solution Explorer, right-click on the project item, DistNorthwindService, and select Rebuild.

Now you have finished setting up the service to be hosted in IIS. Open Internet Explorer, go to the following address, and you should see the ProductService description in the browser: http://localhost/DistNorthwindService/ProductService.svc

Testing the transaction behavior of the WCF service

Before explaining how to enhance this WCF service to support distributed transactions, we will first confirm that the existing WCF service doesn't support distributed transactions. In this article, we will test the following scenarios:

  1. Create a WPF client to call the service twice in one method.
  2. The first service call should succeed and the second service call should fail.
  3. Verify that the update in the first service call has been committed to the database, which means that the WCF service does not support distributed transactions.
  4. Wrap the two service calls in one TransactionScope and redo the test.
  5. Verify that the update in the first service call has still been committed to the database which means the WCF service does not support distributed transactions even if both service calls are within one transaction scope.
  6. Add a second database support to the WCF service.
  7. Modify the client to update both databases in one method.
  8. The first update should succeed and the second update should fail.
  9. Verify that the first update has been committed to the database, which means the WCF service does not support distributed transactions with multiple databases.

Creating a client to call the WCF service sequentially

The first scenario to test is that within one method of the client application two service calls will be made and one of them will fail. We then verify whether the update in the successful service call has been committed to the database. If it has been, it will mean that the two service calls are not within a single atomic transaction and will indicate that the WCF service doesn't support distributed transactions.

You can follow these steps to create a WPF client for this test case:

  1. In Solution Explorer, right-click on the solution item and select Add | New Project… from the context menu.
  2. Select Visual C# | WPF Application as the template.
  3. Enter DistributedWPF as the Name.
  4. Click on the OK button to create the new client project.

Now the new test client should have been created and added to the solution. Let's follow these steps to customize this client so that we can call ProductService twice within one method and test the distributed transaction support of this WCF service:

  1. On the WPF MainWindow designer surface, add the following controls (you can double-click on the MainWindow.xaml item to open this window and make sure you are on the design mode, not the XAML mode):
    • A label with Content Product ID
    • Two textboxes named txtProductID1 and txtProductID2
    • A button named btnGetProduct with Content Get Product Details
    • A separator to separate above controls from below controls
    • Two labels with content Product1 Details and Product2 Details
    • Two textboxes named txtProduct1Details and txtProduct2Details, with the following properties:
      • AcceptsReturn: checked
      • Background: Beige
      • HorizontalScrollbarVisibility: Auto
      • VerticalScrollbarVisibility: Auto
      • IsReadOnly: checked
    • A separator to separate above controls from below controls
    • A label with content New Price
    • Two textboxes named txtNewPrice1 and txtNewPrice2
    • A button named btnUpdatePrice with Content Update Price
    • A separator to separate above controls from below controls
    • Two labels with content Update1 Results and Update2 Results
    • Two textboxes named txtUpdate1Results and txtUpdate2Results with the following properties:
      • AcceptsReturn: checked
      • Background: Beige
      • HorizontalScrollbarVisibility: Auto
      • VerticalScrollbarVisibility: Auto
      • IsReadOnly: checked
    • Your MainWindow design surface should look like the following screenshot:
  2. In Solution Explorer, right-click on the DistNorthwindWPF project item, select Add Service Reference… and add a service reference of the product service to the project. The namespace of this service reference should be ProductServiceProxy and the URL of the product service should be like this:http://localhost/DistNorthwindService/ProductService.svc
  3. On the MainWindow.xaml designer surface, double-click on the Get Product Details button to create an event handler for this button.
  4. In the MainWindow.xaml.cs file, add the following using statement: using DistNorthwindWPF.ProductServiceProxy;
  5. Again in the MainWindow.xaml.cs file, add the following two class members: Product product1, product2;
  6. Now add the following method to the MainWindow.xaml.cs file:
  7. private string GetProduct(TextBox txtProductID, ref Product
    product)
    {
    string result = "";
    try
    {
    int productID = Int32.Parse(txtProductID.Text.ToString());
    ProductServiceClient client = new ProductServiceClient();
    product = client.GetProduct(productID);
    StringBuilder sb = new StringBuilder();
    sb.Append("ProductID:" +
    product.ProductID.ToString() + "\n");
    sb.Append("ProductName:" +
    product.ProductName + "\n");
    sb.Append("UnitPrice:" +
    product.UnitPrice.ToString() + "\n");
    sb.Append("RowVersion:");
    foreach (var x in product.RowVersion.AsEnumerable())
    {
    sb.Append(x.ToString());
    sb.Append(" ");
    }
    result = sb.ToString();
    }
    catch (Exception ex)
    {
    result = "Exception: " + ex.Message.ToString();
    }
    return result;
    }

    This method will call the product service to retrieve a product from the database, format the product details to a string, and return the string. This string will be displayed on the screen. The product object will also be returned so that later on we can reuse this object to update the price of the product.

  8. Inside the event handler of the Get Product Details button, add the following two lines of code to get and display the product details: txtProduct1Details.Text = GetProduct(txtProductID1, ref product1); txtProduct2Details.Text = GetProduct(txtProductID2, ref product2);

Now we have finished adding code to retrieve products from the database through the Product WCF service. Set DistNorthwindWPF as the startup project, press Ctrl + F5 to start the WPF test client, enter 30 and 31 as the product IDs, and then click on the Get Product Details button. You should get a window like this image:

To update the prices of these two products follow these steps to add the code to the project:

  1. On the MainWindow.xaml design surface and double-click on the Update Price button to add an event handler for this button.
  2. Add the following method to the MainWindow.xaml.cs file:
  3. private string UpdatePrice(
    TextBox txtNewPrice,
    ref Product product,
    ref bool updateResult)
    {
    string result = "";
    try
    {
    product.UnitPrice =
    Decimal.Parse(txtNewPrice.Text.ToString());
    ProductServiceClient client =
    new ProductServiceClient();
    updateResult =
    client.UpdateProduct(ref product);
    StringBuilder sb = new StringBuilder();
    if (updateResult == true)
    {
    sb.Append("Price updated to ");
    sb.Append(txtNewPrice.Text.ToString());
    sb.Append("\n");
    sb.Append("Update result:");
    sb.Append(updateResult.ToString());
    sb.Append("\n");
    sb.Append("New RowVersion:");
    }
    else
    {
    sb.Append("Price not updated to ");
    sb.Append(txtNewPrice.Text.ToString());
    sb.Append("\n");
    sb.Append("Update result:");
    sb.Append(updateResult.ToString());
    sb.Append("\n");
    sb.Append("Old RowVersion:");
    }
    foreach (var x in product.RowVersion.AsEnumerable())
    {
    sb.Append(x.ToString());
    sb.Append(" ");
    }
    result = sb.ToString();
    }
    catch (Exception ex)
    {
    result = "Exception: " + ex.Message;
    }
    return result;
    }

    This method will call the product service to update the price of a product in the database. The update result will be formatted and returned so that later on we can display it. The updated product object with the new RowVersion will also be returned so that later on we can update the price of the same product again and again.

  4. Inside the event handler of the Update Price button, add the following code to update the product prices:
  5. if (product1 == null)
    {
    txtUpdate1Results.Text = "Get product details first";
    }
    else if (product2 == null)
    {
    txtUpdate2Results.Text = "Get product details first";
    }
    else
    {
    bool update1Result = false, update2Result = false;
    txtUpdate1Results.Text = UpdatePrice(
    txtNewPrice1, ref product1, ref update1Result);
    txtUpdate2Results.Text = UpdatePrice(
    txtNewPrice2, ref product2, ref update2Result);
    }

Testing the sequential calls to the WCF service

Let's run the program now to test the distributed transaction support of the WCF service. We will first update two products with two valid prices to make sure our code works with normal use cases. Then we will update one product with a valid price and another with an invalid price. We will verify that the update with the valid price has been committed to the database, regardless of the failure of the other update.

Let's follow these steps for this test:

  1. Press Ctrl + F5 to start the program.
  2. Enter 30 and 31 as product IDs in the top two textboxes and click on the Get Product Details button to retrieve the two products. Note that the prices for these two products are 25.89 and 12.5 respectively.
  3. Enter 26.89 and 13.5 as new prices in the middle two textboxes and click on the Update Price button to update these two products. The update results are true for both updates, as shown in following screenshot:
  4. Now enter 27.89 and -14.5 as new prices in the middle two textboxes and click on the Update Price button to update these two products. This time the update result for product 30 is still True but for the second update the result is False. Click on the Get Product Details button again to refresh the product prices so that we can verify the update results.

We know that the second service call should fail so the second update should not be committed to the database. From the test result we know this is true (the second product price didn't change). However from the test result we also know that the first update in the first service call has been committed to the database (the first product price has been changed). This means that the first call to the service is not rolled back even when a subsequent service call has failed. Therefore each service call is in a separate standalone transaction. In other words, the two sequential service calls are not within one distributed transaction.

WCF 4.0 Multi-tier Services Development with LINQ to Entities Build SOA applications on the Microsoft platform with this hands-on WCF 4.0 book and eBook guide updated for VS2010
Published: June 2010
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

(Read more interesting articles on WCF 4.0 here.)

Wrapping the WCF service calls in one transaction scope

This test is not a complete distributed transaction test. On the client side we didn't explicitly wrap the two updates in one transaction scope. We should test to see what will happen if we put the two updates within once transaction scope.

Follow these steps to wrap the two service calls in one transaction scope:

  1. Add a reference to System.Transactions in the client project.
  2. Add a using statement to the MainWindow.xaml.cs file like this:
    using System.Transactions;
  3. Add a using statement to put both updates within one transaction scope.The click event handler for the Update Price button should be like this now:
  4. if (product1 == null)
    {
    txtUpdate1Results.Text = "Get product details first";
    }
    else if (product2 == null)
    {
    txtUpdate2Results.Text = "Get product details first";
    }
    else
    {
    bool update1Result = false, update2Result = false;
    using (TransactionScope ts = new TransactionScope())
    {
    txtUpdate1Results.Text = UpdatePrice(
    txtNewPrice1, ref product1, ref update1Result);
    txtUpdate2Results.Text = UpdatePrice(
    txtNewPrice2, ref product2, ref update2Result);
    if (update1Result == true && update2Result == true)
    ts.Complete();
    }
    }

Run the client program again, still using 30 and 31 as product IDs, and enter 28.89 and -14.5 as new prices and you will find that even though we have wrapped both updates within one transaction scope the first update is still committed to the database—it is not rolled back even though the outer transaction on the client side does not complete and requests all participating parties to roll back. After the updates, product 30's price will be changed to 28.89 and product 31's price will remain as 13.5.

At this point we have proved that the WCF service does not support distributed transactions with multiple sequential service calls. Irrespective of whether the two sequential calls to the service have been wrapped in one transaction scope or not, each service call is treated as a standalone separate transaction and they do not participate in any distributed transaction.

Testing multiple database support of the WCF service

In this article, we tried to call the WCF service sequentially to update records in the same database. We have proved that this WCF service does not support distributed transactions. In this section, we will do one more test, to add a new WCF service—DistNorthwindRemoteService—to update records in another database on another computer. We will call the UpdateProduct operation in this new service together with the original UpdateProduct operation in the old service and then verify whether the two updates to the two databases will be within one distributed transaction.

This new service is very important for our distributed transaction support test because the distributed transaction coordinator will only be activated if more than two servers are involved in the same transaction. For test purposes we can't just update two databases on the same SQL server even though a transaction within a single SQL server that spans two or more databases is actually a distributed transaction. This is because the SQL server manages the distributed transaction internally—to the user it operates as a local transaction.

Creating a new WCF service

First we will add a new WCF service to update a product in a remote database.We will reuse the same WCF service we created for this solution but just change the connection string to point to a remote database in a remote machine.

Follow these steps to add this new service:

  1. Discover another machine with the SQL server installed. We will refer to this machine as the remote machine from now on.
  2. Install a Northwind database to this SQL server on the remote machine.Make sure you add a new column, RowVersion, to the Products table in this remote Northwind database. This is all we need to do on the remote machine in this article.
  3. On the local server, in Windows Explorer, create a new folder, DistNorthwindRemoteService, under the DistNorthwind solution folder: C:\SOAWithWCFandLINQ\Projects\DistNorthwind.
  4. Copy the following items from the DistNorthwindService folder to the new DistNorthwindRemoteService folder: Web.config, ProductService.svc, and bin.
  5. Open the file, Web.config, in the new service folder and change the Data Source part within the connectionString node from localhost to the remote machine name with a new Northwind database installed.
  6. In IIS Manager, add a new application, DistNorthwindRemoteService, and set its physical path to the new DistNorthwindRemoteService folder. If you are running XP just add a new virtual directory. You can open this address in Internet Explorer to verify that the new service is up and running: http://localhost/DistNorthwindRemoteService/ProductService.svc
  7. To make it easier to maintain this new service, from Visual Studio in Solution Explorer, add a new solution folder DistNorthwindRemoteService, to the solution and add the two files and bin folder of this new service to be under the new solution folder.
  8. Also from Visual Studio, in Solution Explorer, right-click on the project item,DistNorthwindService, select Properties, then click on the Build Events tab, and add the following to the Post-build event command line box, below the original line of the copy command: copy .\*.* ..\..\..\DistNorthwindRemoteService\bin

Again this Post-build event command line will make sure the remote service folder will always contain the latest service binary files.

Calling the new WCF service in the client application

The new service is now up and running. Next we will add a checkbox to the WPF client. If this checkbox is checked when the button, Get Product Details, is clicked, we will get the second product from the remote database using the new WCF service. And when the button, Update Price, is clicked, we will also update its price in the remote database using the new WCF service.

Now follow these steps to modify the WPF client application to call the new service:

  1. From Visual Studio, in Solution Explorer, right-click on the DistNorthwindWPF project item and add a service reference to the new WCF service, DistNorthwindRemoteService. The namespace of this service reference should be RemoteProductServiceProxy and the URL of the product service should be like this: http://localhost/DistNorthwindRemoteService/ProductService.svc
  2. Open the MainWindow.xaml file, go to design mode, and add a checkbox to indicate we are going to get and update a product in the remote database using the remote service. Set this checkbox's properties as this:
    • Content: Get and Update 2nd Product in Remote Database
    • Name: chkRemote
  3. Open the MainWindow.xaml.cs file and add a new class member: RemoteProductServiceProxy.Product remoteProduct;
  4. Still in the MainWindow.xaml.cs file, copy the method, GetProduct, and paste it as a new method, GetRemoteProduct. Change the Product type within this new method to be RemoteProductServiceProxy.Product and change the client type to RemoteProductServiceProxy.ProductServiceClient. The new method should be like this:
  5. private string GetRemoteProduct(TextBox txtProductID,
    ref RemoteProductServiceProxy.Product product)
    {
    string result = "";
    try
    {
    int productID = Int32.Parse(txtProductID.Text.ToString());
    RemoteProductServiceProxy.ProductServiceClient client =
    new RemoteProductServiceProxy.ProductServiceClient();
    product = client.GetProduct(productID);
    StringBuilder sb = new StringBuilder();
    sb.Append("ProductID:" +
    product.ProductID.ToString() + "\n");
    sb.Append("ProductName:" +
    product.ProductName + "\n");
    sb.Append("UnitPrice:" +
    product.UnitPrice.ToString() + "\n");
    sb.Append("RowVersion:");
    foreach (var x in product.RowVersion.AsEnumerable())
    {
    sb.Append(x.ToString());
    sb.Append(" ");
    }
    result = sb.ToString();
    }
    catch (Exception ex)
    {
    result = "Exception: " + ex.Message.ToString();
    }
    return result;
    }

  6. Change the method, btnGetProduct_Click, to call the new service if the checkbox is checked, like this:
  7. private void btnGetProduct_Click(object sender, RoutedEventArgs e)
    {
    txtProduct1Details.Text = GetProduct(
    txtProductID1, ref product1);
    if(chkRemote.IsChecked == true)
    txtProduct2Details.Text = GetRemoteProduct(
    txtProductID2, ref remoteProduct);
    else
    txtProduct2Details.Text = GetProduct(
    txtProductID2, ref product2);
    }

  8. Copy the method, UpdatePrice, and paste it as a new method, UpdateRemotePrice. Change the Product type within this new method to RemoteProductServiceProxy.Product and change the client type to RemoteProductServiceProxy.ProductServiceClient.The new method should be like this:
  9. private string UpdateRemotePrice(
    TextBox txtNewPrice,
    ref RemoteProductServiceProxy.Product product,
    ref bool updateResult)
    {
    string result = "";
    try
    {
    product.UnitPrice =
    Decimal.Parse(txtNewPrice.Text.ToString());
    RemoteProductServiceProxy.ProductServiceClient client =
    new RemoteProductServiceProxy.ProductServiceClient();
    updateResult =
    client.UpdateProduct(ref product);
    StringBuilder sb = new StringBuilder();
    if (updateResult == true)
    {
    sb.Append("Price updated to ");
    sb.Append(txtNewPrice.Text.ToString());
    sb.Append("\n");
    sb.Append("Update result:");
    sb.Append(updateResult.ToString());
    sb.Append("\n");
    sb.Append("New RowVersion:");
    }
    else
    {
    sb.Append("Price not updated to ");
    sb.Append(txtNewPrice.Text.ToString());
    sb.Append("\n");
    sb.Append("Update result:");
    sb.Append(updateResult.ToString());
    sb.Append("\n");
    sb.Append("Old RowVersion:");
    }
    foreach (var x in product.RowVersion.AsEnumerable())
    {
    sb.Append(x.ToString());
    sb.Append(" ");
    }
    result = sb.ToString();
    }
    catch (Exception ex)
    {
    result = "Exception: " + ex.Message;
    }
    return result;
    }

  10. Change the method, btnUpdatePrice_Click, to call the new service if the checkbox is checked.The new method should be like this:
  11. private void btnUpdatePrice_Click(object sender, RoutedEventArgs
    e)
    {
    if (product1 == null)
    {
    txtUpdate1Results.Text = "Get product details first";
    }
    else if (chkRemote.IsChecked == false && product2 == null ||
    chkRemote.IsChecked == true && remoteProduct == null)
    {
    txtUpdate2Results.Text = "Get product details first";
    }
    else
    {
    bool update1Result = false, update2Result = false;
    using (TransactionScope ts = new TransactionScope())
    {
    txtUpdate1Results.Text = UpdatePrice(
    txtNewPrice1, ref product1, ref update1Result);
    if(chkRemote.IsChecked == true)
    txtUpdate2Results.Text = UpdateRemotePrice(
    txtNewPrice2, ref remoteProduct,ref update2Result);
    else
    txtUpdate2Results.Text = UpdatePrice(
    txtNewPrice2, ref product2, ref
    update2Result);
    if (update1Result == true && update2Result == true)
    ts.Complete();
    }
    }
    }

Testing the WCF service with two databases

Now let's run the program to test the distributed transaction support of the WCF service with two databases.

Follow these steps for this test:

  1. Press Ctrl + F5 to start the client application.
  2. Check the checkbox, Get and Update 2nd Product in Remote Database.
  3. Enter 30 and 31 as product IDs in the top two textboxes.
  4. Click on the Get Product Details button to get product details for product ID 30 and 31. Note that product 31's details are now retrieved from the remote database. Product 30's price should be 28.89 and product 31's price should be still 12.5 in the remote database.
    If you get an exception like Exception: An error occurred while executing the command definition. See the inner exception for details. in the second product details textbox, make sure you have specified the connection string in the Web.config file of the new WCF service and make sure you have added the RowVersion column in the Products table of the remote Northwind database.

    If there is a firewall on the remote machine make sure you have the SQL Server port open so that your client application can connect to it through the firewall. The SQL Server port number should be 1433 by default.

    If you see the price for product 31 is not 12.5 but 13.5, it is likely that you didn't check the remote database checkbox. For this test we need to involve the remote database so you need to check the remote database checkbox and again click on the button, Get Product Details, before you continue the test.

  5. Enter 29.89 and -14.5 as the new prices in the middle two textboxes and click on the button, Update Price.
  6. The update result for the first product should be True and for the second product should be False. This means the second product in the remote database has not been updated.
  7. Click on the Get Product Details button to refresh the product details so that we can verify the update results.

Just as in the previous test we know that the second service call fails due to the invalid price so the second update is not committed to the database. From the refreshed product details, we know this is true (product 31's price didn't change).However from the refreshed product details we also know that the first update of the first service call has been committed to the remote database (product 30's price has been changed). This means that the first call to the service is not rolled back even when a subsequent service call has failed. Each service call is in a separate standalone transaction. In other words, the two sequential service calls are not within one distributed transaction.

Enabling distributed transaction support

we verified that the WCF service currently does not support distributed transactions irrespective of whether these are two sequential calls to the same service or two sequential calls to two different services, either with one database or with two databases.

In the following article, I will explain how to allow this WCF service to support distributed transactions. We will allow the WCF service to participate in the client transaction. From another point of view, I will explain how to flow or propagate a client transaction across the service boundaries so that the client can include service operation calls on multiple services in the same distributed transaction.

In the following article, I will explain how to allow this WCF service to support distributed transactions. We will allow the WCF service to participate in the client transaction. From another point of view, I will explain how to flow or propagate a client transaction across the service boundaries so that the client can include service operation calls on multiple services in the same distributed transaction.

For more information about WCF transaction support you can visit the MSDN MCF transaction support site at http://msdn.microsoft.com/enus/library/ms730266.aspx

.

Enabling transaction flow in service binding

The first thing that we need to pay attention to is the bindings. the three elements of a WCF service end point are the address,the binding, and the contract (WCF ABC). Although the address has nothing to do with the distributed transaction support the other two elements do.

We know that WCF supports several different bindings but not all of these bindings are capable of propagating a transaction across service boundaries. A transaction can only be propagated from a client application into a WCF service with the following bindings: NetTcpBinding, NetNamedPipeBinding , WSHttpBinding ,WSDualHttpBinding , and WSFederationHttpBinding . In this article, we will use WSHttpBinding as our example.

However using a transaction-aware binding doesn't mean that a transaction will be propagated to the service. The transaction propagation is disabled by default and we have to enable it manually. Unsurprisingly, the attribute to enable transaction flow in the bindings is called transactionFlow.

In the following article, we will do the following to enable the transaction propagation:

  • Use WSHttpBinding on the host application as binding
  • Set the value of the transactionFlow attribute to true on the host application binding configuration

Enabling transaction flow on the service hosting application

In this article, we will enable transaction flow in bindings for both ProductService and RemoteProductService.

  1. In Solution Explorer, open the web.config file under the folder C:\SOAWithWCFandLINQ\Projects\DistNorthwind\DistNorthwindService.
  2. Change the following line:

    <endpoint address="" binding="wsHttpBinding"
    contract="MyWCFServices.DistNorthwindService.IProductService">

    To this line:

    <endpoint address="" binding="wsHttpBinding"
    contract="MyWCFServices.DistNorthwindService.IProductService"
    bindingConfiguration="transactionalWsHttpBinding">

  3. Add the following node to the web.config file inside the node,system.serviceModel,and in parallel with node services:

    <bindings>
    <wsHttpBinding>
    <binding name="transactionalWsHttpBinding"
    transactionFlow="true" receiveTimeout="00:10:00"
    sendTimeout="00:10:00" openTimeout="00:10:00"
    closeTimeout="00:10:00" />
    </wsHttpBinding>
    </bindings>

  4. Make the same changes to the web.config file under the folder C:\SOAWithWCFandLINQ\Projects\DistNorthwind\DistNorthwindRemoteService.

In the above configuration files we have verified and left the bindings for both ProductService and RemoteProductService to wsHttpBinding and set the attribute, transactionFlow, of the binding to true. This will enable distributed transaction support from the WCF service binding side.

Modifying the service operation contract to allow a transaction flow

Now the service is able to participate in a propagated transaction from the client application but the client is still not able to propagate a distributed transaction into the service. Before we enable the distributed transaction support from the client side,we need to make some more changes to the service side code, that is, modify the service operation to opt in to participate in a distributed transaction. By default, it is opted out.

Two things need to be done in order to allow an operation to participate in a propagated transaction. The first thing is to enable the transaction flow in operation contracts. Follow these steps to enable this option:

  • Open the IProductServiceContract.cs file under the DistNorthwindService project.
  • Add the following line before the UpdateProduct method:[TransactionFlow(TransactionFlowOption.Allowed)]

In the above code we set TransactionFlowOption in the UpdateProduct operation to be Allowed. This means a transaction can be propagated from the client to this operation.

The three transaction flow options for a WCF service operation are Allowed,NotAllowed, and Mandatory, as shown in the following table:

Option

Description

NotAllowed

A transaction should not be flowed; this is the default value

Allowed

Transaction may be flowed

Mandatory

Transaction must be flowed

Modifying the service operation implementation to require a transaction scope

The second thing we need to do is to specify the TransactionScopeRequired behavior for the service operation. This has to be done on the service implementation project.

  • Open the ProductService.cs file under the DistNorthwindService project.
  • Add the following line before the UpdateProduct method: [OperationBehavior(TransactionScopeRequired = true)]

The TransactionScopeRequired attribute means that, for the UpdateProduct method, the whole service operation will always be executed inside one transaction. If a transaction is propagated from the client application this operation will participate in this existing distributed transaction. If no transaction is propagated a new transaction will be created and this operation will be running within this new transaction.

If you are interested, you can examine the ambient transaction inside the WCF service (Transaction.Current), and compare it with the ambient transaction of the client to see if they are the same. You can also examine the TransactionInformation property of the ambient transaction object to see if it is a local transaction (TransactionInformation.LocalIdentifier) or a distributed transaction (TransactionInformation.DistributedIdentifier).

Getting back to our example, we now need to regenerate the service proxy and the configuration files on the client project because we have changed the service interfaces. However, in your real project, you shouldn't change any service interface.Once it goes live you should version your service and allow the client applications to migrate to the new versions of the service. To simplify our example we will just update the proxy and configuration files and recompile our client application.

These are the steps to regenerate the configuration and proxy files:

  • Rebuild the solution. As we have set up the post-build event for the DistNorthwindService project to copy all assembly files to two IIS directories, both ProductService and RemoteProductService now should contain the latest assemblies with distributed transaction support enabled.
  • In Solution Explorer, right-click on RemoteProductServiceProxy under the Service References directory of the DistNorthwindWPF project.
  • Select Update Service Reference from the context menu.
  • Right-click on ProductServiceProxy under the Service References directory of the DistNorthwindWPF project.
  • Select Update Service Reference from the context menu.

Open the App.config file under the DistNorthwindWPF project. You will find that the transactionFlow attribute is now populated as true because the code generator finds that some operations in the service now allow transaction propagation.

Summary

The key points discussed in this article include:

  • Only certain bindings allow transactions to flow from the client to the WCF service using the transactionFlow attribute
  • A WCF service operation contract can opt to participate in a propagated transaction using the TransactionFlow attribute
  • A WCF service operation can specify its transaction behavior using the TransactionScopeRequired attribute

If you have read this article you may be interested to view:

WCF 4.0 Multi-tier Services Development with LINQ to Entities Build SOA applications on the Microsoft platform with this hands-on WCF 4.0 book and eBook guide updated for VS2010
Published: June 2010
eBook Price: $29.99
Book Price: $49.99
See more
Select your format and quantity:

About the Author :


Mike Liu

Mike Liu studied Mathematics and Software Engineering at Nanjing University and Brandeis University, graduated with a bachelor’s degree and a master's degree respectively. He is a Sun Certified Java Programmer (SCJP), Microsoft Certified Solution Developer for Visual Studio 6.0 and Microsoft Certified Solution Developer for .NET. He has been working as a software Engineer/architect on various platforms (DOS, Unix, and Windows) using C/C++, Java, VB/VB.NET, and C#.

Mike started using C# for production development back in 2001 when C# was still in beta stage and he is now working as a senior software engineer for an investment management firm in Boston, Mass.

Mike had his first book,MITT: Multi-user Integrated Table-processing Tool Under Unix,published in 1993, and a second book,Advanced C# Programming,published in 2003. The previous two versions of this book,WCF Multi-tier Services Development with LINQ and WCF 4.0 Multi-tier Services Development with LINQ to Entities,were published in 2008 and 2010.

Books From Packt


Joomla! 1.5: Beginner's Guide
Joomla! 1.5: Beginner's Guide

Refactoring with Microsoft Visual Studio 2010
Refactoring with Microsoft Visual Studio 2010

Microsoft Silverlight 4 Data and Services Cookbook
Microsoft Silverlight 4 Data and Services Cookbook

Microsoft Silverlight 4 Business Application Development: Beginner’s Guide
Microsoft Silverlight 4 Business Application Development: Beginner’s Guide

Programming Microsoft Dynamics NAV 2009
Programming Microsoft Dynamics NAV 2009

Microsoft Dynamics AX 2009 Programming: Getting Started
Microsoft Dynamics AX 2009 Programming: Getting Started

Oracle JRockit: The Definitive Guide
Oracle JRockit: The Definitive Guide

Microsoft Dynamics AX 2009 Development Cookbook
Microsoft Dynamics AX 2009 Development Cookbook


Code Download and Errata
Packt Anytime, Anywhere
Register Books
Print Upgrades
eBook Downloads
Video Support
Contact Us
Awards Voting Nominations Previous Winners
Judges Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software
Resources
Open Source CMS Hall Of Fame CMS Most Promising Open Source Project Open Source E-Commerce Applications Open Source JavaScript Library Open Source Graphics Software