EntityFramework 6 (EF6 DBcontext) 併發處理實戰

来源:http://www.cnblogs.com/taotaozujinet/archive/2017/11/17/7851875.html
-Advertisement-
Play Games

學習:C#綜合揭秘——Entity Framework 併發處理詳解 帖子筆記 ,該帖子使用的是objectContext , 一、併發相關概念 併發的類型: 第一種模式稱為悲觀式併發,即當一個用戶已經在修改某條記錄時,系統將拒絕其他用戶同時修改此記錄。第二種模式稱為樂觀式併發,即系統允許多個用戶同... ...


學習:C#綜合揭秘——Entity Framework 併發處理詳解 帖子筆記 ,該帖子使用的是objectContext ,

一、併發相關概念

併發的類型:

第一種模式稱為悲觀式併發,即當一個用戶已經在修改某條記錄時,系統將拒絕其他用戶同時修改此記錄。
第二種模式稱為樂觀式併發,即系統允許多個用戶同時修改同一條記錄,系統會預先定義由數據併發所引起的併發異常處理模式,去處理修改後可能發生的衝突。常用的樂觀性併發處理方法有以下幾種:

    1、保留最後修改的值。
    2、保留最初修改的值。
    3、合併多次修改的值。

二、模型屬性的併發處理選項

如下圖模型設計器中TimeStamp欄位為啟用併發

image

<EntityType Name="UserAccout">
          <Key>
            <PropertyRef Name="Id" />
          </Key>
          <Property Name="Id" Type="Int32" Nullable="false" annotation:StoreGeneratedPattern="Identity" />
          <Property Name="FirstName" Type="String" Nullable="false" />
          <Property Name="LastName" Type="String" Nullable="false" />
          <Property Name="AuditFileds" Type="OrderDB.AuditFields" Nullable="false" />
          <Property Name="Timestamp" Type="DateTime" Nullable="false" ConcurrencyMode="Fixed" annotation:StoreGeneratedPattern="Computed" />
        </EntityType>

併發模式:ConcurencyMode 有兩個成員:

None : 在寫入時從不驗證此屬性。 這是預設的併發模式。

Fixed: 在寫入時始終驗證此屬性。

當模型屬性為預設值 None 時,系統不會對此模型屬性進行檢測,當同一個時間對此屬性進行修改時,系統會以數據合併方式處理輸入的屬性值。

當模型屬性為Fixed 時,系統會對此模型屬性進行檢測,當同一個時間對屬性進行修改時,系統就會激發OptimisticConcurrencyException 異常。

 

三、悲觀併發

 

四、樂觀併發

為瞭解決悲觀併發所帶來的問題,ADO.NET Entity Framework 提供了更為高效的樂觀併發處理方式。相對於LINT to SQL , ADO.NET Entity Framework 簡化了樂觀併發的處理方式,它可以靈活使用合併數據、保留初次輸入數據、保留最新輸入數據(3種方式)等方式處理併發衝突。

4.1 以合併方式處理併發數據

總結:當模型屬性的 ConcurencyMode 為預設值 None ,一旦同一個對象屬性同時被修改,系統將以合併數據的方式處理併發衝突,這也是 Entity Framework 處理併發衝突的預設方式。

合併處理方式如下:

(1)當同一時間針對同一個對象屬性作出修改,系統將保存最新輸入的屬性值

(2)當同一時間對同一對象的不同屬性作出修改,系統將保存已被修改的屬性值。下麵用兩個例子作出說明:

image

運行結果:

image

