接続の回復Connection Resiliency

接続の回復は、失敗したデータベース コマンドを自動的に再試行します。Connection resiliency automatically retries failed database commands. 機能は、「実行方式」、障害を検出して、コマンドを再試行するためのロジックをカプセル化されているを指定することによって、任意のデータベースで使用できます。The feature can be used with any database by supplying an "execution strategy", which encapsulates the logic necessary to detect failures and retry commands. EF コア プロバイダーは、特定のデータベースの障害、最適な再試行ポリシーに合わせて実行ストラテジを指定できます。EF Core providers can supply execution strategies tailored to their specific database failure conditions and optimal retry policies.

たとえば、SQL Server プロバイダーには、SQL Server (SQL Azure を含む) に特別にカスタマイズされている実行方法が含まれています。As an example, the SQL Server provider includes an execution strategy that is specifically tailored to SQL Server (including SQL Azure). 再試行できる例外の種類を認識し、最大再試行回数、再試行などの間の遅延の実用的な既定値を持ちます。It is aware of the exception types that can be retried and has sensible defaults for maximum retries, delay between retries, etc.

実行方法は、コンテキストのオプションを構成するときに指定します。An execution strategy is specified when configuring the options for your context. これは通常、OnConfiguringメソッド、または、派生のコンテキストのStartup.csASP.NET Core アプリケーションです。This is typically in the OnConfiguring method of your derived context, or in Startup.cs for an ASP.NET Core application.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseSqlServer(
            @"Server=(localdb)\mssqllocaldb;Database=EFMiscellanous.ConnectionResiliency;Trusted_Connection=True;ConnectRetryCount=0",
            options => options.EnableRetryOnFailure());
}

カスタムの実行方法Custom execution strategy

既定値のいずれかを変更する場合、独自のカスタムの実行方法を登録するメカニズムがあります。There is a mechanism to register a custom execution strategy of your own if you wish to change any of the defaults.

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    optionsBuilder
        .UseMyProvider(
            "<connection string>",
            options => options.ExecutionStrategy(...));
}

実行の戦略とトランザクションExecution strategies and transactions

エラー発生時に自動的に再試行する実行方法は、失敗した再試行ブロック内の各操作を再生できる必要があります。An execution strategy that automatically retries on failures needs to be able to play back each operation in an retry block that fails. 再試行を有効にすると、EF コアを使用して実行する各操作操作になります独自再試行可能なつまり各クエリへの各呼び出しSaveChanges()単位として、一時的なエラーが発生した場合は再試行されます。When retries are enabled, each operation you perform via EF Core becomes its own retriable operation, i.e. each query and each call to SaveChanges() will be retried as a unit if a transient failure occurs.

ただし、コードを使用してトランザクションを開始する場合BeginTransaction()を単位として扱う必要のある操作のグループを定義する、つまり、トランザクション内のすべて必要がありますを再生できるものと、エラーが発生します。However, if your code initiates a transaction using BeginTransaction() you are defining your own group of operations that need to be treated as a unit, i.e. everything inside the transaction would need to be played back shall a failure occur. 実行方法を使用するときに実行しようとすると、次のような例外が表示されます。You will receive an exception like the following if you attempt to do this when using an execution strategy.

InvalidOperationException: 構成済みの実行方法 'SqlServerRetryingExecutionStrategy' はユーザーが開始したトランザクションをサポートしていません。InvalidOperationException: The configured execution strategy 'SqlServerRetryingExecutionStrategy' does not support user initiated transactions. 'DbContext.Database.CreateExecutionStrategy()' によって返される実行方法を使用して、再試行可能な単位としてトランザクション内のすべての操作を実行します。Use the execution strategy returned by 'DbContext.Database.CreateExecutionStrategy()' to execute all the operations in the transaction as a retriable unit.

ソリューションでは、手動で実行する必要があるすべてのものを表すデリゲートを使用して、実行方法を呼び出します。The solution is to manually invoke the execution strategy with a delegate representing everything that needs to be executed. 一時的な障害が発生した場合、実行方法は、デリゲートをもう一度呼び出されます。If a transient failure occurs, the execution strategy will invoke the delegate again.

using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    strategy.Execute(() =>
    {
        using (var context = new BloggingContext())
        {
            using (var transaction = context.Database.BeginTransaction())
            {
                context.Blogs.Add(new Blog {Url = "http://blogs.msdn.com/dotnet"});
                context.SaveChanges();

                context.Blogs.Add(new Blog {Url = "http://blogs.msdn.com/visualstudio"});
                context.SaveChanges();

                transaction.Commit();
            }
        }
    });
}

トランザクションのコミット エラーおよびべき等問題Transaction commit failure and the idempotency issue

一般に、接続エラーが発生したときに、現在のトランザクションはロールバックされます。In general, when there is a connection failure the current transaction is rolled back. ただし場合は、トランザクション中に、接続が削除されたがコミット、その結果、トランザクションの状態が不明です。However, if the connection is dropped while the transaction is being committed the resulting state of the transaction is unknown. これを参照してくださいブログの投稿詳細についてはします。See this blog post for more details.

