接続の復元性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 Core プロバイダーは、特定のデータベース エラー状態と最適な再試行ポリシーに合わせて調整するという実行ストラテジを指定できます。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 a retry block that fails. 再試行が有効な場合、EF Core を使用して実行する各操作は、独自の再試行可能な操作になります。When retries are enabled, each operation you perform via EF Core becomes its own retriable operation. つまり、各クエリ、呼び出しごとにSaveChanges()一時的なエラーが発生した場合、ユニットとして再試行されます。That is, 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, and 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();
            }
        }
    });
}

この方法は、アンビエント トランザクションにも使用できます。This approach can also be used with ambient transactions.

using (var context1 = new BloggingContext())
{
    context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });

    var strategy = context1.Database.CreateExecutionStrategy();

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

                context1.SaveChanges();

                transaction.Complete();
            }
        }
    });
}

トランザクション コミットの失敗、冪等性の問題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. EF - 簡単に確認する拡張メソッドを提供するIExecutionStrategy.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.