連接恢復功能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-不要 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. 捨棄目前DbContextDiscard 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.ExecuteInTransactionEF 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用來叫用acceptAllChangesOnSuccessfalse若要避免變更的狀態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

如果您需要使用存放區產生的索引鍵,或需要執行的作業不相依的處理認可失敗的泛型方法的每個交易無法指派則認可會失敗時,會檢查的識別碼。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.