如何避免多個 Entity Framework 6 instance 造成資料覆蓋問題 (DB First - SQL Server)

前後台分離且共同存取 table 或是同時有多台機器甚至是修改資料不經由 Entity Framework 都是平常開發上很常見的情境,但這些操作卻可能因為 Entity Framework 的 cache 機制而出現資料不一致的現象,最近同事疑似遇到類似問題,雖然立馬就想到解決方式,但已經好一陣子沒用,為避免說錯就自己先測試一下並紀錄紀錄囉

要確保資料的完整及一致性,做法有好幾個包括 DB First、Code First、DB table setting 首先就從最簡單的做法:DB First + table setting + SQL Server 看起

重現問題

  1. user A 開啟特定一筆資料 (name 為 Yowko) 想要修改內容
  2. user B 也想修改同一筆資料 (name 為 Yowko)
  3. 修改情境 (同時開啟資料,僅存檔順序不同)

    • user B 將 salary 改為 100 並存檔,user A 則改為 110 並存檔

      1b100a110

    • user A 將 salary 改為 110 並存檔,user B 則改為 100 並存檔

      2a1101b100

    • user B 將 salary 改為 100 並存檔,user A 則修改 name 為 Yowko Tsai 並存檔

      3b100aname

    • user A 將 name 為 Yowko Tsai 並存檔,user B 則修改 salary 為 100 並存檔

      4anameb100

  4. 結果為何?

    • user B 將 salary 改為 100 並存檔,user A 則改為 110 並存檔

      5a110

    • user A 將 salary 改為 110 並存檔,user B 則改為 100 並存檔

      6b100

    • user B 將 salary 改為 100 並存檔,user A 則修改 name 為 Yowko Tsai 並存檔

      7b100aname

    • user A 將 name 為 Yowko Tsai 並存檔,user B 則修改 salary 為 100 並存檔

      8anameb100

看完上面幾種情境真實操作的結果,我想應該沒有人可以接受,所以接著就來看看該如何設定

設定步驟

  1. DB Table 設定 >加入 rowversion 欄位

    9addrowversion

  2. EntityFramework 設定

    • 更新 edmx

      10updatefromdb

    • rowversion 欄位 Concurrency Model 屬性改為 Fixed

      11concurrecnyfixed

  3. 調整 View > 讓 rowversion 成為隱藏欄位

    @Html.HiddenFor(model=>model.rowversion)
    
  4. 針對資料過時錯誤做特別處理

    try
    {
        await db.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException ex)
    {
        return Content($"<script>alert('資料已被他人修改,請重新編輯');document.location = '{Url.Action("Edit",user.Id)}'</script>");
    }
    
  5. 實際效果

    12result

心得

一開始在 SQL Server 上想加入 rowversion 一直找不到相關設定,查文件又說從 SQL Server 2008 開始就支援,讓我一度懷疑起是不是應該額外安裝什麼套件之類的,後來才發現原來與 timestamp 是同義詞,甚至還建議應該以 rowversion 為主,timestamp 會逐漸淘汰,但就連同屬自家工具的 SSMS (SQL Server Management Studio) 與 Visual Studio 都出現不同的呈現方式,就比較容易讓人混淆了

13vsssms

參考資訊

  1. rowversion (Transact-SQL)
  2. EF Concurrency Mode Fixed + MVC