EFCore 並發沖突


一、前言

首先我們來了解一下什么是並發沖突。

所謂的並發沖突就是,多個線程同時執行一個操作,例如同時修改數據表,導致數據變更后無法正常保存。

並發分為:悲觀並發和樂觀並發

悲觀並發:兩個線程同時修改數據庫的同一張表,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方法就是去數據庫查詢一次實體的最新值更新到緩存中,在數據庫中的操作可以看出來

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM