دوره آموزشی Entity FrameWork Core - قسمت هفتم
تداخل همزمانی(Concurrency conflicts) زمانی رخ میدهد که یک کاربر دادههای موجودیت را به منظور اصلاح آن بازیابی میکند و سپس کاربر دیگری دادههای همان موجودیت را قبل از نوشته شدن اولین تغییرات کاربر در پایگاه داده بهروزرسانی میکند.
اما چطور با این موضوع مواجه شویم و با آن به عنوان یک مسئله برخورد کنیم. قبل از ادامه بحث لازم است فهم صحیحی از برخی مفاهیم که در ادامه به آنها پرداخته خواهد شد داشته باشیم.
خوب EF Core کنترل همزمانی خوشبینانه(optimistic concurrency) را پیاده سازی می کند، به این معنی که به چندین فرآیند یا کاربر اجازه می دهد تا بدون نیاز به همگام سازی یا قفل کردن، تغییرات را به طور مستقل انجام دهند. در شرایط ایده آل، این تغییرات تداخلی با یکدیگر ندارند و بنابراین می توانند موفق شوند. در بدترین حالت، دو یا چند فرآیند سعی در ایجاد تغییرات متناقض خواهند داشت و تنها یکی از آنها باید موفق شود.
پراپرتی هایی که بهعنوان concurrency tokens پیکربندی شدهاند برای پیاده سازی کنترل خوشبینانه همزمانی(optimistic concurrency) استفاده میشوند.
هر زمان که عملیات بهروزرسانی یا حذف در طول SaveChanges انجام شود، مقدار concurrency token در پایگاه داده با مقدار اصلی خوانده شده توسط EF Core مقایسه میشود.
وضعیتی که کاربر دیگری عملیاتی را انجام داده است که با عملیات فعلی در تضاد است، به عنوان تداخل همزمان(concurrency conflict) شناخته می شود.
ارائه دهندگان پایگاه داده(Database providers) مسئول اجرای مقایسه مقادیر توکن همزمانی هستند.
در پایگاههای داده رابطهای EF Core مقدار concurrency token را در بند WHERE هر عبارت UPDATE یا DELETE بررسی می کند. پس از اجرای دستورات، EF Core تعداد ردیف هایی که تحت تأثیر قرار
گرفته اند را می خواند.
اگر هیچ ردیفی تحت تأثیر قرار نگیرد، تداخل همزمانی (Concurrency conflicts) شناسایی میشود و EF Core DbUpdateConcurrencyException را پرتاب(throws) میکند.
اگر یک کاربر سعی کند برخی از تغییرات را در یک یک موجودیت ذخیره کند، اما کاربر دیگری بروی همان موجودیت تغییراتی داده باشد، یک استثنا ایجاد می شود.
در این مرحله، برنامه به سادگی می تواند به کاربر اطلاع دهد که به دلیل تغییرات متناقض، به روز رسانی موفقیت آمیز نبوده و ادامه دهد. اما مطلوبتر این است که از کاربر خواسته شود مطمئن شود که آیا این رکورد همچنان همان اطلاعات مورد نظر او را نشان می دهد و اینکه دوباره عملیات را انجام دهد.
این فرآیند نمونه ای از نحوه برخورد با تداخل همزمانی (concurrency conflicts) است.
حل یک تضاد همزمانی شامل ادغام تغییرات معلق(pending changes) از DbContext فعلی با مقادیر موجود در پایگاه داده است. اینکه چه مقادیری با هم ادغام می شوند متفاوت است و ممکن است توسط ورودی کاربر هدایت شود.
سه مجموعه از مقادیر برای کمک به حل تعارض همزمانی (concurrency conflicts) وجود دارد:
رویکرد کلی برای رسیدگی به تضادهای همزمانی (concurrency conflicts) عبارت است از:
در مثال زیر، Person.FirstName و Person.LastName به عنوان concurrency tokens تنظیم شده اند.
using var context = new PersonContext(); // Fetch a person from database and change phone number var person = context.People.Single(p => p.PersonId == 1); person.PhoneNumber = "555-555-5555" // Change the person's name in the database to simulate a concurrency conflict context.Database.ExecuteSqlRaw( "UPDATE dbo.People SET FirstName = 'Jane' WHERE PersonId = 1"); var saved = false; while (!saved) { try { // Attempt to save changes to the database context.SaveChanges(); saved = true; } catch (DbUpdateConcurrencyException ex) { foreach (var entry in ex.Entries) { if (entry.Entity is Person) { var proposedValues = entry.CurrentValues; var databaseValues = entry.GetDatabaseValues(); foreach (var property in proposedValues.Properties) { var proposedValue = proposedValues[property]; var databaseValue = databaseValues[property]; // TODO: decide which value should be written to database // proposedValues[property] = <value to be saved>; } // Refresh original values to bypass next concurrency check entry.OriginalValues.SetValues(databaseValues); } else { throw new NotSupportedException( "Don't know how to handle concurrency conflicts for " + entry.Metadata.Name); } } } }
پراپرتی هایی که بهعنوان concurrency tokens پیکربندی شدهاند برای پیاده سازی کنترل خوشبینانه همزمانی(optimistic concurrency) استفاده میشوند. بیایید نحوه این پیکربندی را با هم ببینیم :
public class Person { public int PersonId { get; set; } [ConcurrencyCheck] public string LastName { get; set; } public string FirstName { get; set; } }
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Person>() .Property(p => p.LastName) .IsConcurrencyToken(); }
ـ Timestamp/rowversion یک پراپرتی است که هر بار که یک ردیف درج یا بهروزرسانی میشود، یک مقدار جدید بهطور خودکار توسط پایگاه داده تولید میشود. این ویژگی همچنین به عنوان یک concurrency token در نظر گرفته میشود و اطمینان حاصل میکند که اگر ردیفی که بهروزرسانی میکنید از زمانی که شما آن را درخواست کردهاید تغییر کرده است، استثنا دریافت میکنید. جزئیات دقیق بستگی به ارائه دهنده پایگاه داده مورد استفاده دارد. در SQL Server معمولاً از یک []byte استفاده می شود که به عنوان یک ستون ROWVERSION در پایگاه داده تنظیم می شود.
میتوانید یک پراپرتی را بهصورت timestamp/rowversion پیکربندی کنید:
public class Blog { public int BlogId { get; set; } public string Url { get; set; } [Timestamp] public byte[] Timestamp { get; set; } }
internal class MyContext : DbContext { public DbSet<Blog> Blogs { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Blog>() .Property(p => p.Timestamp) .IsRowVersion(); } } public class Blog { public int BlogId { get; set; } public string Url { get; set; } public byte[] Timestamp { get; set; } }
بیشتر بخوانید : دوره آموزشی Entity FrameWork Core - قسمت نهم
بیشتر بخوانید : دوره آموزشی Entity FrameWork Core
بیشتر بخوانید : نقشه راه توسعه دهندگان Asp.NET Core