3 回答

TA貢獻1872條經(jīng)驗 獲得超4個贊
你分享了嗎DbContext
?DbContext 不是線程安全的。
嘗試將插入操作包裝在 的using塊中DbContext,而不是重試:
using(var context = new DbContext)
{
? // Insert operation here
}
這種沖突很容易理解,但首先你需要知道,當你await
調用時,線程立即返回到調用者。
想象一下這個場景,您有兩個線程正在運行您的代碼。這是執(zhí)行順序:
線程 1:
FirstOrDefault
返回null
.線程 2:
FirstOrDefault
返回null
.線程 1:
Add
運行。SQL 生成并在數(shù)據(jù)庫服務器上排隊。主題 1:
await context.SaveChangesAsync()
.?呼叫立即完成。數(shù)據(jù)庫:線程 1 的調用已完成。
線程2:
Add
運行。SQL 生成并在數(shù)據(jù)庫服務器上排隊。主題 2:
await context.SaveChangesAsync()
.?呼叫立即完成。數(shù)據(jù)庫:嘗試從線程 2 進行調用,但無法完成它,因為之前插入了具有相同鍵值的行。

TA貢獻2051條經(jīng)驗 獲得超10個贊
如果數(shù)據(jù)庫中有一條記錄作為val1
鍵但val2
不同,firstOrDefault()
則不會返回值,并且您仍然無法插入新記錄。
這也可能是緩存問題。您可以嘗試添加AsNoTracking()
到您的查詢中。

TA貢獻2039條經(jīng)驗 獲得超8個贊
重試不起作用,因為一旦您將條目添加到上下文并收到?jīng)_突錯誤,條目仍標記為已插入,因此您將在所有進一步的重試中嘗試插入它。您需要使用新的上下文或將其分離才能使重試起作用。
交易
如果您想確保在嘗試查找記錄時沒有人可以添加記錄,那么您需要使用事務:
using (var context = new MyContext())
using (var transaction = context.Database.BeginTransaction(IsolationLevel.Serializable)) {
? ? ? ? var saved = context.Table.FirstOrDefault(x => x.field1 == val1 && x.field2 == val2);
? ? ? ? if (saved != null)
? ? ? ? {
? ? ? ? ? ? //edits saved
? ? ? ? }
? ? ? ? else
? ? ? ? {
? ? ? ? ? ? context.Table.Add(new Table
? ? ? ? ? ? {
? ? ? ? ? ? ? ? field1 = val1,
? ? ? ? ? ? ? ? field2 = val2
? ? ? ? ? ? });
? ? ? ? }
? ? ? ? await context.SaveChangesAsync();
? ? ? ? transaction.Commit()
? ? ? ? return Json(true);
}
我在這里使用最隔離的級別來鎖定表并防止讀取時的競爭條件。此方法會對性能產生影響,如果可以接受重試,您仍然可以遵循此方法。
更新插入
如果您擁有新實體所需的所有數(shù)據(jù),那么您可以使用FlexLabs.Upsert?-update
或者insert
將在單個事務中執(zhí)行,這樣您就不會再發(fā)生沖突。
重試
請注意,如果更新不是冪等的,您可能仍然存在競爭條件,但現(xiàn)在您將其移至數(shù)據(jù)庫端:2 個線程找到一個項目,單獨更新并保存。您可以按照本文所述使用并發(fā)令牌來避免此類沖突。請記住,如果您堅持重試選項,更新必須是冪等的,這意味著無論有多少線程都會更新實體 - 它將與第一次更新后相同。
有一個很棒的框架Polly.NET對您來說非常方便:
await?Policy.Handle<DbUpdateException>() ????????????.RetryAsync(5) ????????????.ExecuteAsync(()?=>?...);
我不建議在 DbContext (或其他任何東西)上使用任何進程內鎖,因為這會限制您使用此邏輯運行單個進程,而當您需要高可用性時,情況并非如此。
- 3 回答
- 0 關注
- 255 瀏覽
添加回答
舉報