Procedura: creare un servizio transazionale
In questo esempio vengono illustrati vari aspetti della creazione di un servizio transazionale e l'utilizzo di una transazione iniziata dal client per coordinare operazioni del servizio.
Creazione di un servizio transazionale
Creare un contratto di servizio e annotare le operazioni con l'impostazione desiderata dall'enumerazione TransactionFlowOption per specificare i requisiti della transazione in ingresso. Si noti che è anche possibile inserire TransactionFlowAttribute nella classe di servizio implementata. Ciò consente l'utilizzo di queste impostazioni della transazione per una singola implementazione di un'interfaccia, invece che per ogni implementazione.
[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); }
Creare una classe di implementazione e utilizzare ServiceBehaviorAttribute per specificare facoltativamente un TransactionIsolationLevel e un TransactionTimeout. Si noti che in molti casi, l'impostazione TransactionTimeout predefinita di 60 secondi e l'impostazione predefinita TransactionIsolationLevel di
Unspecified
sono appropriate. Per ogni operazione, è possibile utilizzare l'attributo OperationBehaviorAttribute per specificare se il lavoro eseguito all'interno del metodo deve verificarsi all'interno dell'ambito di una transazione in base al valore dell'attributo TransactionScopeRequired. In questo caso, la transazione utilizzata per il metodoAdd
corrisponde alla transazione in ingresso obbligatoria propagata dal client e la transazione utilizzata per il metodoSubtract
corrisponde alla transazione in ingresso se ne è stata propagata una dal client, o a una nuova transazione creata implicitamente e 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. } }
Configurare le associazioni nel file di configurazione, specificando che il contesto della transazione deve essere propagato e i protocolli da utilizzare a tale scopo. Per altre informazioni, vedere Configurazione delle transazioni ServiceModel. In particolare, il tipo di associazione è specificato nell'attributo
binding
dell'elemento endpoint. L'elemento <endpoint> contiene un attributobindingConfiguration
che fa riferimento a una configurazione di binding denominatatransactionalOleTransactionsTcpBinding
, come illustrato nella configurazione di esempio seguente.<service name="CalculatorService"> <endpoint address="net.tcp://localhost:8008/CalcService" binding="netTcpBinding" bindingConfiguration="transactionalOleTransactionsTcpBinding" contract="ICalculator" name="OleTransactions_endpoint" /> </service>
Il flusso delle transazioni viene attivato a livello di configurazione utilizzando l'attributo
transactionFlow
e il protocollo della transazione viene specificato utilizzando l'attributotransactionProtocol
, come illustrato nella configurazione seguente.<bindings> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
Supporto di più protocolli di transazione
Per ottenere prestazioni ottimali, è consigliabile usare il protocollo OleTransactions per scenari che coinvolgono un client e un servizio scritti con Windows Communication Foundation (WCF). Il protocollo WS-AtomicTransaction (WS-AT), tuttavia, è utile per scenari in cui è richiesta l'interoperabilità con stack del protocollo di terze parti. È possibile configurare servizi WCF per accettare entrambi i protocolli fornendo più endpoint con i binding appropriati specifici del protocollo, come illustrato nell'esempio di configurazione seguente.
<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>
Il protocollo di transazione viene specificato utilizzando l'attributo
transactionProtocol
. Questo attributo è tuttavia assente dalwsHttpBinding
fornito dal sistema perché questa associazione può utilizzare solo il protocollo WS-AT.<bindings> <wsHttpBinding> <binding name="transactionalWsatHttpBinding" transactionFlow="true" /> </wsHttpBinding> <netTcpBinding> <binding name="transactionalOleTransactionsTcpBinding" transactionFlow="true" transactionProtocol="OleTransactions"/> </netTcpBinding> </bindings>
Controllo del completamento di una transazione
Per impostazione predefinita, le operazioni WCF completano automaticamente le transazioni, se non viene generata nessuna eccezione non gestita. È possibile modificare questo comportamento utilizzando la proprietà TransactionAutoComplete e il metodo SetTransactionComplete. Quando è necessario che un'operazione si verifichi all'interno della stessa transazione di un'altra operazione (ad esempio, un'operazione di addebito e di accredito), è possibile disattivare il comportamento di completamento automatico impostando la proprietà TransactionAutoComplete su
false
, come illustrato nell'esempio dell'operazioneDebit
seguente. La transazione utilizzata dall'operazioneDebit
non viene completata finché non viene chiamato un metodo con la proprietà TransactionAutoComplete impostata sutrue
, come illustrato nell'operazioneCredit1
o finché non viene chiamato il metodo SetTransactionComplete per contrassegnare in modo esplicito la transazione come completata, come illustrato nell'operazioneCredit2
. Si noti che le due operazioni di accredito sono riportate a solo scopo illustrativo e che una singola operazione sarebbe più tipica.[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; } }
Ai fini della correlazione delle transazioni, per impostare la proprietà TransactionAutoComplete su
false
è richiesto l'utilizzo di un'associazione con sessione. Questo requisito è specificato con la proprietàSessionMode
in 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); }
Controllo della durata di un'istanza del servizio transazionale
WCF usa la proprietà ReleaseServiceInstanceOnTransactionComplete per specificare se l'istanza del servizio sottostante viene rilasciata al completamento di una transazione. Dato che, salvo configurazione diversa, l'impostazione predefinita è
true
, WCF presenta un comportamento di attivazione JIT efficiente e prevedibile. Alle chiamate a un servizio in una transazione successiva viene assicurata una nuova istanza del servizio, senza resti dello stato della transazione precedente. Anche se ciò è spesso utile, qualche volta è necessario mantenere lo stato all'interno dell'istanza del servizio oltre il completamento della transazione, ad esempio quando è oneroso recuperare o ricostruire lo stato richiesto o gli handle di risorse. A tale fine, impostare la proprietà ReleaseServiceInstanceOnTransactionComplete sufalse
. Grazie a tale impostazione, l'istanza e qualsiasi stato associato saranno disponibili alle chiamate successive. In tal caso, valutare attentamente quando e come lo stato e le transazioni verranno cancellati e completati. Nell'esempio seguente viene illustrato come procedere gestendo l'istanza con la variabilerunningTotal
.[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
Poiché la durata dell'istanza è un comportamento interno al servizio e viene controllato tramite la proprietà ServiceBehaviorAttribute, per impostare il comportamento dell'istanza non è richiesta alcuna modifica alla configurazione del servizio o al contratto di servizio. La rete non conterrà inoltre nessuna rappresentazione di tale situazione.