#region (4.1)測試不設置任何併發測試時,當產生併發EF的處理方法
        delegate void MyDelegate(Address addressValue);
        public  StringBuilder sb = new StringBuilder();
        public Address GetAddress(int id)
        {
            using (OrderDBContainer context = new OrderDBContainer())
            {
                IQueryable<Address> list = context.AddressSet.Where(x => x.Id == id);
                return list.First();
            }
        }
        /// <summary>
        /// 修改方法
        /// </summary>
        /// <param name="addressValue"></param>
        public void UpdateAddress(Address addressValue)
        {
            using (OrderDBContainer context = new OrderDBContainer())
            {
                //顯示輸入新數據的信息
                Display("Current", addressValue);
                var obj = context.AddressSet.Where(x => x.Id == addressValue.Id).First();
                if (obj != null)
                    context.Entry(obj).CurrentValues.SetValues(addressValue);
                //虛擬操作,保證數據能同時加入到上下文當中
                Thread.Sleep(100);
                context.SaveChanges();
            }
        }        
        /// <summary>
        /// 顯示實體當前屬性
        /// </summary>
        /// <param name="message"></param>
        /// <param name="addressValue"></param>
        public void Display(string message, Address addressValue)
        {
            String data = string.Format("{0}\n  Address Message:\n    Id:{1}  Address1:{2}  " +
                "address2:{3} \r\n ",
                message, addressValue.Id, addressValue.Address1, addressValue.Address2 );
            sb.AppendLine(data);
        }     
        
        /// <summary>
        /// (1)測試使用EF預設的機制,當配置併發控制時,系統是使用的合併的方式
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button3_Click(object sender, EventArgs e)
        {
            //在更新數據前顯示對象信息
            var beforeObj = GetAddress(1);
            Display("Before", beforeObj);

            //更新Person的SecondName,Age兩個屬性
            Address _address1 = new Address();
            _address1.Id = 1;
            _address1.Address1 = "古溪";
            _address1.Address2 = beforeObj.Address2;
            _address1.AuditFields.InsertDate = beforeObj.AuditFields.InsertDate;
            _address1.AuditFields.UpdateDate = beforeObj.AuditFields.UpdateDate;
            _address1.City = beforeObj.City;
            _address1.Zip = beforeObj.Zip;
            _address1.State = beforeObj.State;

            //更新Person的FirstName屬性
            Address _address2 = new Address();
            _address2.Id = 1;
            _address2.Address1 = beforeObj.Address1;
            _address2.Address2 = "江蘇";
            _address2.AuditFields.InsertDate = beforeObj.AuditFields.InsertDate;
            _address2.AuditFields.UpdateDate = beforeObj.AuditFields.UpdateDate;
            _address2.City = beforeObj.City;
            _address2.Zip = beforeObj.Zip;
            _address2.State = beforeObj.State;

            //使用非同步方式同時更新數據
            MyDelegate myDelegate = new MyDelegate(UpdateAddress);
            myDelegate.BeginInvoke(_address1, null, null);
            myDelegate.BeginInvoke(_address2, null, null);

            Thread.Sleep(1000);
            //在更新數據後顯示對象信息
            var afterObj = GetAddress(1);
            Display("After", afterObj);
            this.textBox1.Text = sb.ToString();
        }

        /// <summary>
        /// 先插入幾條數據等著測試
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void BtnSaveAddress_Click(object sender, EventArgs e)
        {
            using (OrderDBContainer db = new OrderDBContainer())
            {
                Address address = new Address();
                address.Address1 = "古溪鎮";
                address.Address2 = "安鎮";
                address.State = "2";
                address.City = "無錫";
                address.AuditFields.InsertDate = DateTime.Now;
                address.AuditFields.UpdateDate = DateTime.Now;
                address.Zip = "21415";
                db.AddressSet.Add(address);
                db.SaveChanges();
            }
        }

        /// <summary>
        /// 還原成初始值,準備再次測試
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void button5_Click(object sender, EventArgs e)
        {
            using (OrderDBContainer db = new OrderDBContainer())
            {
                Address _address = db.AddressSet.Where(x => x.Id == 1).First();
                _address.Address1 = "aaa";
                _address.Address2 = "bbb";
                db.SaveChanges();
            }
        }
        #endregion

備註:實踐過程中遇到的問題

在多線程中EF修改事件的解決方案,使用attach不可以:

image

使用Entry也報錯

image

最終參考如下帖子

image

/// <summary>
        /// 修改方法
        /// </summary>
        /// <param name="addressValue"></param>
        public void UpdateAddress(Address addressValue)
        {
            using (OrderDBContainer context = new OrderDBContainer())
            {
                //顯示輸入新數據的信息
                Display("Current", addressValue);
                var obj = context.AddressSet.Where(x => x.Id == addressValue.Id).First();
                if (obj != null)
                    context.Entry(obj).CurrentValues.SetValues(addressValue);
                //虛擬操作,保證數據能同時加入到上下文當中
                Thread.Sleep(100);
                context.SaveChanges();
            }
        }

