1. 程式人生 > >asp.net MVC EF+併發處理

asp.net MVC EF+併發處理

還是那句老話:十年河東,十年河西,莫欺騷年窮!~_~ 打錯個字,應該是莫欺少年窮!

學歷代表你的過去,能力代表你的現在,學習代表你的將來。

學無止境,精益求精。

自ASP.NET誕生以來,微軟提供了不少控制併發的方法,在瞭解這些控制併發的方法前,我們先來簡單介紹下併發!

併發:同一時間或者同一時刻多個訪問者同時訪問某一更新操作時,會產生併發!

針對併發的處理,又分為悲觀併發處理樂觀併發處理

所謂悲觀/樂觀併發處理,可以這樣理解:

悲觀者認為:在程式的執行過程中,併發很容易發生滴,因此,悲觀者提出了他們的處理模式:在我執行一個方法時,不允許其他訪問者介入這個方法。(悲觀者經常認為某件壞事會發生在自己身上

樂觀者認為:在程式的執行過程中,併發是很少發生滴,因此,樂觀者提出了他們的處理模式:在我執行一個方法時,允許其他訪問者介入這個方法。(樂觀者經常認為某件壞事不會發生在自己身上

那麼在C#語言中,那些屬於悲觀者呢?

在C#中諸如:LOCK、Monitor、Interlocked 等鎖定資料的方式,屬於悲觀併發處理範疇!資料一旦被鎖定,其他訪問者均無權訪問。有興趣的可以參考:鎖、C#中Monitor和Lock以及區別

但是,悲觀者處理併發的模式有一個通病,那就是可能會造成非常低下的執行效率。

在此:舉個簡單例子:

售票系統,小明去買票,要買北京到上海的D110次列車,如果採用悲觀者處理併發的模式,那麼售票員會將D110次列車的票鎖定,然後再作出票操作。但是,在D110次列車車票被鎖定期間,售票員去了趟廁所,或者喝了杯咖啡,其他視窗售票員是不能進行售票滴!如果採用這種處理方式的話,中國14億人口都不用出行了,原因是買不到票 ~_~

因此:在處理資料庫併發時,悲觀鎖還是要謹慎使用!具體還要看資料庫併發量大不大,如果比較大,建議使用樂觀者處理模式,如果比較小,可以適當採用悲觀者處理模式!

OK。說了這麼多,也就是做個鋪墊,本節內容標題叫資料庫併發的解決方案,我們最終還得返璞歸真,從資料庫併發的解決說起!

那麼問題來了?

資料庫併發的處理方式有哪些呢?

其實資料庫的併發處理也是分為樂觀鎖和悲觀鎖,只不過是基於資料庫層面而言的!關於資料庫層面的併發處理大家可參考我的部落格:樂觀鎖悲觀鎖應用

悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作。[1]

樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。[1] 樂觀鎖不能解決髒讀的問題。

 最常用的處理多使用者併發訪問的方法是加鎖。當一個使用者鎖住資料庫中的某個物件時,其他使用者就不能再訪問該物件。加鎖對併發訪問的影響體現在鎖的粒度上。比如,放在一個表上的鎖限制對整個表的併發訪問;放在資料頁上的鎖限制了對整個資料頁的訪問;放在行上的鎖只限制對該行的併發訪問。可見行鎖粒度最小,併發訪問最好,頁鎖粒度最大,併發訪問效能就會越低。

悲觀鎖:假定會發生併發衝突,遮蔽一切可能違反資料完整性的操作。[1] 悲觀鎖假定其他使用者企圖訪問或者改變你正在訪問、更改的物件的概率是很高的,因此在悲觀鎖的環境中,在你開始改變此物件之前就將該物件鎖住,並且直到你提交了所作的更改之後才釋放鎖。悲觀的缺陷是不論是頁鎖還是行鎖,加鎖的時間可能會很長,這樣可能會長時間的鎖定一個物件,限制其他使用者的訪問,也就是說悲觀鎖的併發訪問性不好。

樂觀鎖:假設不會發生併發衝突,只在提交操作時檢查是否違反資料完整性。[1] 樂觀鎖不能解決髒讀的問題。 樂觀鎖則認為其他使用者企圖改變你正在更改的物件的概率是很小的,因此樂觀鎖直到你準備提交所作的更改時才將物件鎖住,當你讀取以及改變該物件時並不加鎖。可見樂觀鎖加鎖的時間要比悲觀鎖短,樂觀鎖可以用較大的鎖粒度獲得較好的併發訪問效能。但是如果第二個使用者恰好在第一個使用者提交更改之前讀取了該物件,那麼當他完成了自己的更改進行提交時,資料庫就會發現該物件已經變化了,這樣,第二個使用者不得不重新讀取該物件並作出更改。這說明在樂觀鎖環境中,會增加併發使用者讀取物件的次數。

本篇的主旨是講解基於C#的資料庫併發解決方案(通用版、EF版),因此我們要從C#方面入手,最好是結合一個小專案

專案已為大家準備好了,如下:

首先我們需要建立一個小型資料庫:

 View Code

建立的資料庫很簡單,三張表:商品表,庫存表,日誌表

有了資料庫,我們就建立C#專案,本專案採用C# DataBaseFirst 模式,結構如下:

專案很簡單,採用EF DataBaseFirst 模式很好構建。

 專案構建好了,下面我們模擬併發的發生?

主要程式碼如下(減少庫存、插入日誌):

複製程式碼
#region 未做併發處理
        /// <summary>
        /// 模仿一個減少庫存操作  不加併發控制
        /// </summary>
        public void SubMitOrder_3()
        {
            int productId = 1;

            using (BingFaTestEntities context = new BingFaTestEntities())
            {
                var InventoryLogDbSet = context.InventoryLog;
                var InventoryDbSet = context.Inventory;//庫存表

                using (var Transaction = context.Database.BeginTransaction())
                {
                    //減少庫存操作
                    var Inventory_Mol = InventoryDbSet.Where(A => A.ProductId == productId).FirstOrDefault();//庫存物件
                    Inventory_Mol.ProductCount = Inventory_Mol.ProductCount - 1;
                    int A4 = context.SaveChanges();
                    //插入日誌
                    InventoryLog LogModel = new InventoryLog()
                    {
                        Title = "插入一條資料,用於計算是否發生併發",

                    };
                    InventoryLogDbSet.Add(LogModel);
                    context.SaveChanges();
                    //1.5  模擬耗時
                    Thread.Sleep(500); //消耗半秒鐘
                    Transaction.Commit();
                }

            }
        }
        #endregion
複製程式碼

此時我們 int productId=1 處加上斷點,並執行程式(開啟四個瀏覽器同時執行),如下:

由上圖可知,四個訪問者同時訪問這個未採用併發控制的方法,得到的結果如下:

結果顯示:日誌生成四條資料,而庫存量缺只減少1個。這個結果顯然是不正確的,原因是因為發生了併發,其本質原因是髒讀,誤讀,不可重讀造成的。

那麼,問題既然發生了,我們就想辦法法解決,辦法有兩種,分別為:悲觀鎖方法、樂觀鎖方法。

悲觀者方法

悲觀者方法(加了uodlock鎖,鎖定了更新操作,也就是說,一旦被鎖定,其他訪問者不允許訪問此操作)類似這種方法,可以通過儲存過程實現,在此不作解釋了

樂觀者方法(通用版/儲存過程實現):

在上述資料庫指令碼中,有欄位叫做:VersionNum,型別為:TimeStamp。

欄位 VersionNum 大家可以理解為版本號,版本號的作用是一旦有訪問者修改資料,版本號的值就會相應發生改變。當然,版本號的同步更改是和資料庫相關的,在SQLserver中會隨著資料的修改同步更新版本號,但是在MySQL裡就不會隨著資料的修改而更改。因此,如果你採用的是MYSQL資料庫,就需要寫一個觸發器,如下:

OK,瞭解了型別為Timestamp的欄位,下面我們結合上述的小型資料庫建立一個處理併發的儲存過程,如下

複製程式碼
create proc LockProc --樂觀鎖控制併發
(
@ProductId int, 
@IsSuccess bit=0 output
)
as
declare @count as int
declare @flag as TimeStamp
declare @rowcount As int 
begin tran
select @count=ProductCount,@flag=VersionNum from Inventory where ProductId=@ProductId
 
update Inventory set ProductCount=@count-1 where VersionNum=@flag and ProductId=@ProductId
insert into InventoryLog values('插入一條資料,用於計算是否發生併發')
set @rowcount=@@ROWCOUNT
if @rowcount>0
set @IsSuccess=1
else
set @IsSuccess=0
commit tran
複製程式碼

 這個儲存過程很簡單,執行兩個操作:減少庫存和插入一條資料。有一個輸入引數:productId ,一個輸出引數,IsSuccess。如果發生併發,IsSuccess的值為False,如果執行成功,IsSuccess值為True。

在這裡,向大家說明一點:程式採用悲觀鎖,是序列的,採用樂觀鎖,是並行的。

也就是說:採用悲觀鎖,一次僅執行一個訪問者的請求,待前一個訪問者訪問完成並釋放鎖時,下一個訪問者會依次進入鎖定的程式並執行,直到所有訪問者執行結束。因此,悲觀鎖嚴格按照次序執行的模式能保證所有訪問者執行成功。

採用樂觀鎖時,訪問者是並行執行的,大家同時訪問一個方法,只不過同一時刻只會有一個訪問者操作成功,其他訪問者執行失敗。那麼,針對這些執行失敗的訪問者怎麼處理呢?直接返回失敗資訊是不合理的,使用者體驗不好,因此,需要定製一個規則,讓執行失敗的訪問者重新執行之前的請求即可。

 時間有限,就不多寫了...因為併發的控制是在資料庫端儲存過程,所以,C#程式碼也很簡單。如下:

 View Code

在此,需要說明如下:

當IsSuccess的值為False時,應該重複執行該方法,我定的規則是重複請求十次,這樣就很好的解決了直接反饋給使用者失敗的訊息。提高了使用者體驗。

下面著重說下EF框架如何避免資料庫併發,在講解之前,先允許我引用下別人部落格中的幾段話:

在軟體開發過程中,併發控制是確保及時糾正由併發操作導致的錯誤的一種機制。從 ADO.NET 到 LINQ to SQL 再到如今的 ADO.NET Entity Framework,.NET 都為併發控制提供好良好的支援方案。

相對於資料庫中的併發處理方式,Entity Framework 中的併發處理方式實現了不少的簡化。

在System.Data.Metadata.Edm 名稱空間中,存在ConcurencyMode 列舉,用於指定概念模型中的屬性的併發選項。
ConcurencyMode 有兩個成員:

成員名稱 說明
        None 在寫入時從不驗證此屬性。 這是預設的併發模式。
        Fixed 在寫入時始終驗證此屬性。

當模型屬性為預設值 None 時,系統不會對此模型屬性進行檢測,當同一個時間對此屬性進行修改時,系統會以資料合併方式處理輸入的屬性值。
當模型屬性為Fixed 時,系統會對此模型屬性進行檢測,當同一個時間對屬性進行修改時,系統就會激發OptimisticConcurrencyException 異常。

開發人員可以為物件的每個屬性定義不同的 ConcurencyMode 選項,選項可以在*.Edmx找看到:

Edmx檔案用記事本開啟如下:

 View Code

其實,在EF DataBaseFirst中,我們只需設定下型別為 TimeStamp 版本號的屬性即可,如下:

設定好了版本號屬性後,你就可以進行併發測試了,當系統發生併發時,程式會丟擲異常,而我們要做的就是要捕獲這個異常,而後就是按照自己的規則,重複執行請求的方法,直至返回成功為止。

那麼如何捕獲併發異常呢?

在C#程式碼中需要使用異常類:DbUpdateConcurrencyException 來捕獲,EF中具體用法如下:

複製程式碼
public class SaveChangesForBF : BingFaTestEntities
    {
        public override int SaveChanges()
        {
            try
            {
                return base.SaveChanges();
            }
            catch (DbUpdateConcurrencyException ex)//(OptimisticConcurrencyException)
            {
                //併發儲存錯誤
                return -1;
            }
        }
    }
複製程式碼

設定好屬性後,EF會幫我們自動檢測併發並丟擲異常,我們用上述方法捕獲異常後,就可以執行我們重複執行的規則了,具體程式碼如下:

 View Code

 至此,C#併發處理就講解完了,是不是很簡單呢?

本文引用部落格

相關推薦

asp.net MVC EF+併發處理

還是那句老話:十年河東,十年河西,莫欺騷年窮!~_~ 打錯個字,應該是莫欺少年窮! 學歷代表你的過去,能力代表你的現在,學習代表你的將來。 學無止境,精益求精。 自ASP.NET誕生以來,微軟提供了不少控制併發的方法,在瞭解這些控制併發的方法前,我們先來簡單介紹下併

Asp.Net MVC EF-DbFirst之增刪改查

生成 處的 message mod 更新 get請求 layout 失敗 inf 控制器及動作方法: using System; using System.Collections.Generic; using System.Linq; using System.Web;

asp.net mvc中如何處理字符串與對象之間的序列化與反序列化(一)

osi strong 類結構 plain pbo edate inf esc arp 前臺我們一般用ajax來發送數據到後端進行處理,如果json數據結構和後臺的實體類結構一致,就直接可以反序列化為指定的對象進行操作,非常方便。 前端發送的json數據結構: 後端實體結

ASP.NET MVC中錯誤處理方式

itl com archive href tle http ive hive .html http://www.cnblogs.com/shenba/archive/2011/04/16/2018441.htmlASP.NET MVC中錯誤處理方式

Jexus 安裝asp.net mvc EF 項目引發的錯誤總

view pos 安裝asp.net 無法運行 版本 post ons ews bpa 1、Linux 中的文件路徑問題(配置文件路徑),必須使用左斜桿 “/” 2、MVC 看 View/Web.config 下的配置文件中版本不對報錯,如下: Could not

Asp.Net MVC EF之一:使用Database類在EF框架中執行Sql語句

包括 ans cti foo lists sele 下場 tex 對數 h4 { padding: 8px 5px; background-color: #32c5d2 } .start-box,.body { padding: 10px } .tit { font-siz

asp.net MVC + EF , Linq的基本用法

top code sum 裏的 頁碼 reac style from sta public ActionResult Index() { NewsEntities news = new NewsEntities();

Asp.Net MVC+EF+三層架構 簡單搭建 (1) Asp.Net MVC+EF+三層架構

首先,謝謝各位過客觀看,今天我們說下簡單的 Asp.Net MVC+EF+三層架構 搭建( 第一部分)。 很簡單,先看下完成之後程式碼圖:   這裡講的是一個整體框架的搭建,所以頁面暫時Pass,先以一個小的查詢為例。   一、新建Model、Dal、Bl

ASP.NET MVC + EF 利用儲存過程讀取大資料,1億資料測試很OK

 看到本文的標題,相信你會忍不住進來看看!   沒錯,本文要講的就是這個重量級的東西,這個不僅僅支援單表查詢,更能支援連線查詢,   加入一個表10W資料,另一個表也是10萬資料,當你用linq建立一個連線查詢然後

Asp.Net MVC+EF+三層架構】詳解(三)MVC VS 三層架構

前言:        接著上篇部落格說:MVC和三層架構到底是怎麼樣的一個關係?相同?或是迥異?或是部分相同,部分不同?或是思想同,邏輯不同?這是個值得思考的問題。關於三層架構大家應該差不多都有些瞭解

關於ASP.NET MVC+EF報錯提示“不支援關鍵字: "data source......meta data"之類的

常見錯誤: 不支援關鍵字: "data source=(local);initial catalog". 網上大多解決辦法是: 1.data source之間需要插入一個空格或者都是一些低階的拼寫錯誤造成的, 2.或者通過把data source改成serve

Asp.Net MVC+EF+三層架構】詳解(一)初見

前言: 這個專案是小編我進入公司開始全面重頭開始著手的第一個專案,在寶寶的IT生涯裡這是一個里程碑,有著與眾不同的意義。那麼在之後小編會從頭至尾的寫一組關於Asp.Net MVC+EF+三層架構框架

Autofact + Asp.net MVC + EF Code First(附原始碼)

本篇文章,講解如何使用Auotfac, Asp.net MVC和EF Code First,搭建一個鬆散的架構。 例子程式碼主要完成的功能是: 列出資料庫中Student表中的所有學生資訊。 閱讀目錄: 一、 使用Entity Framework Code

三層架構搭建(asp.net mvc + ef)

face 方式 change queryable 數據 .get 關註 clas tor 第一次寫博客,想了半天先從簡單的三層架構開始吧,希望能幫助到你! 簡單介紹一下三層架構, 三層架構從上到下分:表現層(UI),業務邏輯層(BLL),數據訪問層(DAL)再加上數據模型

國產化之路-統信UOS + Nginx + Asp.Net MVC + EF Core 3.1 + 達夢DM8實現簡單增刪改查操作

## 專題目錄 [國產化之路-統信UOS作業系統安裝](https://www.cnblogs.com/yuanqq/p/13738341.html) [國產化之路-國產作業系統安裝.net core 3.1 sdk](https://www.cnblogs.com/yuanqq/p/13686446.htm

ASP.NET沒有魔法——ASP.NET MVC 與數據庫之MySQL&EF

dll 程序 connector resolv rdquo ram key log 找到   本章將介紹如何通過Entity Framework來使用My SQL,之前介紹EF時介紹了provider這個配置項,而且也介紹了在ASP.NET訪問MySQL數據仍然是通過ADO

ASP.NET沒有魔法——ASP.NET MVC 與數據庫之EF實體類與數據庫結構

類之間的關系 context 模型 rst 例子 style 方法 eid 一個   大家都知道在關系型數據庫中每張表的每個字段都會有自己的屬性,如:數據類型、長度、是否為空、主外鍵、索引以及表與表之間的關系。但對於C#編寫的類來說,它的屬性只有一個數據類型和類與類之間的關

七天學會ASP.NET MVC (六)——線程問題、異常處理、自定義URL

d+ mit nes 如何 bus blog edi default 繼續 本節又帶了一些常用的,卻很難理解的問題,本節從文件上傳功能的實現引出了線程使用,介紹了線程饑餓的解決方法,異常處理方法,了解RouteTable自定義路徑 。 目錄 實驗27—

ASP.NET MVC - 處理Html數據

頁面元素 祖先 tor client con 標簽 調用 html font HtmlAgilityPack 使用HtmlAgilityPack可以以面向對象的方式通過查找Html節點來獲取頁面元素。參考:http://html-agility-pack.net Htm

ASP.NET MVC編程——錯誤處理與日記

CP 引用 args context mvc4 strong gpo pub family ASP.NET MVC的錯誤處理應考慮到這幾個方面:模型綁定期間發生的錯誤,未能路由到指定操作,針對控制器的錯誤處理。使用配置文件可以幫助我們處理異常,但是不夠靈活和全面;使用Han