C# 數據庫並發的解決方案(通用版、EF版)


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

學無止境,精益求精。

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

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

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

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

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

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

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

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

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

在此:舉個簡單例子:

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

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

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

那么問題來了?

數據庫並發的處理方式有哪些呢?

其實數據庫的並發處理也是分為樂觀鎖和悲觀鎖,只不過是基於數據庫層面而言的!關於數據庫層面的並發處理大家可參考我的博客:樂觀鎖悲觀鎖應用

悲觀鎖:假定會發生並發沖突,屏蔽一切可能違反數據完整性的操作。[1]

樂觀鎖:假設不會發生並發沖突,只在提交操作時檢查是否違反數據完整性。[1] 樂觀鎖不能解決臟讀的問題。

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

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

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

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

項目已為大家准備好了,如下:

首先我們需要創建一個小型數據庫:

 

創建的數據庫很簡單,三張表:商品表,庫存表,日志表

有了數據庫,我們就創建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#代碼也很簡單。如下:

 

#region 通用並發處理模式 存儲過程實現
        /// <summary>
        /// 存儲過程實現
        /// </summary>
        public void SubMitOrder_2()
        {
            int productId = 1;
            bool bol = LockForPorcduce(productId);
            //1.5  模擬耗時
            Thread.Sleep(500); //消耗半秒鍾
            int retry = 10;
            while (!bol && retry > 0)
            {
                retry--;
                LockForPorcduce(productId);
            }
        }


        private bool LockForPorcduce(int ProductId)
        {
            using (BingFaTestEntities context = new BingFaTestEntities())
            {
                SqlParameter[] parameters = {
                    new SqlParameter("@ProductId", SqlDbType.Int),
                    new SqlParameter("@IsSuccess", SqlDbType.Bit)
                    };
                parameters[0].Value = ProductId;
                parameters[1].Direction = ParameterDirection.Output;
                var data = context.Database.ExecuteSqlCommand("exec LockProc @ProductId,@IsSuccess output", parameters);
                string n2 = parameters[1].Value.ToString();
                if (n2 == "True")
                {
                    return true;
                }
                else
                {
                    return false;
                }
            }
        }
        #endregion

 

在此,需要說明如下:

當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文件用記事本打開如下:

