一、前言
首先我們來了解一下什么是並發沖突。
所謂的並發沖突就是,多個線程同時執行一個操作,例如同時修改數據表,導致數據變更后無法正常保存。
並發分為:悲觀並發和樂觀並發
悲觀並發:兩個線程同時修改數據庫的同一張表,A進入修改,B就不能修改,只能等待A改完,B才能進入修改。
樂觀並發:A修改,B也可以修改,如果在A保存之后B再保存他的修改,此時系統檢測到數據庫中記錄與B剛進入時不一致,B保存時會拋出異常,修改失敗。
樂觀並發的基本出發點是:當保存數據的時候抱着一種樂觀的態度,不期望發生並發沖突,即使萬一發生並發沖突,也能捕捉到沖突異常,然后根據策略解決沖突。
EF使用的就是樂觀並發,所以在做數據處理時我們特別注意。
接下來以我自己遇到的問題為例。
二、場景
在我的項目里有一個功能,第一步先刪除數據,再添加數據,然后模擬兩次使用這個功能,很不巧的是模擬的兩次存在操作相同的數據,其中一次能夠操作成功,但是另一次由於前面有寫數據被刪了,導致我第二次再想刪就出現並發沖突了。
調用的時候出現這個錯:Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. See http://go.microsoft.com/fwlink/?LinkId=527962 for information on understanding and handling optimistic concurrency exceptions.
官網給出的解決方案是通過捕獲 DbUpdateConcurrencyException 異常來解決,下面是官網給出的例子
官網:https://docs.microsoft.com/zh-cn/ef/core/saving/concurrency
using (var context = new PersonContext())
{
// Fetch a person from database and change phone number
var person = context.People.Single(p => p.PersonId == 1);
person.PhoneNumber = "555-555-5555";
// Change the person's name in the database to simulate a concurrency conflict
context.Database.ExecuteSqlRaw(
"UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1");
var saved = false;
while (!saved)
{
try
{
// Attempt to save changes to the database
context.SaveChanges();
saved = true;
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
if (entry.Entity is Person)
{
var proposedValues = entry.CurrentValues; //當前值
var databaseValues = entry.GetDatabaseValues(); //數據庫中的值
foreach (var property in proposedValues.Properties)
{
var proposedValue = proposedValues[property];
var databaseValue = databaseValues[property];
// TODO: decide which value should be written to database
// proposedValues[property] = <value to be saved>;
}
// Refresh original values to bypass next concurrency check
entry.OriginalValues.SetValues(databaseValues); //將數據庫中的值更新到內存原始數據中,這里可能會有疑問,為什么不是把當前值跟你進去,這是因為OriginalValues存的是原始值,也就是所有改動之前的數據
}
else
{
throw new NotSupportedException(
"Don't know how to handle concurrency conflicts for "
+ entry.Metadata.Name);
}
}
}
}
}
要根據自己的實際情況去改造,因為我的操作中包含刪除數據,所以GetDatabaseValues會出現為空的情況,所以我采用的是另一種方式
bool saved = false;
while (!saved)
{
try
{
context.SaveChanges();
saved = true;
}
catch(DbUpdateConcurrencyException ex)
{
var s = ex.Entries.Single();
s.Reload(); //刷新數據,直到更新成功
}
}
上段代碼中的Reload方法就是去數據庫查詢一次實體的最新值更新到緩存中,在數據庫中的操作可以看出來

