WCF:TransactionAbortedException,由于DTC在TransactionScope.Complete()上停止事务

时间:2014-12-12 04:47:15

标签: c# .net wcf transactions

我正在学习WCF,特别是分布式事务。 我的项目包括一个调用第一级服务的客户端应用程序,它本身称为第二级服务。 因为我处于开发阶段,所以它们都在同一台机器上运行。这两个服务都在与.Net framework 4.0相同的IIS 8上运行,并在SQL Server 2012上使用相同的数据库(我尝试在2个不同的SQL服务器上使用2个不同的数据库,但结果是相同的)。 / p>

让我们忘记客户端应用程序,它不参与故障情况。 当第一级服务(我将其命名为"客户端")在分布式事务中调用第二级服务(我将其命名为"服务")时,会出现问题:

客户代码:

    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 < 1)
                        throw new Exception("Error during account debit..");

                    ret2 = new BoCard().UpdateCardBalanceAddAmount(
                        pCardIdTo,
                        pAccountTransactionAmount);
                    if (ret2 < 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 < 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

服务代码:

[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 < 1)
                throw new Exception("Error during insert into transactions history.");

            return accountTransactionId;
        }
        catch (Exception ex)
        {
            ThrowFaultException("InsertAccountTransaction()", ex);
            return null;
        }
    }
}

在事务作用域中调用的三个业务对象方法中的每一个都执行不同的存储过程,处理到其各自的更新或插入。

我有证据表明在事务范围内调用的三个命令成功,并且没有达到超时。但是在TransactionScope.Dispose()的那一刻,结束&#34;使用&#34;部分,MS DTC停止交易并继续回滚,这是相当混乱......

这是日志,其中包含异常的堆栈跟踪:

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& 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)

如果我用简单的&#34; return 1;&#34;替换服务的InsertAccountTransaction()方法的代码,我仍然得到相同的例外。

如果在TransactionScope using部分中,我只是保持对这个远程方法的调用并删除所有其余的,结果再次相同。

如果我不在客户端使用事务范围,并禁止[TransactionFlow(TransactionFlowOption.Mandatory)],[ServiceBehaviorTransactionIsolationLevel = IsolationLevel.Unspecified]]和[OperationBehavior(TransactionScopeRequired = true,TransactionAutoComplete = false)在服务中,它运作良好。

这是我配置DTC的方式:

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

使用以下命令创建证书:

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

这是MSDTC日志文件的摘录:

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"

有人知道我错过了什么吗?

1 个答案:

答案 0 :(得分:0)

我想我修好了它:

我替换了

[OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=false)]

通过

[OperationBehavior(TransactionScopeRequired=true, TransactionAutoComplete=true)]

我只是希望在出现真正的错误时不会产生太多的副作用。