2016-02-08 35 views
0

我有一個映射到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. 線程#1啓動一個SQL事務並開始運行上面的代碼。
  2. 線程#2啓動一個SQL事務並開始運行上面的代碼(使用不同的DBContext)。
  3. 線程#1獲取要刪除的對象。
  4. 上下文切換。
  5. 線程#2獲取要刪除的對象 - 它們將具有#1讀取的相同ID。
  6. 線程#2將對象標記爲已刪除並調用SaveChanges,它發出SQL DELETE語句。
  7. 線程#2提交它的事務並完成。
  8. 線程#1,使用它的(現在陳舊的)列表,將那些相同的對象標記爲已刪除並調用SaveChanges,它發出SQL DELETE語句。
  9. 例外!實體拋出這個:

商店更新,插入或刪除語句影響行的意外數字(0)。

現在,即使我將事務隔離級別一直變爲可序列化,這也無濟於事。 「可序列化」指定語句不能讀取已被修改但尚未被其他事務處理的數據。但問題發生在第5步,在那時沒有任何修改。啊,但是Serializable還指定,在當前事務完成之前,沒有其他事務可以修改當前事務讀取的數據。好的,所以在上面的順序中,線程#2將在步驟6中阻塞。但是,這只是轉移問題而不解決問題。無論哪個線程是第二個嘗試刪除的線程都會遇到'意外的行數'異常。

我看不到任何預防 - 只有治療。似乎我必須吞下異常或關閉實體框架的保存驗證。而且由於我使用的是ASP.NET Boilerplate,所以這看起來不太方便。我很困惑,那些顯然簡單而典型的東西不容易控制。我錯過了什麼?

+0

是否所有對'repository'的調用都通過相同的'Context'實例進行,無論線程如何? –

+0

不,每個線程都有自己的'repository'和'Context'。 – CarlJ

回答

1
var objectsToDelete = repository.GetAll().Where(r => r.parentId == param1).ToList(); 
if(objectsToDelete.Count!=0) 
{ 
    repository.RemoveRange(objectsToDelete); 
} 
... 
repository.SaveChanges(); 
+0

我不明白這有何幫助。我們仍然存在這樣的問題:在獲得objectsToDelete之後,另一個線程可以刪除它們並在調用之前調用SavesChanges。當你調用SaveChanges時,你會得到意外的行數異常。 – CarlJ