Concurrency trong Entity Framework
Bất kỳ nhà phát triển nào cũng đã từng gặp khó khăn khi giải quyết các vấn đề liên quan đến truy cập dữ liệu đồng thời. Truy cập dữ liệu đồng thời (concurrency) xảy ra nếu có nhiều hơn một người dùng đang chỉnh sửa cùng một dữ liệu tại cùng một thời điểm.
Mặc định Entity Framework hỗ trợ xử lý truy cập dữ liệu đồng thời (concurrency). Entity Framework sẽ lưu một thông tin phiên bản vào cơ sở dữ liệu, thông tin này sẽ tự động thay đổi mỗi khi bạn thực hiện lệnh update
.
Khi Entity Framework thực hiện lệnh update
nó sẽ bổ sung thêm thông tin phiên bản vào mệnh đề where
của truy vấn. Nếu dữ liệu đã được thay đổi trước đó thì sẽ có một ngoại lệ xảy ra.
Ví dụ sau đây sẽ minh họa cách xử lý truy cập dữ liệu đồng thời bằng cách thêm một cột VersionNo vào bảng Course.
Đi đến trình thiết kế và nhấp chuột phải vào cửa sổ trình thiết kế và chọn Update Model from Database…
Bạn sẽ thấy cột VersionNo đã được thêm vào trong thực thể Course.
Nhấp chuột phải vào cột VersionNo mới được tạo và chọn Properties rồi thay đổi ConcurrencyMode
thành Fixed
như trong hình dưới đây.
Sau khi đã thiết lập ConcurrencyMode của VersionNo thành Fixed, bất cứ khi nào Course được cập nhật, lệnh update
sẽ tìm Course bằng cách sử dụng thuộc tính EntityKey và thuộc tính VersionNo.
Hãy xem xét một kịch bản đơn giản sau:
- Hai người dùng cùng truy xuất một khóa học tại cùng một thời điểm và người dùng 1 thay đổi tiêu đề của khóa học đó thành Toán học và lưu các thay đổi vào cơ sở dữ liệu.
- Sau đó, người dùng 2 thay đổi tiêu đề của khóa học đó thành Hình học rồi lưu vào cơ sở dữ liệu. Trong trường hợp này người dùng 2 sẽ nhận được ngoại lệ “Optimistic Concurrency exception occured“ .
using System;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
namespace DatabaseFirstDemo
{
class Program
{
static void Main(string[] args)
{
Course c1 = null;
Course c2 = null;
//User 1 gets Course
using (var context = new UniContextEntities())
{
context.Configuration.ProxyCreationEnabled = false;
c1 = context.Courses.Where(s ⇒ s.CourseID == 1).FirstOrDefault();
}
//User 2 also get the same Course
using (var context = new UniContextEntities())
{
context.Configuration.ProxyCreationEnabled = false;
c2 = context.Courses.Where(s ⇒ s.CourseID == 1).FirstOrDefault();
}
//User 1 updates Course Title
c1.Title = "Edited from user1";
//User 2 updates Course Title
c2.Title = "Edited from user2";
//User 1 saves changes first
using (var context = new UniContextEntities())
{
try
{
context.Entry(c1).State = EntityState.Modified;
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine("User1: Optimistic Concurrency exception occurred");
}
}
//User 2 saves changes after User 1.
//User 2 will get concurrency exection
//because CreateOrModifiedDate is different in the database
using (var context = new UniContextEntities())
{
try
{
context.Entry(c2).State = EntityState.Modified;
context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
Console.WriteLine("User2: Optimistic Concurrency exception occurred");
}
}
}
}
}
Concurrency trong Code-First
Bạn có thể sử dụng TimestampAttribute cho thuộc tính VersionNo trong Code-First. Hãy đảm bảo rằng kiểu dữ liệu của VersionNo là kiểu byte[]
.
[Timestamp]
public byte[] VersionNo { get; set; }
EF sẽ tự động thêm VersionNo vào trong mệnh đề where
trong khi thực hiện thao tác update
.