2014-03-01 84 views
5

在關於實體框架的文檔中寫道,它不支持開箱即用的悲觀併發性。這就是爲什麼當使用Linq to Entities語法從它讀取實體時無法鎖定表的原因。實體框架的悲觀併發性

讓我們假設我們有下一個任務:有多個線程在數據庫中共享一個表。表有一個結構:

Table Counter 
Name : varchar[10] 
Value : bigint 

每個線程都希望得到計數器,名稱=「一些名字」,得到它的計數值,不是增加它並保存到數據庫中。

在這裏我們可以看到問題,一次所有這些線程可以從數據庫讀取相同的值並增加它。假設我們有5個線程。初始計數器值爲1.如果所有線程都讀取計數器,則所有線程均獲得值1.然後,將其值增加並將其保存回數據庫。最後我們的計數器值等於2.但期望值爲6,因爲我們希望每個線程都從數據庫中獲得實際值。

我認爲這是可以解決使用事務範圍,像這樣高水平的隔離這個問題:

隔離級別的
using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{IsolationLevel = System.Transactions.IsolationLevel.Serializable})) 
    { 
     using (var context = new TestDbContext()) 
     { 
      var counter = context.Counters.First(); 
      Thread.Sleep(2000); 
      counter.Value ++; 
      Console.WriteLine("Value={0}", counter.Value); 
      context.SaveChanges(); 
     } 
     transactionScope.Complete(); 
    } 

非讓我們從中讀取記錄後,鎖表。在事務SQL中,我們有UPDLOCK和HOLDLOCK關鍵字,它們允許我們鎖定表,直到事務完成。 所以我解決創建一個擴展方法的DbContext這個問題:

public static class DataContextExtensions 
{ 
    public static void LockTable<TEntity>(this DbContext context) where TEntity : class 
    { 
     var tableName = (context as IObjectContextAdapter).ObjectContext.CreateObjectSet<TEntity>().EntitySet.Name; 
     List<TEntity> topEntity = context.Database.SqlQuery<TEntity>(String.Format("SELECT TOP 1 * from {0} WITH (UPDLOCK, HOLDLOCK)", tableName)).ToList(); 
    } 
} 

現在我可以讀這樣後,當我想要鎖定表使用此方法:

using (var transactionScope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions{IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted})) 
    { 
     using (var context = new FrontierDbContext()) 
     { 
      context.LockTable<Counter>(); 
      var counter = context.Counters.First(); 
      Thread.Sleep(2000); 
      counter.Value ++; 
      Console.WriteLine("Value={0}", counter.Value); 
      context.SaveChanges(); 
     } 
     transactionScope.Complete(); 
    } 

所有線程之後想要增加計數器值將從數據庫中獲得實際值,因爲它們將等待表格被釋放。

正如我所說這是我的解決方法,也許我錯了某個地方。這就是爲什麼我想要你指出更好的解決方案。因爲我不想重新發明輪子 在此先感謝!

+0

有任何理由樂觀併發不能用於這種情況? –

+0

這是我的問題。我如何使用實體框架來解決這個問題? – BootGenius

+0

實體框架支持樂觀併發性,您可以將[併發性]屬性應用於您的Value屬性,這應該會導致您在線程嘗試更新計數器的較舊值時獲得併發異常, –

回答

0

你的情況,你可以使用在使用的TransactionScope塊的辦法解決這個具體情況是這樣的:

const string updateStr = "update table set counter = counter + 1"; 
context.ExecuteSqlCommand(updateStr); 
context.SaveChanges(); 
+1

這不是一個選項。因爲我的例子被簡化來解釋問題。如果您的業務需求不僅僅是增加一個計數器,並且例如對鎖定表做更多的工作,您仍然會遇到這個問題 – BootGenius