接続の回復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 派生コンテキストのメソッドにあります。This is typically in the OnConfiguring method of your derived context:

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

Startup.csASP.NET Core アプリケーションの場合:or in Startup.cs for an ASP.NET Core application:

public void ConfigureServices(IServiceCollection services)
{
    services.AddDbContext<PicnicContext>(
        options => options.UseSqlServer(
            "<connection string>",
            providerOptions => providerOptions.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() が1つの単位として再試行されます。That is, each query and each call to SaveChanges() will be retried as a unit if a transient failure occurs.

ただし、コードがを使用してトランザクションを開始する場合、 BeginTransaction() 1 つの単位として処理する必要がある独自の操作グループを定義する場合は、トランザクション内のすべてのものを再生して、エラーが発生する必要があります。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.

既定では、トランザクションがロールバックされた場合と同様に操作が再試行されますが、その場合、新しいデータベースの状態に互換性がない場合、または自動生成されたキー値を使用して新しい行を挿入する場合など、操作が特定の状態に依存してい ない場合は 、例外が発生します。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-(ほぼ) 何もしない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();
}

注意

SaveChangesacceptAllChangesOnSuccess に設定されて 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.

その他の技術情報Additional resources