既定では、実行方法、操作を再試行、トランザクションがロールバックがケースではない場合この例外が発生する場合は、新しいデータベースの状態は、互換性がないか、発生する可能性データの破損場合、操作は、たとえば、自動生成されたキー値を持つ新しい行を挿入するときに、特定の状態に依存しません。By default, the execution strategy will retry the operation as if the transaction was rolled back, but if it's not the case this will result in an exception if the new database state is incompatible or could lead to data corruption if the operation does not rely on a particular state, for example when inserting a new row with auto-generated key values.

これに対処するいくつかの方法はあります。There are several ways to deal with this.

オプション 1 - Do nothing (ほとんど)Option 1 - Do (almost) nothing

トランザクションのコミット中に接続エラーの可能性は低いに失敗し、この条件が実際に発生した場合にアプリケーションで許容される場合があります。The likelihood of a connection failure during transaction commit is low so it may be acceptable for your application to just fail if this condition actually occurs.

ただし、重複する行を追加する代わりに例外がスローされることを確認するためにストア生成のキーを使用しないようにする必要があります。However, you need to avoid using store-generated keys in order to ensure that an exception is thrown instead of adding a duplicate row. クライアントで生成された GUID 値またはクライアント側の値ジェネレーターを使用してください。Consider using a client-generated GUID value or a client-side value generator.

オプション 2 - アプリケーションの状態を再構築Option 2 - Rebuild application state

  1. 現在の破棄DbContextです。Discard the current DbContext.
  2. 新しいDbContextとデータベースからのアプリケーションの状態を復元します。Create a new DbContext and restore the state of your application from the database.
  3. 最後の操作が完了していないが正常にユーザーに通知します。Inform the user that the last operation might not have been completed successfully.

オプション 3 - 状態の検証を追加しますOption 3 - Add state verification

データベースの状態を変更する操作のほとんどの成功したかどうかを確認するコードを追加することができます。For most of the operations that change the database state it is possible to add code that checks whether it succeeded. これを簡単に拡張メソッドを提供する EFIExecutionStrategy.ExecuteInTransactionです。EF provides an extension method to make this easier - IExecutionStrategy.ExecuteInTransaction.

このメソッドは、開始し、トランザクションをコミットし、関数で指定することも、verifySucceededトランザクションのコミット中に一時的なエラーが発生したときに呼び出されるパラメーター。This method begins and commits a transaction and also accepts a function in the verifySucceeded parameter that is invoked when a transient error occurs during the transaction commit.

using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    var blogToAdd = new Blog {Url = "http://blogs.msdn.com/dotnet"};
    db.Blogs.Add(blogToAdd);

    strategy.ExecuteInTransaction(db,
        operation: context =>
        {
            context.SaveChanges(acceptAllChangesOnSuccess: false);
        },
        verifySucceeded: context => context.Blogs.AsNoTracking().Any(b => b.BlogId == blogToAdd.BlogId));

    db.ChangeTracker.AcceptAllChanges();
}

注意

ここでSaveChangesが呼び出されるとacceptAllChangesOnSuccessに設定falseの状態が変更されないように、BlogエンティティをUnchanged場合SaveChangesは成功します。Here SaveChanges is invoked with acceptAllChangesOnSuccess set to false to avoid changing the state of the Blog entity to Unchanged if SaveChanges succeeds. これにより、コミットが失敗するし、トランザクションはロールバック、同じ操作を再試行してください。This allows to retry the same operation if the commit fails and the transaction is rolled back.

オプション 4 - 手動でトランザクションの追跡Option 4 - Manually track the transaction

ストア生成のキーを使用するか、実行される操作に依存せずにコミットのエラー処理の一般的な方法を必要する必要がある場合の各トランザクションはコミットに失敗したときにチェックインされている ID 割り当てでした。If you need to use store-generated keys or need a generic way of handling commit failures that doesn't depend on the operation performed each transaction could be assigned an ID that is checked when the commit fails.

  1. トランザクションの状態を追跡するために使用されるデータベースにテーブルを追加します。Add a table to the database used to track the status of the transactions.
  2. 各トランザクションの先頭にテーブルに行を挿入します。Insert a row into the table at the beginning of each transaction.
  3. コミット中に、接続に失敗した場合は、データベース内の対応する行の存在を確認します。If the connection fails during the commit, check for the presence of the corresponding row in the database.
  4. コミットが成功した場合は、テーブルの増加を避けるために、対応する行を削除します。If the commit is successful, delete the corresponding row to avoid the growth of the table.
using (var db = new BloggingContext())
{
    var strategy = db.Database.CreateExecutionStrategy();

    db.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });

    var transaction = new TransactionRow {Id = Guid.NewGuid()};
    db.Transactions.Add(transaction);

    strategy.ExecuteInTransaction(db,
        operation: context =>
        {
            context.SaveChanges(acceptAllChangesOnSuccess: false);
        },
        verifySucceeded: context => context.Transactions.AsNoTracking().Any(t => t.Id == transaction.Id));

    db.ChangeTracker.AcceptAllChanges();
    db.Transactions.Remove(transaction);
    db.SaveChanges();
}

注意

検証に使用するコンテキストに、接続がトランザクションのコミット中に失敗した場合、検証時に再び失敗する可能性が定義されている実行方法があることを確認してください。Make sure that the context used for the verification has an execution strategy defined as the connection is likely to fail again during verification if it failed during transaction commit.