In this post we will run the demo developed in the previous posts in the series and analyze the results. The last sections contain conclusions and suggestions for further reading.
The other posts in this series are:
WCF Transactions – Barebones Demo – Overview
WCF Transactions – Barebones Demo – Part 1 (Tools)
WCF Transactions – Barebones Demo – Part 2 (Service Code)
WCF Transactions – Barebones Demo – Part 3 (Client Code)
You can download the source code for the series from here.
Run the Demo (transactionFlow = true)
You will recall that in the configuration files of our projects, we have configured the client and service bindings with the setting transactionFlow = true. This, on a transaction-aware binding such as wsHttpBinding will allow a client transaction to flow from the client to the service.
To see that this is the case, select the Server project as the StartUp Project and press CTL+F5 to Run. Now select the Client project as the StartUp Project and press F5.
Client Side
On the Client console you should see this:
The first section of prints shows us the result of the Run with a local instance of the VolatileResourceManager.
The first Set operation was executed outside the scope of a transaction. (“Set without Transaction”). Then, the code enters the scope of TransactionScope. Both before and after calling Set a second time, the transaction is a local transaction as all resources participating in the transaction are local.
The deliberate exception causes the transaction to abort, a rollback to occur, and, as expected, Get returns the first value that was set and not the second.
The section of prints shows us the result of the Run that uses the WCF service.
We dont see the prints from the VolatileResourceManager because these occur on the service side. But we do see that the transaction initiated on the client begins as a local transaction and after the call to the service is promoted to be a distributed transaction. This is a good indication that the transaction has been propagated across the service boundary and therefore needed to be promoted.
Finally, the deliberate exception on the client side causes a rollback to occur in the service and here also, Get returns the first value that was set and not the second.
Service Side
Let’s take a look at the prints on the service console:
The hosting code in the main of the Service project first prints out the addresses of the endpoints which it is listening on.
Then a Local Transaction begins. This actually occurs during the first call to Set, which was not part of a client transaction. The reason that this occurs is because the Set method has the following decoration:
[OperationBehavior(TransactionScopeRequired = true)]
This setting tells WCF that the implementation of this method must run in the context of a transaction. If the client is transactional and all other settings allow that transaction to flow to the service for this method, then joining the client’s transaction will meet this requirement. But, if the client is not running in a transaction, or it is, but the transaction does not flow to the service for some reason, the service will create its own local transaction for the method. In this case, the client has no transaction, so the service has created its own local transaction.
The local transaction commits at the end of the first Set() because by default, OperationBehavior.TransactionAutoComplete is true.
The next print occurs when the second Set is called in the client code. In this case, the client is executing in the scope of a transaction and all the conditions have been setup to allow the transaction to flow to the service.
We therefore see a distributed transaction on the service side. Note also, that the identifier of this transaction is the same as the one we see on the client after the call returns. Well, that’s really just what we wanted. We wanted the service to join the client’s transaction.
Indeed, this is what happens, and when the client’s transaction aborts, we see a Rollback on the service.
Run the Demo (transactionFlow = false)
Now let’s make a small change.
In the app.config of the server project set transactionFlow to false in the binding settings.
Now run the server using CTRL + F5 and update the service reference on the client side. Just to make sure, verify that transactionFlow is set to false in the client’s app.config too.
Now run the client.
Client Side
This is what you should see on the client console:
As you can see, nothing has changed (except for the identifier values) in the results of the Run that uses the local resource manager.
However, when using the service, we can see that the client transaction was not propagated to the service. This can be seen in two ways:
- Even after the call to the service, the transaction remains local (with the same identifier that it started with). This shows that the call to the service did not cause a promotion of the transaction to a distributed transaction.
- The result stored in the service object is the second result. (“Set 2”). This shows that despite the abort of the transaction on the client, the service operation did not rollback.
Service Side
Now let’s take a look at the service side:
Here you see two local transactions that commit. The first should be no surprise and occurs for the same reason discussed above when transactionFlow was true on the bindings.
The second transaction however, is now also a local transaction. This is because no transaction flowed in from the client so the service created its own local transaction. This transaction auto completed when the method returned and the second setting was commited.
Conclusion
Here are some conclusions from this demo:
- TransactionScopeRequired (of the OperationalBehavior attribute)
When this setting is true, the method must run in the context of a transaction on the server. If the client transaction was allowed to flow across the service boundary, WCF will join that transaction, if not, a local transaction will be created (and autocompleted when the method completes). - TransactionFlow (of some of the Binding classes)
This is a property of the TCP, IPC and WS bindings. When true (the default is false), these bindings will enable a client transaction, if one exists, to flow to the service. - TransactionFlowOption (of the TransactionFlow attribute, set on an operation at the contract level)
In this example, I set this property to the value of TransactionFlowOption.Allowed. I did this so I could experiment with transactionFlow both true and false. Were this value TransactionFlowOption.Mandatory, an exception would be thrown if transactionFlow were false and were the value TransactionFlowOption.NotAllowed, an exception would be thrown if transactionFlow were true.
Further Reading and Experimentation
The three properties that were discussed here (see Conclusion) can be configured in a number of different ways. Some have no meaning, but 4 particular combinations do.
See this link (section 7.6.2) for a description of these modes.
Now that you have the barebones demo up and running, you can experiment with each of these and verify they work as expected.
Enjoy!