5

概念的には次のような WCF 操作があります。

    [OperationBehavior(TransactionScopeRequired = true)]
    public void Foo() 
    {
        try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); }
        catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
    }

DAL コードの実行中に何か問題が発生した場合 (外部キー制約違反など)、期待どおりに制御が catch ブロックに渡されます。しかし、メソッドが戻ると、トランザクション スコープはトランザクションが失敗したことを嗅ぎつけたようで、呼び出し元に確実に通知するために例外をスローする方がよいと判断します。

次に、クライアント アプリケーションは、返そうとしたレシートを取得せず、例外を取得します。

System.ServiceModel.FaultException:  
  The transaction under which this method call was executing was asynchronously aborted.

私のデザインのどこが悪いのですか?

サービスが何もキャッチしないようにすることもできますが、サービスが例外シールドを使用する必要があり、クライアント (システム内部のバッチ ツール) がエラー情報をログに記録する必要があるため、これには独自の問題があります。サービスもエラーをログに記録しますが、同じ方法ではなく、バッチと同じ場所に記録します。

4

2 に答える 2

7

ここで注意してください!TransactionAutoComplete=true を設定すると、サービスが正常に戻った場合、トランザクションはコミットされます。未処理の例外 (例外をキャッチして受信メッセージを返すため、ほとんどの場合、例外はありません) がある場合にのみ、トランザクションはロールバックされます。http://msdn.microsoft.com/en-us/library/system.servicemodel.operationbehaviorattribute.transactionautocomplete.aspxを参照してください。

いくつかの DAL 呼び出しを正常に実行したが、他の例外 (NullReferenceException など) が発生したシナリオについて考えてみてください。未処理の例外は発生していませんが、クライアントは ErrorReceipt を受け取るため、メソッドが完了するとトランザクションがコミットされます。

あなたのシナリオでは、トランザクションを自分で管理する必要があると思います。例えば:

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public Receipt Foo() 
{   
    // Create TransactionScope using the ambient transaction
    using (var scope = new TransactionScope() )
    {
        try { DAL.Foo(); return Receipt.CreateSuccessReceipt(); scope.Complete(); }
        catch (Exception ex) { return Receipt.CreateErrorReceipt(ex); }
    }
}

トランザクション内にすべてをラップするヘルパー メソッドを作成することでボイラープレート コードを削除するか、ポリシー インジェクション/インターセプト/アスペクトを使用してトランザクションを管理することができます。

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = false)]
public Receipt Foo() 
{   
    return ProcessWithTransaction(() =>
        {
            DAL.Foo();
            return Receipt.CreateSuccessReceipt();
        }
        , (ex) =>
        {
            return Receipt.CreateErrorReceipt(ex);
        }
    );
}

T ProcessWithTransaction<T>(Func<T> processor, Func<Exception, T> exceptionHandler)
{
    using (var scope = new TransactionScope())
    {
        try
        {
            T returnValue = processor();
            scope.Complete();
            return returnValue;
        }
        catch (Exception e)
        {
            return exceptionHandler(e);
        }
    }
}

例外シールドを使用する必要があると述べています。エラーが発生したときにフォールトをスローすることを嫌わない場合は、Enterprise Library Exception Handling Block の例外シールドを使用して、途中で情報をログに記録することもできます (必要な場合)。

そのルートに進むことにした場合、コードは次のようになります。

[OperationBehavior(TransactionScopeRequired = true)]
public void Foo() 
{
    // Resolve the default ExceptionManager object from the container.
    ExceptionManager exManager = EnterpriseLibraryContainer.Current.GetInstance<ExceptionManager>();

    exManager.Process(() => 
      {
          DAL.Foo(); 
          return Receipt.CreateSuccessReceipt(); 
      }, 
      "ExceptionShielding");
}

その後、エンタープライズ ライブラリは (構成を介して) 例外をキャッチし、クライアントに返される新しい FaultException に置き換えます。

于 2012-08-11T04:52:48.063 に答える
0
[OperationBehavior(TransactionAutoComplete = true, TransactionScopeRequired = true)]

おそらく、スコープがスコープ外になったときに非同期ではなく、エラーが発生するとすぐにトランザクションがロールバックされるためです:D、これは、元の動作を期待したように動作し、設計をそのままにしておくことができます。

(これを試してみたとき、私はすでに質問を書いていました。うまくいけば、質問をまったく投稿しないよりも、Q&Aスタイルで投稿する方が役に立ちます。)

于 2012-08-10T11:06:09.997 に答える