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
  • 概述:在C#中,++i和i++都是自增運算符,其中++i先增加值再返回,而i++先返回值再增加。應用場景根據需求選擇,首碼適合先增後用,尾碼適合先用後增。詳細示例提供清晰的代碼演示這兩者的操作時機和實際應用。 在C#中,++i 和 i++ 都是自增運算符,但它們在操作上有細微的差異,主要體現在操作的 ...
  • 上次發佈了:Taurus.MVC 性能壓力測試(ap 壓測 和 linux 下wrk 壓測):.NET Core 版本,今天計劃準備壓測一下 .NET 版本,來測試並記錄一下 Taurus.MVC 框架在 .NET 版本的性能,以便後續持續優化改進。 為了方便對比,本文章的電腦環境和測試思路,儘量和... ...
  • .NET WebAPI作為一種構建RESTful服務的強大工具,為開發者提供了便捷的方式來定義、處理HTTP請求並返迴響應。在設計API介面時,正確地接收和解析客戶端發送的數據至關重要。.NET WebAPI提供了一系列特性,如[FromRoute]、[FromQuery]和[FromBody],用 ...
  • 原因:我之所以想做這個項目,是因為在之前查找關於C#/WPF相關資料時,我發現講解圖像濾鏡的資源非常稀缺。此外,我註意到許多現有的開源庫主要基於CPU進行圖像渲染。這種方式在處理大量圖像時,會導致CPU的渲染負擔過重。因此,我將在下文中介紹如何通過GPU渲染來有效實現圖像的各種濾鏡效果。 生成的效果 ...
  • 引言 上一章我們介紹了在xUnit單元測試中用xUnit.DependencyInject來使用依賴註入,上一章我們的Sample.Repository倉儲層有一個批量註入的介面沒有做單元測試,今天用這個示例來演示一下如何用Bogus創建模擬數據 ,和 EFCore 的種子數據生成 Bogus 的優 ...
  • 一、前言 在自己的項目中,涉及到實時心率曲線的繪製,項目上的曲線繪製,一般很難找到能直接用的第三方庫,而且有些還是定製化的功能,所以還是自己繪製比較方便。很多人一聽到自己畫就害怕,感覺很難,今天就分享一個完整的實時心率數據繪製心率曲線圖的例子;之前的博客也分享給DrawingVisual繪製曲線的方 ...
  • 如果你在自定義的 Main 方法中直接使用 App 類並啟動應用程式,但發現 App.xaml 中定義的資源沒有被正確載入,那麼問題可能在於如何正確配置 App.xaml 與你的 App 類的交互。 確保 App.xaml 文件中的 x:Class 屬性正確指向你的 App 類。這樣,當你創建 Ap ...
  • 一:背景 1. 講故事 上個月有個朋友在微信上找到我,說他們的軟體在客戶那邊隔幾天就要崩潰一次,一直都沒有找到原因,讓我幫忙看下怎麼回事,確實工控類的軟體環境複雜難搞,朋友手上有一個崩潰的dump,剛好丟給我來分析一下。 二:WinDbg分析 1. 程式為什麼會崩潰 windbg 有一個厲害之處在於 ...
  • 前言 .NET生態中有許多依賴註入容器。在大多數情況下,微軟提供的內置容器在易用性和性能方面都非常優秀。外加ASP.NET Core預設使用內置容器,使用很方便。 但是筆者在使用中一直有一個頭疼的問題:服務工廠無法提供請求的服務類型相關的信息。這在一般情況下並沒有影響,但是內置容器支持註冊開放泛型服 ...
  • 一、前言 在項目開發過程中,DataGrid是經常使用到的一個數據展示控制項,而通常表格的最後一列是作為操作列存在,比如會有編輯、刪除等功能按鈕。但WPF的原始DataGrid中,預設只支持固定左側列,這跟大家習慣性操作列放最後不符,今天就來介紹一種簡單的方式實現固定右側列。(這裡的實現方式參考的大佬 ...