トランザクションのコミット エラーを処理する

Note

EF6.1 以降のみ - このページで説明する機能、API などは、Entity Framework 6.1 で導入されました。 以前のバージョンを使用している場合、一部またはすべての情報は適用されません。

EF 6.1 では、接続の回復性のための新しい機能が導入されています。トランザクション コミットの受信確認時に一時的な接続エラーを検出して自動的に復旧する機能です。 このシナリオについては、ブログ記事「SQL Database の接続とべき等の問題」で詳細に説明されています。 簡単に言うと、トランザクションのコミット中に例外が発生した場合、次の 2 つの原因が考えられるというものです。

  1. サーバーでトランザクションのコミットが失敗する
  2. サーバーでトランザクションのコミットは成功したものの、接続の問題により、成功の通知がクライアントに届いていない

1 つ目の状況では、アプリケーションまたはユーザーが操作を再試行できます。しかし 2 つ目の状況では、再試行を回避すべきです。アプリケーションが自動的に復旧する可能性があります。 ここで問題となるのが、コミット中に例外がレポートされた実際の理由を検出できなければ、アプリケーションは適切な方針を選べないということです。 EF 6.1 で導入された新機能であれば、EF は、トランザクションが成功しているかどうかのダブルチェックをデータベースに対して行い、適切な方針を透過的に採用することができます。

機能の使用

必要な機能を有効にするには、DbConfiguration のコンストラクターに SetTransactionHandler の呼び出しを追加する必要があります。 DbConfiguration についてまだよくご存じない方は、コード ベースの構成に関するページを参照してください。 この機能を EF6 で導入された自動再試行と組み合わせれば、一時的なエラーが原因でトランザクションが実際にはサーバーでコミットされなかった状況に対応できます。

using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.SqlServer;

public class MyConfiguration : DbConfiguration  
{
  public MyConfiguration()  
  {  
    SetTransactionHandler(SqlProviderServices.ProviderInvariantName, () => new CommitFailureHandler());  
    SetExecutionStrategy(SqlProviderServices.ProviderInvariantName, () => new SqlAzureExecutionStrategy());  
  }  
}

トランザクションの追跡方法

この機能を有効にすると、__Transactions というデータベースに新しいテーブルが自動的に追加されます。 このテーブルには、EF によってトランザクションが作成されるたびに新しい行が挿入されます。また、コミット中にトランザクション エラーが発生した場合は、その行の存在がチェックされます。

不要になった行は、EF によってテーブルから削除されますが、これはベスト エフォートの処理となります。アプリケーションが途中で終了した場合、テーブルが肥大化することがあるので、場合によっては、テーブルを手動で消去する必要があります。

以前のバージョンでコミット エラーを処理する方法

EF 6.1 未満の EF 製品には、コミット エラーを処理するメカニズムがありませんでした。 この状況に関して、以前のバージョンの EF6 に適用できる対処法がいくつかあります。

  • 方法 1 - 何もしない

    トランザクションのコミット中に接続エラーになる可能性は低いので、実際にこの状況が発生しても、アプリケーションでエラーが発生するだけなら許容できることがあります。

  • 方法 2 - データベースを使用して状態をリセットする

    1. 現在の DbContext を破棄します。
    2. 新しい DbContext を作成し、アプリケーションの状態をデータベースから復元します。
    3. 最後の操作が正常に完了していない可能性があることをユーザーに通知します。
  • 方法 3 - トランザクションを手動で追跡する

    1. トランザクションの状態を追跡するためのデータベースに、追跡されていないテーブルを追加します。
    2. 各トランザクションの開始時に、そのテーブルに行を挿入します。
    3. コミット中に接続エラーが発生した場合は、対応する行がデータベースに存在するかどうかをチェックします。
      • その行が存在する場合は、トランザクションが正常にコミットされているため、通常どおり続行します。
      • 行が存在しない場合は、実行戦略を使用して現在の操作を再試行します。
    4. コミットが成功した場合は、テーブルの肥大化を防ぐために、対応する行を削除します。

このブログ記事には、これを SQL Azure で行うためのサンプル コードが掲載されています。