連接恢復力
連線韌性會自動重試失敗的資料庫命令。 此功能可藉由提供「執行策略」來與任何資料庫搭配使用,其會封裝偵測失敗和重試命令所需的邏輯。 EF Core 提供者可以提供針對其特定資料庫失敗狀況和最佳重試原則量身打造的執行策略。
例如,SQL Server 提供者包含專為 SQL Server 量身打造的執行策略(包括 SQL Azure)。 它知道可以重試的例外狀況類型,並針對重試次數上限、重試之間的延遲等有合理的預設值。
當配置您上下文的選項時,會指定一個執行策略。 這通常位於衍生內容的 OnConfiguring
方法中:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFMiscellanous.ConnectionResiliency;Trusted_Connection=True;ConnectRetryCount=0",
options => options.EnableRetryOnFailure());
}
或在 ASP.NET Core 應用程式的 Startup.cs
中:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<PicnicContext>(
options => options.UseSqlServer(
"<connection string>",
providerOptions => providerOptions.EnableRetryOnFailure()));
}
注意
啟用失敗重試會導致 EF 將結果集暫存於內部,這可能會顯著增加查詢傳回大型結果集所需的記憶體需求。 如需詳細資訊,請參閱 緩衝處理和串流。
自訂執行策略
如果您想要變更任何預設值,有一個機制可以自行註冊自定義執行策略。
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseMyProvider(
"<connection string>",
options => options.ExecutionStrategy(...));
}
執行策略和交易
在失敗時自動重試的執行策略,必須能夠重新執行失敗重試區塊中的每個作業。 啟用重試時,您透過EF Core 執行的每個作業都會變成它自己的可重試作業。 也就是說,如果發生暫時性失敗,每個查詢和對 SaveChangesAsync()
的呼叫都會作為一個單元進行重試。
不過,如果您的代碼使用 BeginTransactionAsync()
來起始交易,您會定義一個需要視作整體的操作組,而且交易內的所有專案出現失敗時都需要重執行。 如果您在使用執行策略時嘗試這樣做,將會收到如下的例外狀況:
InvalidOperationException:已設定的執行策略 'SqlServerRetryingExecutionStrategy' 不支持使用者起始的交易。 使用由 'DbContext.Database.CreateExecutionStrategy()' 傳回的執行策略,將交易中的所有作業作為可以重試的單位來執行。
解決方案是手動呼叫執行策略,並使用委派來表示所有需要執行的內容。 如果發生暫時性失敗,執行策略將會再次叫用委派。
using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(
async () =>
{
using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
await context.SaveChangesAsync();
await transaction.CommitAsync();
});
此方法也可以與環境交易搭配使用。
using var context1 = new BloggingContext();
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
var strategy = context1.Database.CreateExecutionStrategy();
await strategy.ExecuteAsync(
async () =>
{
using var context2 = new BloggingContext();
using var transaction = new TransactionScope();
context2.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context2.SaveChangesAsync();
await context1.SaveChangesAsync();
transaction.Complete();
});
交易認可失敗和等冪問題
一般而言,發生連線失敗時,會回復目前的交易。 不過,如果在認可交易時連線中斷,則交易狀態為未知。
根據預設,執行策略會重試作業,就像是復原交易一樣,但如果不是這種情況,如果新的資料庫狀態不相容,則會導致 數據損毀, 如果作業不依賴特定狀態,例如插入具有自動產生索引鍵值的新數據列時。
有幾種方式可以處理這個問題。
選項 1 - 不執行任何動作 (幾乎)
在交易提交期間連線失敗的可能性很低,因此如果實際出現此狀況,讓您的應用程式直接失敗可能是可以接受的。
不過,您必須避免使用存放區產生的索引鍵,以確保擲回例外狀況,而不是新增重複的數據列。 請考慮使用客戶端產生的 GUID 值或用戶端值產生器。
選項 2 - 重建應用程式狀態
- 捨棄目前的
DbContext
。 - 建立新的
DbContext
,並從資料庫還原應用程式的狀態。 - 通知使用者,最後一項作業可能尚未順利完成。
選項 3 - 新增狀態驗證
對於變更資料庫狀態的大部分作業,可以新增檢查它是否成功的程序代碼。 EF 提供擴充方法,以簡化這個過程 - IExecutionStrategy.ExecuteInTransaction
。
此方法開始並提交交易,並且接受一個函式作為 verifySucceeded
參數,在交易提交期間發生暫時錯誤時會叫用該函式。
using var db = new BloggingContext();
var strategy = db.Database.CreateExecutionStrategy();
var blogToAdd = new Blog { Url = "http://blogs.msdn.com/dotnet" };
db.Blogs.Add(blogToAdd);
await strategy.ExecuteInTransactionAsync(
db,
operation: (context, cancellationToken) => context.SaveChangesAsync(acceptAllChangesOnSuccess: false, cancellationToken),
verifySucceeded: (context, cancellationToken) => context.Blogs.AsNoTracking().AnyAsync(b => b.BlogId == blogToAdd.BlogId, cancellationToken));
db.ChangeTracker.AcceptAllChanges();
注意
此處會叫用 SaveChanges
,並將 acceptAllChangesOnSuccess
設為 false
,以避免在 SaveChanges
成功時,將 Blog
實體的狀態變更為 Unchanged
。 這可以在提交失敗且交易被回滚時重試相同的作業。
選項 4 - 手動追蹤交易
如果您需要使用儲存生成的密鑰,或需要一種通用方式來處理提交失敗,而不依賴於執行的操作,可以對每個交易分配一個在提交失敗時進行檢查的ID。
- 將數據表新增至資料庫,以追蹤交易的狀態。
- 在每個交易的開頭在表格中插入一行。
- 如果提交期間連接失敗,請檢查資料庫中對應的資料列是否存在。
- 如果認可成功,刪除對應的資料列,以避免資料表成長。
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);
await strategy.ExecuteInTransactionAsync(
db,
operation: (context, cancellationToken) => context.SaveChangesAsync(acceptAllChangesOnSuccess: false, cancellationToken),
verifySucceeded: (context, cancellationToken) => context.Transactions.AsNoTracking().AnyAsync(t => t.Id == transaction.Id, cancellationToken));
db.ChangeTracker.AcceptAllChanges();
db.Transactions.Remove(transaction);
await db.SaveChangesAsync();
注意
確保用於驗證的環境已經定義了執行策略,因為如果連線在交易提交期間失敗,那麼在驗證期間很可能再次失敗。