<?xml version="1.0" encoding="utf-8"?><edmx:Edmx Version="3.0" xmlns:edmx="http://schemas.microsoft.com/ado/2009/11/edmx">  <!-- EF Runtime content -->  <edmx:Runtime>    <!-- SSDL content -->    <edmx:StorageModels>      <Schema Namespace="BingFaTestModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2008" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2009/11/edm/ssdl">        <EntityContainer Name="BingFaTestModelStoreContainer">          <EntitySet Name="Inventory" EntityType="BingFaTestModel.Store.Inventory" store:Type="Tables" Schema="dbo" />          <EntitySet Name="InventoryLog" EntityType="BingFaTestModel.Store.InventoryLog" store:Type="Tables" Schema="dbo" />          <EntitySet Name="Product" EntityType="BingFaTestModel.Store.Product" store:Type="Tables" Schema="dbo" />          <AssociationSet Name="FK__Inventory__Produ__145C0A3F" Association="BingFaTestModel.Store.FK__Inventory__Produ__145C0A3F">            <End Role="Product" EntitySet="Product" />            <End Role="Inventory" EntitySet="Inventory" />          </AssociationSet>        </EntityContainer>        <EntityType Name="Inventory">          <Key>            <PropertyRef Name="InventoryId" />          </Key>          <Property Name="InventoryId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />          <Property Name="ProductId" Type="int" />          <Property Name="ProductCount" Type="int" />          <Property Name="VersionNum" Type="timestamp" Nullable="false" StoreGeneratedPattern="Computed" />          <Property Name="InventoryTime" Type="datetime" />        </EntityType>        <EntityType Name="InventoryLog">          <Key>            <PropertyRef Name="Id" />          </Key>          <Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />          <Property Name="Title" Type="nvarchar" MaxLength="50" />        </EntityType>        <EntityType Name="Product">          <Key>            <PropertyRef Name="ProductId" />          </Key>          <Property Name="ProductId" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />          <Property Name="ProductName" Type="nvarchar" MaxLength="50" />          <Property Name="ProductPrice" Type="money" />          <Property Name="ProductUnit" Type="nvarchar" MaxLength="10" />          <Property Name="AddTime" Type="datetime" />        </EntityType>        <Association Name="FK__Inventory__Produ__145C0A3F">          <End Role="Product" Type="BingFaTestModel.Store.Product" Multiplicity="0..1" />          <End Role="Inventory" Type="BingFaTestModel.Store.Inventory" Multiplicity="*" />          <ReferentialConstraint>            <Principal Role="Product">              <PropertyRef Name="ProductId" />            </Principal>            <Dependent Role="Inventory">              <PropertyRef Name="ProductId" />            </Dependent>          </ReferentialConstraint>        </Association>      </Schema>    </edmx:StorageModels>    <!-- CSDL content -->    <edmx:ConceptualModels>      <Schema Namespace="BingFaTestModel" Alias="Self" p1:UseStrongSpatialTypes="false" xmlns:annotation="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns:p1="http://schemas.microsoft.com/ado/2009/02/edm/annotation" xmlns="http://schemas.microsoft.com/ado/2009/11/edm">        <EntityContainer Name="BingFaTestEntities" p1:LazyLoadingEnabled="true">          <EntitySet Name="Inventory" EntityType="BingFaTestModel.Inventory" />          <EntitySet Name="InventoryLog" EntityType="BingFaTestModel.InventoryLog" />          <EntitySet Name="Product" EntityType="BingFaTestModel.Product" />          <AssociationSet Name="FK__Inventory__Produ__145C0A3F" Association="BingFaTestModel.FK__Inventory__Produ__145C0A3F">            <End Role="Product" EntitySet="Product" />            <End Role="Inventory" EntitySet="Inventory" />          </AssociationSet>        </EntityContainer>        <EntityType Name="Inventory">          <Key>            <PropertyRef Name="InventoryId" />          </Key>          <Property Name="InventoryId" Type="Int32" Nullable="false" p1:StoreGeneratedPattern="Identity" />          <Property Name="ProductId" Type="Int32" />          <Property Name="ProductCount" Type="Int32" />          <Property Name="VersionNum" Type="Binary" Nullable="false" MaxLength="8" FixedLength="true" p1:StoreGeneratedPattern="Computed" ConcurrencyMode="None" />          <Property Name="InventoryTime" Type="DateTime" Precision="3" />          <NavigationProperty Name="Product" Relationship="BingFaTestModel.FK__Inventory__Produ__145C0A3F" FromRole="Inventory" ToRole="Product" />        </EntityType>        <EntityType Name="InventoryLog">          <Key>            <PropertyRef Name="Id" />          </Key>          <Property Name="Id" Type="Int32" Nullable="false" p1:StoreGeneratedPattern="Identity" />          <Property Name="Title" Type="String" MaxLength="50" Unicode="true" FixedLength="false" />        </EntityType>        <EntityType Name="Product">          <Key>            <PropertyRef Name="ProductId" />          </Key>          <Property Name="ProductId" Type="Int32" Nullable="false" p1:StoreGeneratedPattern="Identity" />          <Property Name="ProductName" Type="String" MaxLength="50" Unicode="true" FixedLength="false" />          <Property Name="ProductPrice" Type="Decimal" Precision="19" Scale="4" />          <Property Name="ProductUnit" Type="String" MaxLength="10" Unicode="true" FixedLength="false" />          <Property Name="AddTime" Type="DateTime" Precision="3" />          <NavigationProperty Name="Inventory" Relationship="BingFaTestModel.FK__Inventory__Produ__145C0A3F" FromRole="Product" ToRole="Inventory" />        </EntityType>        <Association Name="FK__Inventory__Produ__145C0A3F">          <End Role="Product" Type="BingFaTestModel.Product" Multiplicity="0..1" />          <End Role="Inventory" Type="BingFaTestModel.Inventory" Multiplicity="*" />          <ReferentialConstraint>            <Principal Role="Product">              <PropertyRef Name="ProductId" />            </Principal>            <Dependent Role="Inventory">              <PropertyRef Name="ProductId" />            </Dependent>          </ReferentialConstraint>        </Association>      </Schema>    </edmx:ConceptualModels>    <!-- C-S mapping content -->    <edmx:Mappings>      <Mapping Space="C-S" xmlns="http://schemas.microsoft.com/ado/2009/11/mapping/cs">        <EntityContainerMapping StorageEntityContainer="BingFaTestModelStoreContainer" CdmEntityContainer="BingFaTestEntities">          <EntitySetMapping Name="Inventory">            <EntityTypeMapping TypeName="BingFaTestModel.Inventory">              <MappingFragment StoreEntitySet="Inventory">                <ScalarProperty Name="InventoryId" ColumnName="InventoryId" />                <ScalarProperty Name="ProductId" ColumnName="ProductId" />                <ScalarProperty Name="ProductCount" ColumnName="ProductCount" />                <ScalarProperty Name="VersionNum" ColumnName="VersionNum" />                <ScalarProperty Name="InventoryTime" ColumnName="InventoryTime" />              </MappingFragment>            </EntityTypeMapping>          </EntitySetMapping>          <EntitySetMapping Name="InventoryLog">            <EntityTypeMapping TypeName="BingFaTestModel.InventoryLog">              <MappingFragment StoreEntitySet="InventoryLog">                <ScalarProperty Name="Id" ColumnName="Id" />                <ScalarProperty Name="Title" ColumnName="Title" />              </MappingFragment>            </EntityTypeMapping>          </EntitySetMapping>          <EntitySetMapping Name="Product">            <EntityTypeMapping TypeName="BingFaTestModel.Product">              <MappingFragment StoreEntitySet="Product">                <ScalarProperty Name="ProductId" ColumnName="ProductId" />                <ScalarProperty Name="ProductName" ColumnName="ProductName" />                <ScalarProperty Name="ProductPrice" ColumnName="ProductPrice" />                <ScalarProperty Name="ProductUnit" ColumnName="ProductUnit" />                <ScalarProperty Name="AddTime" ColumnName="AddTime" />              </MappingFragment>            </EntityTypeMapping>          </EntitySetMapping>        </EntityContainerMapping>      </Mapping>    </edmx:Mappings>  </edmx:Runtime>  <!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->  <Designer xmlns="http://schemas.microsoft.com/ado/2009/11/edmx">    <Connection>      <DesignerInfoPropertySet>        <DesignerProperty Name="MetadataArtifactProcessing" Value="EmbedInOutputAssembly" />      </DesignerInfoPropertySet>    </Connection>    <Options>      <DesignerInfoPropertySet>        <DesignerProperty Name="ValidateOnBuild" Value="true" />        <DesignerProperty Name="EnablePluralization" Value="False" />        <DesignerProperty Name="IncludeForeignKeysInModel" Value="True" />        <DesignerProperty Name="CodeGenerationStrategy" Value="無" />      </DesignerInfoPropertySet>    </Options>    <!-- Diagram content (shape and connector positions) -->    <Diagrams></Diagrams>  </Designer></edmx:Edmx>

