در برنامههایی که چند کاربر بهصورت همزمان دادهها را مشاهده یا ویرایش میکنند، نیاز به یک سیستم مطمئن برای جلوگیری از بروز تعارض – Conflict بین عملیات کاربران وجود دارد. این مسئله در EF Core از طریق مدیریت همزمانی یا Concurrency Handling کنترل میشود. در این آموزش بهصورت کامل با مفاهیم تئوری و عملی مدیریت همزمانی آشنا خواهید شد. و روشهای مختلف جلوگیری و حل Conflictها در EF Core را بررسی میکنیم. با ما همراه باشید با آموزش مدیریت همزمانی – Concurrency در EF Core و Asp.Net Core.
فرض کنید دو کاربر بهصورت همزمان اطلاعات یک مشتری را ویرایش میکنند. اگر هر دو تغییرات را ذخیره کنند، بدون مدیریت همزمانی یکی از تغییرات از بین خواهد رفت. این وضعیت را Concurrency Conflict مینامند.
برای کنترل همزمانی میتوان از Data Annotationهایی مانند [ConcurrencyCheck] یا [Timestamp] استفاده کرد. معمولاً از نوع byte[] برای Timestamp استفاده میشود که بهصورت rowversion در SQL Server ذخیره میگردد.
public class Customer
{
public int Id { get; set; }
public string Name { get; set; }
[Timestamp]
public byte[] RowVersion { get; set; }
}
در Fluent API نیز میتوان فیلد همزمانی را بهصورت زیر تعریف کرد:
modelBuilder.Entity<Customer>()
.Property(c => c.RowVersion)
.IsRowVersion();
مدیریت DbUpdateConcurrencyException
در صورت بروز Conflict، EF Core یک استثناء به نام DbUpdateConcurrencyException پرتاب میکند. برای مدیریت آن:
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
var databaseValues = await entry.GetDatabaseValuesAsync();
// مقایسه مقدار فعلی با مقدار دیتابیس و انتخاب بهترین راهحل
}
}
نمونه کد
public async Task<IActionResult> Edit(int id, [Bind("Id,Name,BankName,RowVersion")] Banker banker)
{
if(id!=banker.Id)
return NotFound();
if(!ModelState.IsValid)
return View(banker);
//اطلاعات ردیابی بانکر را بیار تا فیلد مورد نظر خود را مقدار دهی کنم
_context.Entry(banker).Property(a=>a.RowVersion).OriginalValue=banker.RowVersion;
try
{
_context.Update(banker);
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
catch (DbUpdateConcurrencyException ex)
{
foreach (var entry in ex.Entries)
{
var DbValue = await entry.GetDatabaseValuesAsync();
if (DbValue == null) {
TempData["ErrorMessage"] = "recorde morede nazar hazfe shode ast";
return RedirectToAction(nameof(Index));
}
var DatabaseValues = (Banker)DbValue.ToObject();
var CurrentValues = (Banker)entry.CurrentValues.ToObject();
var OriginalValues = (Banker)entry.OriginalValues.ToObject();
//Store Wins
//entry.OriginalValues.SetValues(DatabaseValues);
//entry.CurrentValues.SetValues(DatabaseValues);
//TempData["ErrorMessage"] = "ghable shoma in record viraiesh shode ast etelate shoma zakhire nashod";
//Client Wins
//entry.OriginalValues.SetValues(DatabaseValues);
//TempData["ErrorMessage"] = "در زمان ویرایش شما رگورد ویرایش شده بود ولی اطلاعات شما چایگزین شد";
}
await _context.SaveChangesAsync();
return RedirectToAction(nameof(Index));
}
}
| CurrentValues | دادههایی که از سمت کاربر ارسال شده و EF Core قصد دارد آنها را روی دیتابیس ذخیره کند. این مقادیر همان چیزی هستند که از فرم یا API آمده و آمادهی ذخیرهسازیاند. |
| OriginalValues | نسخهای از رکورد که EF Core هنگام بارگذاری اولیه موجودیت از دیتابیس خوانده و برای تشخیص تغییرات نگه داشته است. اختلاف این مقادیر با DatabaseValues منجر به Conflict میشود. |
| DatabaseValues | مقادیر واقعی و فعلی رکورد در دیتابیس، یعنی همان چیزی که در لحظهی رخ دادن Conflict از دیتابیس خوانده میشود. این نسخه ممکن است توسط کاربر دیگری ذخیره شده باشد. |
| RowVersion | فیلدی از نوع byte[] که در دیتابیس (SQL Server) بهصورت خودکار و یکتا با هر تغییر مقدار جدیدی میگیرد. EF Core از این فیلد برای تشخیص دقیق تغییرات همزمان استفاده میکند. |
| CurrentValues.SetValues | متدی برای جایگزینکردن مقادیر فرم با مقدار دیتابیس. در استراتژیهایی مانند Store Wins برای لغو تغییرات کاربر و پذیرش مقدار دیتابیس استفاده میشود. |
| OriginalValues.SetValues | متدی برای بهروزرسانی وضعیت اصلی Entity با مقدار جدید از دیتابیس. در همه استراتژیها پس از حل Conflict برای هماهنگکردن EF با وضعیت جدید لازم است. |
| DbUpdateConcurrencyException | استثنایی که هنگام اجرای SaveChanges() و عدم تطابق RowVersion رخ میدهد. نشان میدهد رکورد مورد نظر از زمان بارگذاری تاکنون در دیتابیس تغییر کرده است. |
مدیریت همزمانی در EF Core یکی از مباحث مهم برای تضمین سلامت دادهها در برنامههای چندکاربره است. با استفاده از RowVersion، Attributeهای EF Core و مدیریت درست Conflictها میتوان سیستم قابل اعتمادی برای ویرایش دادهها ایجاد کرد.