1

我正在嘗試處理近似同步輸入到我的實體框架應用程序。會員(用戶)可以對事情進行評分,所以我有一張表格來顯示他們的評分,其中一列是會員的ID,一個是他們評分的事物的ID,一個是評分,另一個是他們評分的時間。最近的評級應該會覆蓋較早的評級。當我收到輸入信息時,會檢查會員是否已經評估了某件事物,如果他們有,我只是使用現有行更新評級,或者如果他們沒有,我添加一個新行。我注意到,當幾乎同一時間來自同一用戶的同一用戶的輸入時,我最終得到兩個用於同一用戶的評分。如何處理無法插入後更新行的唯一約束異常?

早些時候,我問了這個問題:How can I avoid duplicate rows from near-simultaneous SQL adds?和我按照建議添加了一個SQL約束,要求MemberID和ThingID的唯一組合,這是有道理的,但我無法使這種技術工作,可能是因爲我沒有知道發生異常時我想要做的事情的語法。出現異常說約束被違反了,我想要做的是忘記非法添加具有相同MemberID和ThingID的行,取而代之的是現有的,並簡單地將值設置爲稍微更近一點數據。然而,我還沒有能夠提出一個可以做到這一點的語法。我嘗試了一些東西,並且在得到異常後嘗試SaveChanges時總是遇到異常 - 唯一約束仍然出現,或者出現死鎖異常。

我試過的最新版本是這樣的:

// Get the member's rating for the thing, or create it. 
Member_Thing_Rating memPref = (from mip in _myEntities.Member_Thing_Rating 
           where mip.thingID == thingId 
           where mip.MemberID == memberId 
           select mip).FirstOrDefault(); 
bool RetryGet = false; 
if (memPref == null) 
{ 
    using (TransactionScope txScope = new TransactionScope()) 
    { 
     try 
     { 
      memPref = new Member_Thing_Rating(); 
      memPref.MemberID = memberId; 
      memPref.thingID = thingId; 
      memPref.EffectiveDate = DateTime.Now; 
      _myEntities.Member_Thing_Rating.AddObject(memPref); 
      _myEntities.SaveChanges(); 
     } 
     catch (Exception ex) 
     { 
      Thread.Sleep(750); 
      RetryGet = true; 
     } 
    } 
    if (RetryGet == true) 
    { 
     Member_Thing_Rating memPref = (from mip in _myEntities.Member_Thing_Rating 
             where mip.thingID == thingId 
             where mip.MemberID == memberId 
             select mip).FirstOrDefault(); 
    } 
} 

寫了上之後,我也試過包裝在一個函數調用的邏輯,因爲它看起來像實體框架離開作用域時清理數據庫事務提交更改的地方。因此,而不是使用的TransactionScope和管理如上在同一水平異常的,我包了整個事情的管理功能裏面,像這樣:

bool Succeeded = false; 
while (Succeeded == false) 
{ 
    Thread.Sleep(750); 
    Exception Problem = AttemptToSaveMemberIngredientPreference(memberId, ingredientId, rating); 
    if (Problem == null) 
     Succeeded = true; 
    else 
    { 
     Exception BaseEx = Problem.GetBaseException(); 
    } 
} 

但是,這不僅導致異常的唯一約束無休止的字符串,在上層功能中被永遠處理。我在兩次嘗試之間有3/4秒的延遲,所以我很驚訝可以有一個報告的衝突,但是當我查詢一行時仍然沒有發現任何東西。我想這意味着所有的線程都失敗了,因爲它們同時運行,Entity Framework會通知它們全部並在所有線程成功之前全部失敗。所以我想應該有辦法通過查看所有意見書並對其進行調整來應對例外情況?我不知道或看到它的語法。那麼,怎麼處理呢?

更新: 帕迪在下面提出了三個很好的建議。我希望他的存儲過程技術能夠解決這個問題,但我仍然對這個問題的答案感興趣。也就是說,當然可以通過操縱提交來響應這個異常,但我還沒有找到讓它插入一行並使用最新值的語法。

回答

1

引用Eric Lippert的話,「如果疼,就停止做吧」。如果您預計會得到非常高的體積,並且您想要執行「插入或更新」,那麼您可能需要考慮在存儲過程中處理此操作,而不是使用上述方法。

您的問題即將到來是因爲您調用數據庫以檢查是否存在以及插入/更新之間存在小差距。

sproc可以使用MERGE在表格上進行一次插入或更新操作,保證您只會看到一行的評分,並且它將成爲您收到的最新更新。


注 - 您可以在EF模型中包含sproc,並使用類似的EF語法調用它。


注意事項2 - 查看您的代碼,在異常情況下,在休眠線程之前不要回滾事務作用域。這是一個相對較長的時間來進行交易,尤其是當您預計交易量很大時。您可能需要更新您的代碼是這樣的:

try 
    { 
     memPref = new Member_Thing_Rating(); 
     memPref.MemberID = memberId; 
     memPref.thingID = thingId; 
     memPref.EffectiveDate = DateTime.Now; 
     _myEntities.Member_Thing_Rating.AddObject(memPref); 
     _myEntities.SaveChanges(); 
     txScope.Complete(); 
    } 
    catch (Exception ex) 
    { 
     txScope.Dispose(); 
     Thread.Sleep(750); 
     RetryGet = true; 
    } 

這可能就是爲什麼你好像當你重試從死鎖的痛苦,特別是如果你正在快速併發請求。

+0

謝謝。明智的話。我已經想過處理這種情況的其他方法,但是因爲有人建議我這樣做,所以我很好奇怎麼做到這一點。知道如何在不中止的情況下實際處理異常似乎很有用。但是,是的,我認爲使用互斥鎖或者只允許多行並在稍後清理它們會起作用,並且對我來說更容易。 – Dronz

+0

也感謝您的Note 2補充!我現在會嘗試一下... – Dronz

+0

這是一個很好的觀點,所以非常感謝你的語法,但是當我按照你的建議測試出來的時候,我仍然會在嘗試之後反覆得到唯一的約束異常重新提交。因此,Dispose()似乎不會導致遺漏提交到異常中的提交,也不會提升函數調用級別。必須有一些方法來獲取和操作未完成的更改(我已經使用LINQ完成了這項工作),但正如你先說的,看起來更容易退出約束並只處理代碼中的額外行。 – Dronz

相關問題