title: WCF : TransactionAbortedException, due to DTC stopping the transaction on TransactionScope.Complete()
body: <p>I am at the moment learning WCF, and in particular distributed transactions.
  My project consists in a client application calling a 1st-level service, which itself calls a 2nd-level service.
  For I am in the development phase, they all run on the same machine. Both services run on the same IIS 8 with .Net framework 4.0, and use the same database on SQL Server 2012 (I tried to use 2 different DB's on 2 distinct SQL servers, but the result is the same).</p>
  
  <p>Let's forget the client application, which is not in involved in the trouble scenario.
  The problem happens when the 1st-level service (I name it "the client") calls the 2nd-level service (I name it "the service") inside a distributed transaction : </p>
  
  <p>Client code :</p>
  
  <pre><code>    public int? TransferFromAccountToCard(
          int pAccountIdFrom,
          int pCardIdTo,
          decimal pAccountTransactionAmount,
          string pAccountTransactionNote
          )
      {
          int ret1 = -2;
          int ret2 = -2;
          int? accountTransactionId = null;
  
          try
          {
              var account = new BoAccount();
  
              var accountFrom = account.GetAccount(pAccountIdFrom, (int)_BankId);
              /* (here, different checks...) */
  
              var genBankProxy = new WcfGBSContractClient();
              try
              {
                  Log("Login to GeneralBankingService...");
                  var userToGeneralBank = genBankProxy.Login(
                      ConfigurationManager.AppSettings["UserNameToGeneralBank"].ToString(),
                      ConfigurationManager.AppSettings["UserPasswordToGeneralBank"].ToString());
                  Log("... Login to GeneralBankingService done.");
  
                  var trOpts = new TransactionOptions();
                  trOpts.IsolationLevel = IsolationLevel.ReadCommitted;
                  trOpts.Timeout = TimeSpan.FromSeconds(60);
                  using (TransactionScope ts = new TransactionScope(TransactionScopeOption.RequiresNew, trOpts))
                  {
                      ret1 = new BoAccount().UpdateAccountBalanceAddAmount(
                          (int)accountFrom.AccountId,
                          -pAccountTransactionAmount);
                      if (ret1 &lt; 1)
                          throw new Exception("Error during account debit..");
  
                      ret2 = new BoCard().UpdateCardBalanceAddAmount(
                          pCardIdTo,
                          pAccountTransactionAmount);
                      if (ret2 &lt; 1)
                          throw new Exception("Error during card crediting.");
  
                      accountTransactionId = genBankProxy.InsertAccountTransaction(
                          (int)accountFrom.AccountId,
                          null,
                          null,
                          pCardIdTo,
                          pAccountTransactionAmount,
                          pAccountTransactionNote); // The call to the service
                      if (accountTransactionId &lt; 1)
                          throw new Exception("Error during insert into account transaction history.");
  
                      Log("TransactionScope.Complete()...");
                      ts.Complete();
                      Log("... TransactionScope.Complete() done.");
  
                      Log("TransactionScope.Dispose()...");
                  } // Exception here
                  Log("... TransactionScope.Dispose() done.");
              }
              catch
              {
                  Log("Exception happened. Rethrow...");
                  throw;
              }
              finally
              {
                  try
                  {
                      Log("Logout from GeneralBankingService...");                    
                      genBankProxy.Logout();
                      Log("... Logout from GeneralBankingService done.");
                  }
                  catch
                  {
                  }
              }
  
              return accountTransactionId;                                         
          }
          catch (Exception ex)
          {
              ThrowFaultException("Exception in TransferFromAccountToCard(" + pAccountIdFrom + ", " + pCardIdTo + ", " + pAccountTransactionAmount + ", '"
                  + (pAccountTransactionNote == null ? "NULL" : pAccountTransactionNote) + "')."
                  + " ret1 = " + ret1 + ", ret2 = " + ret2 + ", accountTransactionId = " + (accountTransactionId == null ? "NULL" : accountTransactionId.ToString()) + "."
                  , ex);
          }
      } // this is line 583
  </code></pre>
  
  <p>Service code :</p>
  
  <pre><code>[ServiceContract(
      ProtectionLevel = ProtectionLevel.EncryptAndSign,
      SessionMode = SessionMode.Required)]
  public interface IWcfGBSContract
  {
      /* ... */
  
      [OperationContract(
          IsInitiating = false,
          IsTerminating = false,
          ProtectionLevel = ProtectionLevel.EncryptAndSign)]
      [TransactionFlow(TransactionFlowOption.Mandatory)]
      int? InsertAccountTransaction(
          int? pAccountIdFrom,
          int? pCardIdFrom,
          int? pAccountIdTo,
          int? pCardIdTo,
          decimal pAccountTransactionAmount,
          string pAccountTransactionNote);
  }
  
  [ServiceBehavior(
      InstanceContextMode = InstanceContextMode.PerSession, 
      ConcurrencyMode = ConcurrencyMode.Single,
      TransactionIsolationLevel = IsolationLevel.Unspecified)]
  [AspNetCompatibilityRequirements(
      RequirementsMode = AspNetCompatibilityRequirementsMode.Required)]
  public class WcfGeneralBankingSvc : WcfGeneralBankingService.ServiceContract.IWcfGBSContract
  {
      /* ... */
  
      [OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=false)]
      public int? InsertAccountTransaction(
          int? pAccountIdFrom,
          int? pCardIdFrom,
          int? pAccountIdTo,
          int? pCardIdTo,
          decimal pAccountTransactionAmount,
          string pAccountTransactionNote)
      {
          try
          {                
              /* (here, checks and log...) */
  
              var accountTransactionId = new BoAccountTransaction().InsertAccountTransaction(
                  pAccountIdFrom,
                  pCardIdFrom,
                  pAccountIdTo,
                  pCardIdTo,
                  pAccountTransactionAmount,
                  pAccountTransactionNote);
              if (accountTransactionId &lt; 1)
                  throw new Exception("Error during insert into transactions history.");
  
              return accountTransactionId;
          }
          catch (Exception ex)
          {
              ThrowFaultException("InsertAccountTransaction()", ex);
              return null;
          }
      }
  }
  </code></pre>
  
  <p>Each of the three business object methods called in the transaction scope execute a different stored procedure, processing to its respective update or insert. </p>
  
  <p>I have the proof that the three commands called inside the transaction scope succeed, and no timeout is reached. But at the moment of the TransactionScope.Dispose(), ending the "using" section, MS DTC stop the transaction and proceed to a rollback, which is rather confusing...</p>
  
  <p>Here is the log, with the stack trace of the exception :</p>
  
  <pre><code>11/12/2014 19:07:30 - Login to GeneralBankingService...
  11/12/2014 19:07:32 - ... Login to GeneralBankingService done.
  11/12/2014 19:07:33 - TransactionScope.Complete()...
  11/12/2014 19:07:33 - ... TransactionScope.Complete() done.
  11/12/2014 19:07:33 - Exception happened. Rethrow...
  11/12/2014 19:07:34 - Logout from GeneralBankingService...
  11/12/2014 19:07:34 - ... Logout from GeneralBankingService done.
  11/12/2014 19:07:34 - Exception in TransferFromAccountToCard(1, 1, 8, 'Rechargement carte 1'). 
  ret1 = 1, ret2 = 1, accountTransactionId = 3048. /
  EXCEPTION :
  Type : System.Transactions.TransactionAbortedException
  Stack trace :
  at System.Transactions.TransactionStatePromotedAborted.PromotedTransactionOutcome(InternalTransaction tx)
  at System.Transactions.CommittableTransaction.Commit()
  at System.Transactions.TransactionScope.InternalDispose()
  at System.Transactions.TransactionScope.Dispose()
  at WcfBankingService.ServiceImplementation.WcfBankingSvc.TransferFromAccountToCard(Int32 pAccountIdFrom, Int32 pCardIdTo, Decimal pAccountTransactionAmount, String pAccountTransactionNote)
            in d:\AFJ\NET-dev\Projets\Exercices\WCF\Développement\Ex_WCF_1_06 Banking service\V02\WcfBankingService\WcfBankingService\ServiceImplementation\WcfBankingService.cs:ligne 583
  INNER EXCEPTION :
      Type : System.Data.SqlClient.SqlException
      Msg : Microsoft Distributed Transaction Coordinator (MS DTC) has stopped this transaction.
      Class : 18
      State : 1
      ErrorCode : -2146232060
      Errors :
          8522. MONPORTABLE\SQLEXP2012_64  line 1 : Microsoft Distributed Transaction Coordinator (MS DTC) has stopped this transaction.
      Stack trace :
      at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
      at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
      at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean&amp; dataReady)
      at System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
      at System.Data.SqlClient.TdsParser.TdsExecuteTransactionManagerRequest(Byte[] buffer, TransactionManagerRequestType request, String transactionName, TransactionManagerIsolationLevel isoLevel, Int32 timeout, SqlInternalTransaction transaction, TdsParserStateObject stateObj, Boolean isDelegateControlRequest)
      at System.Data.SqlClient.SqlInternalConnectionTds.ExecuteTransactionYukon(TransactionRequest transactionRequest, String transactionName, IsolationLevel iso, SqlInternalTransaction internalTransaction, Boolean isDelegateControlRequest)
      at System.Data.SqlClient.SqlDelegatedTransaction.SinglePhaseCommit(SinglePhaseEnlistment enlistment)
  </code></pre>
  
  <p>If I replace the code of the service's InsertAccountTransaction() method by a simple "return 1;", I still got the same exception.</p>
  
  <p>If, in the TransactionScope using section, I just keep the call to this remote method and remove all the rest, the result is again the same.</p>
  
  <p>If I just don't use the transaction scope in the client, and suppress [TransactionFlow(TransactionFlowOption.Mandatory)], [ServiceBehaviorTransactionIsolationLevel = IsolationLevel.Unspecified)] and [OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=false)] in the service, then it works well.</p>
  
  <p>This is how I configured my DTC :</p>
  
  <pre><code>Security :
    Security Settings
      Network DTC Access : On
        Client and Administration
           Allow remote clients : Off
           Allow remote administration : Off
        Transaction Manager Communication
           Allow Inbound : On
           Allow Outbound : On
           No Authentification Required
      Enable XA Transactions : Off
      Enable SNA LU 6.2 Transactions : On
    DTC Logon Accont : NT AUTHORITY\Network Services
  
  WS-AT :
    Enable WS-Atomic Transaction network support : On
    Network
      HTTPS port : 443
      Endpoint certificate : CN=MyFullDomainName
    Timeouts
      Default outgoing timeout : 60 seconds
      Maximum incoming timeout : 3600 seconds
  </code></pre>
  
  <p>Certificate was created using those commands :</p>
  
  <pre><code>makecert.exe -pe -n CN=MyCN -cy authority -r -sv C:\Mycer.pvk C:\Mycer.cer
  makecert.exe -ss Root -sr LocalMachine -n CN=MyCN -cy authority -r -sv C:\Mycer.pvk
  makecert -ss My -sr LocalMachine -n CN=MyFullDomainName -sky exchange -ir LocalMachine -iv C:\Mycer.pvk -ic C:\Mycer.cer
  </code></pre>
  
  <p>And this is an extract from MSDTC log file :</p>
  
  <pre><code>eventid=TRANSACTION_BEGUN                        ;"transaction has begun, description :'user_transaction'"
  eventid=RM_ENLISTED_IN_TRANSACTION               ;"resource manager #1001 enlisted as transaction enlistment #1. RM guid = '172f0548-da12-4b2d-bbe3-77f39820fd7a'"
  eventid=RECEIVED_COMMIT_REQUEST_FROM_BEGINNER    ;"received request to commit the transaction from beginner"
  eventid=TRANSACTION_ABORTING                     ;"transaction is aborting"
  eventid=RM_ISSUED_ABORT                          ;"abort request issued to resource manager #1001 for transaction enlistment #1"
  eventid=RM_ACKNOWLEDGED_ABORT                    ;"received acknowledgement of abort request from the resource manager #1001 for transaction enlistment #1"
  eventid=TRANSACTION_ABORTED                      ;"transaction has been aborted"
  </code></pre>
  
  <p>Has somebody a little idea of what I am missing  ???</p>