1. 程式人生 > >Entity Framework 4.1 之六:樂觀併發

Entity Framework 4.1 之六:樂觀併發

原文名稱:Entity Framework 4.1: Optimistic Concurrency (6)

這篇文章討論樂觀併發。

我們經常要面對多使用者併發訪問問題。這是因為人訪問機器的速度無法與高速的機器匹配,典型情況下,人需要大約一分鐘或者更多來填寫資料,而機器的交易處理通常只需要不到一秒鐘。

在使用者編輯相應資料的時候,我們不能讓資料庫的事務處理保持開啟,這有許多原因:連線問題,信任問題,技術原因等等。基於這些原因,我們需要併發管理。有兩種管理方法:樂觀和悲觀。悲觀方式更難處理,因為你需要實現基於記錄的鎖定機制,而且還隨之而來帶來一些問題,例如,在自動釋放鎖之前,系統應該鎖定多長的時間。樂觀併發要簡單一些。

樂觀併發基於的一個基本的假設是使用者的修改很少衝突。對於很少使用者同時編輯同樣資料的應用來說,不管有很多使用者還是很少的使用者都是成立的。基本的考慮是為訪問資料的使用者提供一個數據的版本,當用戶以後回來更新資料的時候,通過版本來確認原來的資料,如果資料已經在後臺被其他操作更新,版本發生了變化,我們就可以通過版本檢測到,然後拒絕更新。

可以有許多方式來實現版本,包括通過一個版本數字來關聯到資料,可以是最後一次修改的日期時間欄位,還可能是多於一個的欄位。在 EF 中,這被稱為併發標識 concurrenty token,在這篇文章中,我使用 SQL Server 的 time-stamp 特性,這需要在表中增加一個 time-stamp 型別的列,我們通過它來實現樂觀併發。由 SQL Server 在每次記錄被更新的時候維護這個列。

為了告訴 EF 在實體中有一個屬性表示併發標識,你可以通過標籤 [ConcurrencyCheck] 來標識這個屬性,或者使用模型構建器。我認為併發標識定義了業務規則,應該是模型的一部分。所以這裡使用標籤。

publicclass Order 
{
publicint OrderID { get; set; }
[Required]
[StringLength(
32, MinimumLength =2)]
publicstring OrderTitle { get; set; }
[Required]
[StringLength(
64, MinimumLength
=5)]
publicstring CustomerName { get; set; }
public DateTime TransactionDate { get; set; }
[ConcurrencyCheck]
[Timestamp]
publicbyte[] TimeStamp { get; set; }

publicvirtual List<OrderDetail> OrderDetails { get; set; }
publicvirtual List<Employee> InvolvedEmployees { get; set; }
}

在這段程式碼中,當我們通過 DbContext 呼叫 SaveChanges 的時候,將會使用樂觀併發。

Timestamp 屬性的型別是 byte[], 通過標籤 Timestamp ,將這個屬性對映到 SQL Server 的 time-stamp 型別的列。

現在,我們模擬併發,我們將執行下面的三個步驟:

  1. 建立一個訂單
  2. 模擬使用者 X 修改這個訂單
  3. 模擬使用者 Y 修改這個訂單,此時訂單已經被修改,而使用者 Y 不知道。

通過在另一個 DbContext 中連線實體來模擬修改的過程,當我們通過 DbContet 連線實體的時候,它會假設實體沒有被修改,所以我們在它之後進行修改。

privatestaticvoid ConcurrencyCheck() 
{
Order originalOrder;

// Create an order using (var context1 =new MyDomainContext())
{
originalOrder
=new Order
{
OrderTitle
="Paper",
CustomerName
="*Bob*",
TransactionDate
= DateTime.Now
};

context1.Orders.Add(originalOrder);
context1.SaveChanges();
}
// Simulate the modification of the created order by user X using (var context2 =new MyDomainContext())
{
// Recreate the order object in order to attach it var order =new Order
{
OrderID
= originalOrder.OrderID,
OrderTitle
= originalOrder.OrderTitle,
CustomerName
= originalOrder.CustomerName,
TransactionDate
= originalOrder.TransactionDate,
TimeStamp
= originalOrder.TimeStamp
};

context2.Orders.Attach(order);

// Alter the order order.CustomerName ="Robert";

context2.SaveChanges();
}
// Simulate the modification of the created order by user Y (after user X already modified it) using (var context3 =new MyDomainContext())
{
// Recreate the order in order to attach it var order =new Order
{
OrderID
= originalOrder.OrderID,
OrderTitle
= originalOrder.OrderTitle,
CustomerName
= originalOrder.CustomerName,
TransactionDate
= originalOrder.TransactionDate,
TimeStamp
= originalOrder.TimeStamp
};

context3.Orders.Attach(order);

// Alter the order order.CustomerName ="Luke**";

try
{
context3.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine(
"Concurrency exception on "+ ex.Entries.First().Entity.GetType().Name);
}
}
}

你也可以強制 EF 相信訂單已經被修改了。

context3.Entry(order).State = EntityState.Modified; 

所以,EF 提供了開箱即用的樂觀併發支援,這個竅門也可以用在 EF 狀態的各個方面:當你連線實體的時候,確信它們處於正確的狀態。

  • Detached
  • Unchanged
  • Added
  • Deleted
  • Modified