其實,在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會幫我們自動檢測並發並拋出異常,我們用上述方法捕獲異常后,就可以執行我們重復執行的規則了,具體代碼如下:

#region EF專屬並發處理模式
        /// <summary>
        /// 存儲過程實現
        /// </summary>
        public void SubMitOrder()
        {
            int C = LockForEF();
            //1.5  模擬耗時
            Thread.Sleep(500); //消耗半秒鍾
            int retry = 10;
            while (C<0 && retry > 0)
            {
                retry--;
                C= LockForEF();
            }
        }
        /// <summary>
        /// 模仿一個減少庫存操作  EF專屬並發處理模式
        /// </summary>
        public int LockForEF()
        {
            int productId = 1;
            int C = 0;
            using (SaveChangesForBF context = new SaveChangesForBF())
            {
                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;
                    C = context.SaveChanges();
                    //插入日志
                    InventoryLog LogModel = new InventoryLog()
                    {
                        Title = "插入一條數據,用於計算是否發生並發",
                        
                    };
                    InventoryLogDbSet.Add(LogModel);
                    context.SaveChanges();
                    //1.5  模擬耗時
                    Thread.Sleep(500); //消耗半秒鍾
                    Transaction.Commit();
                }

            }
            return C;
        }
        #endregion

 

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