引用:“以合併數據的方式處理併發衝突固然方便快節,但在業務邏輯較為複雜的系統下並不適合使用此處理方式。比如在常見的Order、OrderItem的表格中,OrderItem 的單價,數量會直接影響Order的總體價格,這樣使用合併數據的方式處理併發,有可能引起邏輯性的錯誤。此時,應該考慮以其他方式處理併發衝突。”。

其他什麼方式呢?【待補充】

 

4.1 刪除與更新操作同時運行(非框架自動處理能力,開發自行修改狀態手動增加的

Entity Framework 能以完善的機制靈活處理同時更新同一對象的操作,但一旦刪除操作與更新操作同時運行時,就可能存在邏輯性的異常。

例如:兩個客戶端同時載入了同一個對象,第一個客戶端更新了數據後,把數據再次提交。但在提交前,第二個客戶端已經把資料庫中的已有數據刪除。

此時,上下文中的對象處於不同的狀態下,將會引發 OptimisticConcurrencyException 異常(ObjectContext 與DBContext兩種方式下,異常不一樣,具體要根據測試結果自己判斷)。
遇到此異常時,可以用 try(OptimisticConcurrencyException){...} catch {...} 方式捕獲異常,然後更改對象的State 屬性。把EntityState 更改為 Added ,被刪除的數據便會被再次載入。若把 EntityState 更改為 Detached 時,數據便會被順利刪除。下麵把對象的 EntityState 屬性更改為 Added 作為例子。

image

代碼如下:處理結果前後ID變化了(或許這就是有些架構師使用手動創建的GUID的方式,而不使用自增的原因之一吧,因為數據刪除後再創建就回不到之前的ID了,不是太靈活,使用GUID再結合數據版本(dataVison)欄位,timeStamp基本上控制數據的併發已經足夠啊。

//更新對象
        public int UpdateWithConcurrent(int num, Address addressValue)
        {
            int returnValue = -1;
            using (OrderDBContainer context = new OrderDBContainer())
            {
                var obj = context.AddressSet.Where(x => x.Id == addressValue.Id).First();
                //顯示對象所處狀態
                DisplayState("Before Update", obj);
                try
                {
                    if (obj != null)
                        context.Entry(obj).CurrentValues.SetValues(addressValue);
                    //虛擬操作,保證數據已經在資料庫中被非同步刪除
                    Thread.Sleep(300);
                    context.SaveChanges();
                    returnValue = obj.Id;
                }
                catch (Exception)
                {
                    //針對異常要做相應的判斷,因為我只測試了刪除的情況,就寫死直接修改成Added 了
                    //正確的是要區分到底是修改還是刪除  OptimisticConcurrencyException ex
                    //把對象的狀態更改為 Added
                    context.Entry(obj).State = System.Data.Entity.EntityState.Added;
                    context.SaveChanges();
                    returnValue = obj.Id;
                }
            }
            return returnValue;
        }

併發時的異常類型:

image

ID發生了變化

image

 

4.3 當發生數據併發時,保留最終(最新:最後一次)輸入的數據

要驗證輸入對象的屬性,必須先把該屬性的 ConcurencyMode 設置為 Fixed,這樣系統就會實時檢測對象屬性的輸入值 。
當該屬性被同時更新,系統便會激發 OptimisticConcurrencyException 異常。捕獲該異常後,可以使用 ObjectContext.Refresh (RefreshMode,object) 刷新上下文中該對象的狀態,當 RefreshMode 為 ClientWins 時,系統將會保持上下文中的現在有數據,即保留最新輸入的對象值。此時再使用ObjectContext.SaveChanges, 系統就會把最新輸入的對象值加入資料庫當中。

在下麵的例子當,系統啟動前先把 Person 的 FirstName、SecondName 兩個屬性的 ConcurencyMode 屬性設置為Fixed,使系統能監視這兩個屬性的更改。所輸入的數據只在FirstName、SecondName 兩個值中作出修改。在數據提交前先以 DisplayProperty 方法顯示資料庫最初的數據屬性,在數據初次更新後再次調用 DisplayProperty 顯示更新後的數據屬性。在第二次更新數據時,由調用ObjectContext.SaveChanges時,資料庫中的數據已經被修改,與當前上下文ObjectContext 的數據存在衝突,系統將激發OptimisticConcurrencyException 異常,此時把引發異常的對象屬性再次顯示出來。對異常進行處理後,顯示資料庫中最終的對象值。

 

 

觀察測試結果,可見當RefreshMode狀態為ClientWins時,系統將會保存上下文當中的對象屬性,使用此方法可以在發生併發異常時保持最新輸入的對象屬性。

 

4.4 當發生數據併發時,保留最早(最初:最早一次)輸入的數據

把對象屬性的 ConcurencyMode 設置為 Fixed 後,同時更新該屬性,將會激發 OptimisticConcurrencyException 異常。此時使用 ObjectContext.Refresh (RefreshMode,object) 刷新上下文中該對象的狀態,當 RefreshMode 為 StoreWins 時,系統就會把數據源中的數據代替上下文中的數據。
因為初次調用 SaveChanges,數據可以成功保存到資料庫。但是在 ObjectContext 並未釋放時,再次使用 SaveChanges 非同步更新數據,就會引發OptimisticConcurrencyException 併發異常。當 RefreshMode 為 StoreWins 時,系統就會保留初次輸入的數據屬性。
此例子與上面的例子十分相似,只是把 RefreshMode 改為 StoreWins 而已。在業務邏輯較為複雜的的系統當中,建議使用此方式處理併發異常。在保留最初輸入的數據修改屬性後,把屬性返還給客戶,讓客戶進行對比後再決定下一步的處理方式。

image

image

 

觀察測試結果,可見當 RefreshMode 狀態為 StoreWins 時,系統將會以數據源中的數據代替上下文當中的對象屬性。在業務邏輯較為複雜的的系統當中,建議使用此方式處理併發異常。


鏈接: https://pan.baidu.com/s/1gfu6fZl 密碼: fyb3

練習的源碼,有糾正的錯誤的朋友記得分享


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、安裝Office2007以上版本。(如安裝的是Office2007需安裝SaveAsPDFandXPS.exe組件) 2、確認網站在IIS內使用的登錄用戶。(如圖所示用戶為IUSR,下麵操作以此用戶為例) 3、打開運行視窗,執行comexp.msc -32 ,打開32位的組件服務。 4、分別設置 ...
  • arch/x86/boot/header.S --> _start --> calll main arch/x86/boot/main.c --> main -- > go_to_protected_mode arch/x86/boot/pm.c --> go_to_protected_mode - ...
  • [20171115]ZEROCONF ROUTE.txt--//如果你檢查linux伺服器的網路配置,就可以發現如下一條路由:# route -n | egrep "169.254|Destination"Destination Gateway Genmask Flags Metric Ref Us ...
  • 總項目流程圖,詳見http://www.cnblogs.com/along21/p/7435612.html 實驗一:實現反向代理負載均衡且動靜分離 1、環境準備: 機器名稱 IP配置 服務角色 備註 nginx VIP:172.17.11.11 反向代理伺服器 開啟代理功能 設置監控,調度 rs0 ...
  • 一.序言 本資料是Trevor Martin編寫的《The Designers Guide to the Cortex-M Processor Family》的摘要,並得到Elsevier的再版許可。查詢更多細節,請到本資料尾部進階章節。 本資料著力於介紹RTX,RTX可運行在基於Cortex-M構 ...
  • 背景: 一段明顯的字元串,可能潛伏著看不見 的 幽靈字元。 某些字元 比較常見、常用,比如: \r \n \t 但是,有些 幽靈字元(保守估計 >200~1000個),不僅不常見,而且基本沒價值。 這些幽靈字元,潛伏在 正常字元串中,有的偽裝成空格符,有的直接隱形。 當你要 處理字元串時,這些幽靈字 ...
  • 在使用由Angular,React,Vue等應用程式框架構建的客戶端應用程式時,您總是會處理HTML5客戶端路由,它將完全在瀏覽器中處理到頁面和組件的客戶端路由。幾乎完全在瀏覽器中... HTML5客戶端路由在客戶端上工作的很好,但是當深入鏈接到一個站點或在瀏覽器中按刷新時,客戶端路由有一個惡習,變 ...
  • DEV控制項GridControl和TreeList的數據導出操作 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...