Procedimiento para crear un servicio transaccional
Este ejemplo muestra varios aspectos sobre la creación de un servicio transaccional y el uso de una transacción iniciada por el cliente para coordinar las operaciones de servicio.
Creación de un servicio transaccional
Cree un contrato de servicios y anote las operaciones con el valor deseado de la enumeración TransactionFlowOption para especificar los requisitos de las transacciones entrantes. Tenga en cuenta que también puede colocar TransactionFlowAttribute en la clase de servicio que se va a implementar. Esto permite una implementación única de una interfaz para utilizar estos valores de transacción, en lugar de cada implementación.
[ServiceContract] public interface ICalculator { [OperationContract] // Use this to require an incoming transaction [TransactionFlow(TransactionFlowOption.Mandatory)] double Add(double n1, double n2); [OperationContract] // Use this to permit an incoming transaction [TransactionFlow(TransactionFlowOption.Allowed)] double Subtract(double n1, double n2); }
Cree una clase de implementación y utilice ServiceBehaviorAttribute para especificar opcionalmente TransactionIsolationLevel y TransactionTimeout. Debería tener en cuenta que, en muchos casos, el valor predeterminado de TransactionTimeout de 60 segundos y el de TransactionIsolationLevel de
Unspecified
son adecuados. Para cada operación, puede utilizar el atributo OperationBehaviorAttribute para especificar si el trabajo realizado dentro del método debería producirse dentro del ámbito de la transacción según el valor del atributo TransactionScopeRequired. En este caso, la transacción utilizada para el métodoAdd
es igual que la transacción entrante obligatoria que fluye desde el cliente y la transacción utilizada para el métodoSubtract
es igual que la transacción entrante si una fluye desde el cliente, o una transacción nueva creada implícitamente y localmente.[ServiceBehavior( TransactionIsolationLevel = System.Transactions.IsolationLevel.Serializable, TransactionTimeout = "00:00:45")] public class CalculatorService : ICalculator { [OperationBehavior(TransactionScopeRequired = true)] public double Add(double n1, double n2) { // Perform transactional operation RecordToLog($"Adding {n1} to {n2}"); return n1 + n2; } [OperationBehavior(TransactionScopeRequired = true)] public double Subtract(double n1, double n2) { // Perform transactional operation RecordToLog($"Subtracting {n2} from {n1}"); return n1 - n2; } private static void RecordToLog(string recordText) { // Database operations omitted for brevity // This is where the transaction provides specific benefit // - changes to the database will be committed only when // the transaction completes. } }
Configure los enlaces en el archivo de configuración, especificando que debería fluir el contexto de la transacción, y los protocolos que se van a utilizar para ello. Para más información, consulte Configuración de transacciones de ServiceModel. Concretamente, se especifica el tipo de enlace en el atributo
binding
del elemento del punto de conexión. El elemento <endpoint> contiene un atributobindingConfiguration
que hace referencia a una configuración de enlace denominadatransactionalOleTransactionsTcpBinding
, tal y como se muestra en la configuración de ejemplo siguiente.<service name="CalculatorService"> <endpoint address="net.tcp://localhost:8008/CalcService" binding="netTcpBinding" bindingConfiguration="transactionalOleTransactionsTcpBinding" contract="ICalculator" name="OleTransactions_endpoint" /> </service>
El flujo de la transacción está habilitado en el nivel de configuración mediante el atributo
transactionFlow
y el protocolo de transacción se especifica mediante el atributotransactionProtocol
, tal y como se muestra en la configuración siguiente.<bindings> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
Compatibilidad de varios protocolos de transacción
Para un rendimiento óptimo, debe usar el protocolo OleTransactions en escenarios donde un cliente y un servicio se escriben con Windows Communication Foundation (WCF). Sin embargo, el protocolo WS-AtomicTransaction (WS-AT) es útil para los escenarios cuando se requiere la interoperabilidad con pilas de protocolo de otro fabricante. Puede configurar servicios WCF para aceptar ambos protocolos proporcionando varios puntos de conexión con los enlaces específicos del protocolo adecuados, tal y como se muestra en la configuración de ejemplo siguiente.
<service name="CalculatorService"> <endpoint address="http://localhost:8000/CalcService" binding="wsHttpBinding" bindingConfiguration="transactionalWsatHttpBinding" contract="ICalculator" name="WSAtomicTransaction_endpoint" /> <endpoint address="net.tcp://localhost:8008/CalcService" binding="netTcpBinding" bindingConfiguration="transactionalOleTransactionsTcpBinding" contract="ICalculator" name="OleTransactions_endpoint" /> </service>
El protocolo de transacción se especifica utilizando el atributo
transactionProtocol
. Sin embargo, este atributo está ausente delwsHttpBinding
proporcionado por sistema, ya que este enlace solo puede utilizar el protocolo WS-AT.<bindings> <wsHttpBinding> <binding name="transactionalWsatHttpBinding" transactionFlow="true" /> </wsHttpBinding> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
Control de la realización de una transacción
De forma predeterminada, las operaciones de WCF completan automáticamente las transacciones si no se producen excepciones no controladas. Puede modificar este comportamiento mediante la propiedad TransactionAutoComplete y el método SetTransactionComplete. Cuando es necesario que se produzca una operación dentro de la misma transacción como otra operación (por ejemplo, una operación de débito y crédito), puede deshabilitar el comportamiento de autocompletar definiendo la propiedad TransactionAutoComplete en
false
tal y como se muestra en el ejemplo de operaciónDebit
siguiente. La transacción que la operaciónDebit
utiliza no se completa hasta que se llama a un método con la propiedad TransactionAutoComplete definida entrue
, tal y como se muestra en la operaciónCredit1
o cuando se llama al método SetTransactionComplete para marcar explícitamente la transacción como completada, tal y como se muestra en la operaciónCredit2
. Tenga en cuenta que las dos operaciones de crédito se muestran para fines informativos, y que una operación de crédito única sería más típica.[ServiceBehavior] public class CalculatorService : IAccount { [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = false)] public void Debit(double n) { // Perform debit operation return; } [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = true)] public void Credit1(double n) { // Perform credit operation return; } [OperationBehavior( TransactionScopeRequired = true, TransactionAutoComplete = false)] public void Credit2(double n) { // Perform alternate credit operation OperationContext.Current.SetTransactionComplete(); return; } }
Para la correlación de transacción, establecer la propiedad TransactionAutoComplete en
false
requiere el uso de un enlace con sesión. Este requisito se especifica con la propiedadSessionMode
en ServiceContractAttribute.[ServiceContract(SessionMode = SessionMode.Required)] public interface IAccount { [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Debit(double n); [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Credit1(double n); [OperationContract] [TransactionFlow(TransactionFlowOption.Allowed)] void Credit2(double n); }
Control de la duración de una instancia de servicio transaccional
WCF usa la propiedad ReleaseServiceInstanceOnTransactionComplete para especificar si se libera la instancia del servicio subyacente cuando se completa una transacción. Puesto que el valor predeterminado es
true
, a menos que se configure lo contrario, WCF muestra un comportamiento de activación "Just-In-Time" eficaz y predecible. Las llamadas a un servicio en una transacción subsiguiente aseguran una nueva instancia del servicio sin restos del estado de la transacción anterior. Aunque esto resulta a menudo útil, a veces puede querer mantener el estado dentro de la instancia del servicio más allá de la realización de la transacción. Algunos ejemplos de ello serían cuándo resulta costoso recuperar o volver a constituir el estado necesario o los controladores a los recursos. Puede hacer esto al definir la propiedad ReleaseServiceInstanceOnTransactionComplete enfalse
. Con ese valor, la instancia y cualquier estado asociado estarán disponibles en llamadas subsiguientes. Al utilizar esto, proporcione la consideración adecuada a cuándo y cómo se borrarán y completarán las transacciones y el estado. El ejemplo siguiente muestra cómo hacer esto manteniendo la instancia con la variablerunningTotal
.[ServiceBehavior(TransactionIsolationLevel = [ServiceBehavior( ReleaseServiceInstanceOnTransactionComplete = false)] public class CalculatorService : ICalculator { double runningTotal = 0; [OperationBehavior(TransactionScopeRequired = true)] public double Add(double n) { // Perform transactional operation RecordToLog($"Adding {n} to {runningTotal}"); runningTotal = runningTotal + n; return runningTotal; } [OperationBehavior(TransactionScopeRequired = true)] public double Subtract(double n) { // Perform transactional operation RecordToLog($"Subtracting {n} from {runningTotal}"); runningTotal = runningTotal - n; return runningTotal; } private static void RecordToLog(string recordText) { // Database operations omitted for brevity } }
Nota
Desde que la duración de la instancia es un comportamiento que es interno al servicio y controlado a través de la propiedad ServiceBehaviorAttribute, no se requiere ninguna modificación en la configuración de servicio ni en el contrato de servicio para establecer el comportamiento de la instancia. Además, la conexión no contendrá ninguna representación de esto.