我有一個映射到SQL Server表的EF類。我有以下非常簡單的實體框架代碼(使用過EF的DbContext的ASP.NET樣板庫包裝):加載和刪除對象時實體框架中的併發異常
var objectsToDelete = repository.GetAll().Where(r => r.parentId == param1).ToList();
foreach(var obj in objectsToDelete) {
repository.Delete(obj); //doesn't actually update the database yet.
}
...
repository.SaveChanges();
注意,GETALL通話將立即從數據庫中讀取。
令人驚訝的是,當多個Web服務調用嘗試以相同的param1值同時運行此代碼(想象用戶快速連續執行兩次完全相同的操作)時,這是一個真正的併發頭痛。長話短說,可以在獲取ID和調用SaveChanges之間進行上下文切換。所以這就是發生了什麼(你可以在不影響結果的情況下改變這個順序):
- 線程#1啓動一個SQL事務並開始運行上面的代碼。
- 線程#2啓動一個SQL事務並開始運行上面的代碼(使用不同的DBContext)。
- 線程#1獲取要刪除的對象。
- 上下文切換。
- 線程#2獲取要刪除的對象 - 它們將具有#1讀取的相同ID。
- 線程#2將對象標記爲已刪除並調用SaveChanges,它發出SQL DELETE語句。
- 線程#2提交它的事務並完成。
- 線程#1,使用它的(現在陳舊的)列表,將那些相同的對象標記爲已刪除並調用SaveChanges,它發出SQL DELETE語句。
- 例外!實體拋出這個:
商店更新,插入或刪除語句影響行的意外數字(0)。
現在,即使我將事務隔離級別一直變爲可序列化,這也無濟於事。 「可序列化」指定語句不能讀取已被修改但尚未被其他事務處理的數據。但問題發生在第5步,在那時沒有任何修改。啊,但是Serializable還指定,在當前事務完成之前,沒有其他事務可以修改當前事務讀取的數據。好的,所以在上面的順序中,線程#2將在步驟6中阻塞。但是,這只是轉移問題而不解決問題。無論哪個線程是第二個嘗試刪除的線程都會遇到'意外的行數'異常。
我看不到任何預防 - 只有治療。似乎我必須吞下異常或關閉實體框架的保存驗證。而且由於我使用的是ASP.NET Boilerplate,所以這看起來不太方便。我很困惑,那些顯然簡單而典型的東西不容易控制。我錯過了什麼?
是否所有對'repository'的調用都通過相同的'Context'實例進行,無論線程如何? –
不,每個線程都有自己的'repository'和'Context'。 – CarlJ