項目源碼地址:http://download.csdn.net/download/wolongbb/9977216

@陳卧龍的博客

 

c#解決高並發--加鎖(Lock)

private static object sign = new object();

        public static DBHelper CreateMapping(string connStr = "")
        {
            DBHelper db = new DBHelper(connStr);
            return db;
        }

        /// <summary>
        /// 增刪改的數據庫連接字符串
        /// </summary>
        string conString = "";
        /// <summary>
        /// 查詢的數據庫連接字符串
        /// </summary>
        string queryConString = "";

        DBHelper(string connStr = "")
        {
            if (string.IsNullOrWhiteSpace(connStr))
            {
                conString = AsString(ConfigurationManager.ConnectionStrings["DBConfig"]);
                if(string.IsNullOrEmpty(conString))WriteErrorLogDataInteraction(conString,"連接字符串獲取失敗!");
                //第一個鏈接字符串是ConfigurationManager.ConnectionStrings[0].Name LocalSqlServer不知道是誰,所以要從第二個取
                //如果連接字符串的數量和當前索引+1相同 則從第一個索引開始取
                NameValueCollection connStrs = new NameValueCollection();
                int j = 0;
                for (int i = 0; i < ConfigurationManager.ConnectionStrings.Count; i++)
                {
                    if (ConfigurationManager.ConnectionStrings[i].Name.StartsWith("QueryDB"))
                    {
                        connStrs.Add(j.ToString(), ConfigurationManager.ConnectionStrings[i].ToString());
                        j++;
                    }
                }
                // 加鎖
                lock (sign)
                {
                    if (connStrs.Count > 0)
                    {
                        if (connStrs.Count >= ConnIndex)
                        {
                            ConnIndex = 0;
                        }
                        queryConString = connStrs[ConnIndex].ToString();
                        ConnIndex++;
                    }
                    else
                    {
                        queryConString = conString;
                    }
                }
            }
            else
            {
                conString = connStr;
                queryConString = connStr;
            }

            DBMapping = new SqlDatabase(conString);
            QueryDBMapping = new SqlDatabase(queryConString);

        }

 

 

問題描述:實驗室項目中新建項目提交審核,提交時會同時觸發兩個ajax請求,兩個ajax請求對同一張表中的同一行數據進行修改。這個時候就容易發生並發沖突。。捕捉到System.Data.Linq.ChangeConflictException HResult=-2146233088……的問題


問題分析:在正常運行狀態下,Linq在運行時,會把數據庫的數據緩存到實體對象中,這是一種理想化的情況,並且在更新時,Linq會默認把除更新字段外的所有字段,作為Update語句中的Where條件。但是,如果此時有另外的程序,在訪問數據庫,並修改數據庫數據的時候。這個時候where就會匹配不到該行,所以出現此問題(找不到行或行已更改)。

 

 


解決方案:
代碼如下:

  public void Submit() { try { base.SubmitChanges(); } catch (System.Data.Linq.ChangeConflictException ex) { //base.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues); //用上面的方法路徑可以正常保存,但是state不能正常保存 //base.ChangeConflicts.ResolveAll(RefreshMode.OverwriteCurrentValues); //用上面的方法state可以正常保存,但是路徑不能正常保存 base.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges); //用該方法,路徑和、state都能正常保存 base.SubmitChanges(); } }

兩個ajax請求,分別對同一張表的同一行中的路徑和state進行了修改,這個時候更新數據庫肯定報錯:

base.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);
修改路徑的ajax請求中,state對應的是未修改的,但是修改state的請求已經正常結束保存到數據庫中,這個時候模型中的state和數據庫中的state是不一致的,出現並發沖突。base.ChangeConflicts.ResolveAll(RefreshMode.KeepCurrentValues);雖然可以不報錯正常運行,但是這種方法使用Linq緩存中實體對象的值,覆蓋當前數據庫中的值也就是state並沒有更改(保持當前的值),所以這種方法不適用。

base.ChangeConflicts.ResolveAll(RefreshMode.OverwriteCurrentValues);
保持原來的更新,放棄了當前的值.。分析同上

base.ChangeConflicts.ResolveAll(RefreshMode.KeepChanges);
保存原來的值 有沖突的話保存當前版本。


免責聲明!

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



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