WCF Transaction Propagation or Transaction Flow
Transactions are the key to building robust, high-quality service-oriented applications. Windows® Communication Foundation provides simple, declarative transaction support for service developers, enabling you to configure parameters such as transaction flow and voting, all outside the scope of your service. In addition, Windows Communication Foundation allows client applications to create transactions and to propagate( or Maybe we can calling Transaction transmition) them across service boundaries. In this column, I take a closer look at configuring transaction propagation and voting in Windows Communication Foundation and the resulting programming models.
Transaction-aware binding. (Which I call any binding that is capable of propagating the client’s transaction to the service (if configured to do so)) Specifically, only NetTcpBinding, NetNamedPipeBinding, WSHttpBinding, WSDualHttpBinding, and WSFederationHttpBinding are transaction-aware. By default, transaction-aware bindings do not propagate transactions. The reason is that, like most everything else in Windows Communication Foundation, it is an opt-in setting.
To propagate a transaction, you must explicitly enable it at the binding on both the service host and the client sides. All transaction-aware bindings offer the Boolean property TransactionFlow, which defaults to false, as shown here:
public class NetTcpBinding : Binding,... { public bool TransactionFlow {get;set;} //More members }
Can be config in your config file.
<bindings> <netTcpBinding> <binding name = “TransactionalTCP” transactionFlow = “true” /> </netTcpBinding> </bindings>
To enable propagation, simply set this property to true, either programmatically or in the host config file. For example, when using a config file this code snippet will set the property.
Note that the value of the TransactionFlow property is not published in the service metadata. If you use Visual Studio® 2005 to generate the client config file, *you will still need to enable transaction flow manually in client side.(Step 1 enable binding to support transaction propagation.)
public enum TransactionFlowOption { Allowed, NotAllowed, Mandatory } [AttributeUsage(AttributeTargets.Method)] public sealed class TransactionFlowAttribute : Attribute,IOperationBehavior //Notice this is use the OperationBehavior. { public TransactionFlowAttribute(TransactionFlowOption flowOption); }
Note that the TransactionFlow attribute is a method-level attribute because Windows Communication Foundation insists that the decision on transaction flow be made on a per-operation level:
[ServiceContract] interface IMyContract { [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void MyMethod(...); }
This is to enable the granularity of having some methods that use the client’s transaction and some that do not.
The value of the TransactionFlow attribute is included in the published metadata of the service. When you import a contract definition, the imported definition will contain the configured value. These attributes are TransactionFlowOption.NotAllowed, TransactionFlowOption.Allowed, and TransactionFlowOption.Mandatory.
TransactionFlowOption.NotAllowed
When the operation is configured to disallow transaction flow via the attribute TransactionFlowOption.NotAllowed, the client cannot propagate its transaction to the service. Even if transaction flow is enabled at the binding and the client has a transaction, it will be silently ignored and not propagate to the service. As a result, the service will never use the client’s transaction, and the service and the client can select any binding with any configuration. TransactionFlowOption.NotAllowed is the default TransactionFlowOption value of the TransactionFlow attribute.
TransactionFlowOption.Allowed
1.When choosing TransactionFlowOption.Allowed, if the client has a transaction, then the service will allow the client’s transaction to flow across the service boundary. However, the service may or may not use the client’s transaction even though it was propagated.
2.When choosing TransactionFlowOption.Allowed, the service can be configured to use any binding, transaction-aware or not,
3.the client and the service must be compatible in their binding configuration. The service operation allows transaction flow, but the binding disallows it, Then the client should also disallow it in the binding on its side. Trying to flow the client transaction will cause an error because the transaction information in the message will not be understood by the service.
4.when the service-side binding configuration is set to allow transaction flow, the client may or may not want to enable propagation on its side, and so may elect to set TransactionFlow to false in the binding even if the service has it set to true. (so the service may be the root of transaction)
TransactionFlowOption.Mandatory
When the operation is configured for TransactionFlowOption.Mandatory, the service and client must use a transaction-aware binding with transaction flow enabled. Windows Communication Foundation verifies this requirement at the service load time and throws an InvalidOperationException if the service has at least one incompatible endpoint. TransactionFlowOption.Mandatory means the client must have a transaction to propagate to the service. Trying to call a service without a transaction throws an exception on the client. With mandatory flow, the client’s transaction always propagates to the service. Once again, the service may or may not use the client’s transaction.
Propagating the client transaction to the service requires, by its very nature, allowing the service to abort the client transaction. This implies that you cannot flow the client transaction to a service over a one-way operation because that call does not have a reply message. Windows Communication Foundation validates this at service load time and throws an exception when a one-way operation is configured for anything but TransactionFlowOption.NotAllowed(one way is not allowed in other two options):
//Invalid definition: [ServiceContract] interface IMyContract { [OperationContract(IsOneWay = true)] [TransactionFlow(TransactionFlowOption.Allowed)] void MyMethod(...); }
The Ambient Transaction
The Transaction class from the System.Transactions namespace is used to represent a Windows Communication Foundation transaction:
[Serializable] public class Transaction : IDisposable,ISerializable { public static Transaction Current {get;set;} public TransactionInformation TransactionInformation {get;} public void Dispose(); //More members }
To obtain a reference to the ambient transaction, call the static Current property of Transaction:
Transaction ambientTransaction = Transaction.Current;
If there is no ambient transaction, Current returns null. Every piece of code, be it client or service, can always reach out for its ambient transaction. The ambient transaction object is stored in thread-local storage. As a result, when the thread winds its way across multiple objects and methods on the same call chain, all objects and methods can access their ambient transaction.
In the context of Windows Communication Foundation, the ambient transaction is paramount. When present, any Windows Communication Foundation resource manager (such as a SQL Server™ or a volatile resource manager) will automatically enlist in the ambient transaction. When a client calls a Windows Communication Foundation service, if the client has an ambient transaction and the binding and the contract are configured to allow transaction flow, the ambient transaction will propagate to the service.
The Transaction class is used both for local and distributed transactions. If no distributed transaction support is required, Windows Communication Foundation will use a local transaction manager. If the client tries to flow its transaction to a service, or if multiple durable resources are involved, the transaction will be promoted to a distributed transaction and managed by the Distributed Transaction Control (DTC). Each transaction has two identifiers used to identify the local and the distributed transaction. You obtain the transaction identifiers by accessing the TransactionInformation property of the Transaction class. TransactionInformation is of the type TransactionInformation defined as follows:
public class TransactionInformation { public Guid DistributedIdentifier {get;} public string LocalIdentifier {get;} //More members }
LocalIdentifier is never null; it represents the local transaction. DistributedIdentifier may be Guid.Empty if the transaction has not been promoted yet to a distributed transaction. The main use of these identifiers is for logging, tracing, and analysis. In this column, I use them as a convenient way in code to demonstrate transaction flow as a result of configuration.
Transactional Service Programming(Step 3 TransactionScopeRequired of OperationBehavior)
The ambient transaction of the service will be null, even though the mandatory transaction flow guarantees the client’s transaction propagation. In order to have an ambient transaction, for each contract method the service must indicate it wants Windows Communication Foundation to scope the body of the method with a transaction using the TransactionScopeRequired property of OperationBehavior attribute, as shown in the following code:
[AttributeUsage(AttributeTargets.Method)] public sealed class OperationBehaviorAttribute : Attribute,... { public bool TransactionScopeRequired {get;set;} //More members }
The default value of TransactionScopeRequired is false, which is why by default the service has no ambient transaction. Setting TransactionScopeRequired to true provides the operation with an ambient transaction:
class MyService : IMyContract { [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod() { Transaction transaction = Transaction.Current; Debug.Assert(transaction != null); } }
If the client transaction is propagated to the service, Windows Communication Foundation will set the client transaction as the operation’s ambient transaction. Otherwise, Windows Communication Foundation will create a new transaction for that operation and set the new transaction as the ambient transaction.
The diagram in Figure 2 demonstrates which transaction a Windows Communication Foundation service uses as a product of the binding configuration, the contract operation and the local operation behavior attribute.
In Figure 2, a non-transactional client calls Service 1. The operation contract is configured with TransactionFlowOption.Allowed. Even though transaction flow is enabled in the binding, since the client has no transaction, no transaction is propagated. The operation behavior on Service 1 is configured to require a transaction scope. As a result, Windows Communication Foundation creates a new transaction for Service 1, Transaction A. Service 1 then calls three other services, each configured differently.
The binding used for Service 2 has transaction flow enabled, and the operation contract mandates the flow of the client transaction. Since the operation behavior is configured to require transaction scope, Windows Communication Foundation sets Transaction A as the ambient transaction for Service 2. The call to Service 3 has the binding and the operation contract disallow transaction flow. However, since Service 3 has its operation behavior require a transaction scope, Windows Communication Foundation creates a new transaction for Service 3 (Transaction B) and sets it as the ambient transaction for Service 3. Similar to Service 3, the call to Service 4 has the binding and the operation contract disallow transaction flow. Since Service 4 does not require a transaction scope, it has no ambient transaction for Service 4.
Binding Transaction Flow | Transaction- FlowOption | Transaction- ScopeRequired | Transaction Mode |
---|---|---|---|
False | Allowed | False | None |
False | Allowed | True | Service |
False | NotAllowed | False | None |
False | NotAllowed | True | Service |
True | Allowed | False | None |
True | Allowed | True | Client/Service |
True | Mandatory | False | None |
True | Mandatory | True | Client |
When the service joins the client transaction, all the work done by the client and the service (and potentially other services the client calls) will be committed or aborted as one atomic operation. If the client does not have a transaction, the service still requires the protection of the transaction, and so this mode provides a contingent transaction to the service, by making it the root of a new transaction. Being the root makes the service control the lifespan of the transaction. Figure 4 shows a service configured for Client/Service transaction. Note that the service asserts it always has a transaction. The service cannot assume or assert whether it is the client’s transaction or a locally created one.
[ServiceContract] interface IMyContract { [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void MyMethod(...); } class MyService : IMyContract { [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod(...) { Transaction transaction = Transaction.Current; Debug.Assert(transaction != null); //true may be the one from client or self-created } }
The Client/Service mode is applicable when the service can be used standalone or it can be used as part of a bigger transaction. When selecting this mode you should be mindful of potential deadlocks. Specifically, if the resulting transaction is a service-side transaction, it may deadlock with other transactions trying to access the same resources because the resources would isolate access per transaction, and the service-side transaction will be a new transaction. When using the Client/Service mode, the service may or may not be the root of the transaction, and the service must not behave differently when it is the root or when it is joining the client’s transaction.
2.Client Transaction
The Client mode ensures the service only uses the client’s transaction. You select the Client transaction mode when, by design, the service must use its client’s transactions and can never be used standalone. The main motivation for this is to avoid deadlocks and maximize overall system consistency. By having the service share the client’s transaction you reduce the potential for a deadlock, because all resources accessed will enlist in the same transaction so there will not be another transaction that competes for access to the same resources and underlying locks. By having a single transaction you maximize consistency because that transaction will commit or abort as one atomic operation. Figure 5 shows a service configured for the Client transaction mode.
[ServiceContract] interface IMyContract { [OperationContract] [TransactionFlow(TransactionFlowOption.Mandatory)] void MyMethod(...); } class MyService : IMyContract { [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod(...) { Transaction transaction = Transaction.Current; Debug.Assert(transaction.TransactionInformation. DistributedIdentifier != Guid.Empty); } }
Note that the method asserts the fact that the ambient transaction is a distributed one, meaning the transaction originated with the client.
3.Service Transaction
[ServiceContract] interface IMyContract { [OperationContract]//Use TransactionFlow default set. void MyMethod(...); } class MyService : IMyContract { [OperationBehavior(TransactionScopeRequired = true)] public void MyMethod(...) { Transaction transaction = Transaction.Current; Debug.Assert(transaction.TransactionInformation. DistributedIdentifier == Guid.Empty); } }
[ServiceContract] interface IMyContract { [OperationContract] void MyMethod(); } class MyService : IMyContract { public void MyMethod() { Transaction transaction = Transaction.Current; Debug.Assert(transaction == null); } }
a service above configured for the None Transaction mode. Note that the service asserts it has no ambient transaction.
[AttributeUsage(AttributeTargets.Method)] public sealed class OperationBehaviorAttribute : Attribute,... { public bool TransactionAutoComplete {get;set;} //More members }
When a non-service client starts a transaction, the transaction ends when the client disposes of the transaction object it uses, typically when disposing of a TransactionScope object. Having the option to create a root transaction scope enables the client to flow its transaction to services and to manage and commit the transaction based on the aggregated result of the services:
using(MyContractClient proxy1 = new MyContractClient()) using(MyOtherContractClient proxy2 = new MyOtherContractClient()) using(TransactionScope scope = new TransactionScope()) { proxy1.MyMethod(...); proxy2.MyOtherMethod(...); scope.Complete(); }
see also :
http://www.codeproject.com/Articles/38793/6-Steps-to-Enable-Transactions-in-WCF
http://blogs.msdn.com/b/chrisforster/archive/2008/04/02/wcf-transaction-flows.aspx
posted on 2012-07-10 22:47 malaikuangren 阅读(867) 评论(0) 编辑 收藏 举报