連線復原能力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();
            }
        }
    });
}

交易認可失敗且等冪性問題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-執行 (幾乎) 任何項目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叫用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

如果您需要使用存放區所產生的金鑰,或需要一般的方式來處理認可失敗不會相依於執行的作業,每一筆交易,可以指派認可失敗時,會檢查的識別碼。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.