在關於實體框架的文檔中寫道,它不支持開箱即用的悲觀併發性。這就是爲什麼當使用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();
}
所有線程之後想要增加計數器值將從數據庫中獲得實際值,因爲它們將等待表格被釋放。
正如我所說這是我的解決方法,也許我錯了某個地方。這就是爲什麼我想要你指出更好的解決方案。因爲我不想重新發明輪子 在此先感謝!
有任何理由樂觀併發不能用於這種情況? –
這是我的問題。我如何使用實體框架來解決這個問題? – BootGenius
實體框架支持樂觀併發性,您可以將[併發性]屬性應用於您的Value屬性,這應該會導致您在線程嘗試更新計數器的較舊值時獲得併發異常, –