مدیریت Concurrency در Entity Framework Core

ناسازگاری های همزمان موقع ای اتفاق می افتد که یک کاربر داده های یک موجودیت را به منظور اصلاح آن بازیابی کند و سپس کاربر دیگری همان داده ها را به روز می کند قبل از ذخیره تغییرات اولین کاربر به پایگاه داده. چگونگی رسیدگی به این ناسازگاری ها بستگی به ماهیت تغییرات ایجاد شده دارد. آخرین برنده last in winsدر بسیاری از موارد تنها یک نسخه درست وجود دارد، بنابراین مهم نیست که تغییرات یک کاربر تغییرات دیگری را تغییر دهد. در تئوری، این تغییرات باید همان به روز رسانی را به رکورد اضافه کند. به عنوان مثال، مهم نیست که دو کاربر سعی کنند یک رکورد ثابت ورزشی را با امتیاز نهایی به روز کنند. در این سناریو نیازی به استراتژی مدیریت همزمانی (concurrency) نیست. این به عنوان رویکرد last in wins (آخرین برنده) به کنترل همزمانی شناخته شده است.همزمانی بدبینانه Pessimistic Concurrencyهمزمانی بدبینانه شامل قفل رکورد های پایگاه داده برای جلوگیری از دسترسی دیگر کاربران به آنها می شود تا زمانی که قفل منتشر شود، بسیار شبیه زمانی است که دو کاربر سعی می کنند همان فایل به اشتراک گذاشته شده در شبکه را باز کنند.با این حال، توانایی قفل کردن رکوردها توسط تمام پایگاه های داده پشتیبانی نمی شود، و می تواند باتوجه به برنامه و منابع بسیاری را مصرف کند. این به سادگی عملی نیست در سناریوهای قطع شونده مانند برنامه های وب. Entity Framework Core پشتیبانی از کنترل همزمانی بدبینانه را فراهم نمی کند.همزمانی خوشبینانه Optimistic Concurrencyهمزمانی خوش بینانه فرض می کند که به روز رسانی انجام شده پذیرفته می شود، اما قبل از تغییر در پایگاه داده، مقادیر اصلی رکورد با ردیف موجود در پایگاه داده مقایسه می شوند و اگر هر تغییری شناسایی شود، یک استثنا همگانی ایجاد می شود .این در شرایطی مفید است که احتمال تغییر یک کاربر برای بازنویسی دیگران ، میتواند منجر به از دست دادن داده شود. به عنوان مثال، اگر دو کاربر به دنبال یک رکورد مشتری می روند، و یکی از کاربران یک شماره تلفن از دست رفته را اضافه می کند. کاربر دوم ، رکوردی که کاربر اول در حال تغییر دادن آن بود بازیابی کرد و آدرس را قبل از آنکه شماره تلفن توسط کاربر اول ثبت شود تغییر دهد. هنگامی که کاربر دوم تغییرات خود را انجام می دهد (که شماره تلفن را شامل نمی شود)، اولین تغییر از دست خواهد رفت. Entity Framework Core از مدیریت همزمانی خوش بینانه پشتیبانی میکند.تشخیص ناسازگاری همزمانی Detecting Concurrency Conflicts چهارچوب Entity Framework core از دو روش برای تشخیص ناسازگاری همزمان استفاده می کند:پیکربندی پراپرتی های موجود به عنوان نشانه های(توکن های) همزمانی؛ و اضافه کردن یک property اضافی "rowversion" به عنوان یک نشانه(توکن) همزمانی. پیکربندی خواص موجود Configuring existing properties خواص را می توان با استفاده از صفت ConcurrencyCheck به عنوان نشانه های همزمان از طریق data annotations پیکربندی کرد:public class Author { public int AuthorId { get; set; } public string FirstName { get; set; } [ConcurrencyCheck] public string LastName { get; set; } public ICollection<Book> Books { get; set; } }همچنین، خواص را می توان با استفاده از روش Fluent API متد IsConcurrencyToken پیکربندی کرد:public class SampleContext : DbContext { public DbSet<Author> Authors { get; set; } protected override void OnModelCreating (ModelBuilder modelBuilder) { modelBuilder.Entity<Author> () .Property (a => a.LastName).IsConcurrencyToken (); } } public class Author { public int AuthorId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public ICollection<Book> Books { get; set; } }هر پراپرتی موجود که به عنوان نشانه های concurrency(همزمانی) پیکربندی شده است با مقادیر اصلی آنها با شرط WHERE در عبارت UPDATE یا DELETE همراه است. وقتی دستور SQL اجرا می شود، EF Core انتظار دارد یک ردیف را پیدا کند که با مقادیر اصلی مطابقت داشته باشد.اگر هر یک از ستون های پیکربندی مقادیر خود را بین زمان بازیابی اطلاعات و زمانی که تغییرات به پایگاه داده فرستاده می شود،تغییر کرده باشد EF Core DbUpdateConcurrencyException را با پیام پرتاب می کند: Database operation expected to affect 1 row(s) but actually affected 0 row(s). Data may have been modified or deleted since entities were loaded. پیکربندی نشانه همزمانی (concurrency token configuration)می تواند به عنوان بسیاری از پراپرتی های غیر کلید اصلی(non-primary key properties) در صورت نیاز مورد استفاده قرار گیرد. باید مراقب بود زیرا این رویکرد می تواند به مقادیر WHERE بسیار طولانی و یا بسیاری از داده ها به آنها منتقل شود، به خصوص اگر هر یک از خواص پیکربندی شده به عنوان نشانه های همزمانی، مقادیر رشته نامحدود باشند، همانطور که در جایی که در آن فیلد Biography(بیوگرافی) به عنوان یک نشانه concurrecny شامل شده است:برای مقادیر بزرگ مانند رشته های نامحدود نباید این صفت را تعیین کنیماضافه کردن پراپرتی نسخه سطر Adding a RowVersion propertyرویکرد دوم مدیریت همزمانی شامل اضافه کردن یک ستون به جدول پایگاه داده برای ذخیره مهر نسخه برای ردیف داده است( version stamp for the row of data ). سیستم های پایگاه داده های مختلف به شیوه های مختلف به این مورد نیاز دارند. SQL Server نوع داده ای rowversion را برای این منظور ارائه می دهد. ستون یک عدد افزایشی ذخیره میکند. هر بار که داده ها وارد یا اصلاح شوند، تعداد افزایش می یابد.کاربر A ممکن است یک ردیف از داده ها را بازیابی کند، و سپس کاربر B آن را بازیابی کند. مقدار rowversion برای ردیف برای هر دو کاربر یکسان است.اگر کاربر B تغییرات را ارائه کند، مقدار rowversion در جدول 1 واحد برای این ردیف افزایش می یابد.اگر کاربر A پس از آن تلاش کند که رکورد مشابه را تغییر دهد، مقدار rowversion در شرط WHERE با مقدار کلید اصلی آن ترکیب شده و دیگر با ردیف موجود را در پایگاه داده مطابقت نخواهد داد و EF Core DbUpdateConcurrencyException را پرتاب می کند.یک پراپرتی باید از نوع داده آرایه بایت باشد که به ستون rowversion مرتبط شود.پراپرتی پیکربندی شده می تواند برای بررسی(چک کردن) همزمانی با اضافه کردن صفت TimeStamp در روش data annotations شرکت کند:public class Author { public int AuthorId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public ICollection<Book> Books { get; set; } [TimeStamp] public byte[] RowVersion { get; set; } } اگر شما ترجیح می دهید برای پیکربندی پراپرتی از API Fluent استفاده کنید ، از متد IsRowVersion استفاده کنید:public class SampleContext : DbContext { public DbSet<Author> Authors { get; set; } protected override void OnModelCreating (ModelBuilder modelBuilder) { modelBuilder.Entity<Author> () .Property (a => a.RowVersion).IsRowVersion (); } } public class Author { public int AuthorId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public ICollection<Book> Books { get; set; } public byte[] RowVersion { get; set; } }توجه داشته باشید که متد IsRowVersion در EF Core 1.1 اضافه شده است.این یک روش راحتی است که رویکرد قبلی را که روش ترکیبی از متد IsConcurrencyToken با متد ValueGeneratedOnAddOrUpdate بود را ساده کرده است (پس برای نسخه قبل از 1.1باید به روش زیر عمل کر):public class SampleContext : DbContext { public DbSet<Author> Authors { get; set; } protected override void OnModelCreating (ModelBuilder modelBuilder) { modelBuilder.Entity<Author> () .Property (a => a.RowVersion) .IsConcurrencyToken () .ValueGeneratedOnAddOrUpdate (); } }در هر دو رویکرد(روش)، همزمانی نتیجه یکسانی دارند - ستون RowVersion به عنوان یک نوع پایگاه داده پیکربندی شده است، که به صورت خودکار نسخه بندی ردیف (به عنوان مثال rowversion در SQL Server) را تنظیم می کند، نه یک نوع varbinary، که mapping پیش فرض برای انواع آرایه های byte است.متد SaveChanges باید در یک بلوک try-catch فراخوانی شود تا هر استثنائی DbUpdateException را بگیرد و اقدام مناسب انجام شود، مانند ارائه رکورد تازه به کاربر:try { db.SaveChanges (); // move on } catch (DbUpdateException) { // get latest version of record for display }در یک سناریو قطع شونده مانند یک برنامه وب، شما احتمالا مقادیر هر «نشانه های همزمانی - concurrency tokens» را در فیلد های پنهان(hidden fields) ذخیره می کنید، اگر آنها به صورت فیلدهای عادی فرم نباشد.اینگونه اطمینان حاصل می شود که آنها به عنوان مقادیر پارامتر به شرط WHERE در هر عبارت UPDATE یا DELETE در دسترس هستند.منبع