في هذه المقالة سأتطرق لمفهوم الـ Data Concurrency وقد يكون فيه من الصعوبة في بداية التعامل معه بالنسبة لبعض المطورين، خصوصا أنه قد يتم تطبيقه في بعض التطبيقات خصوصا التطبيقات الحسابية أو تطبيبقات البنوك أو تطبيقات التسوق الإلكتروني.
الـ Data Concurrency تنقسم لقسمين وهما:
- Pessimistic
- Optimistic
نفترض لدينا برنامج مبيعات POS وبه جدول يُسمى Product في هذا الجدول يوجد ثلاثة حقول الاول رقم المنتج والثاني اسم المنتج والثالث سعر المنتج.
وبرنامج المبيعات هذا يتم استخدامه من قبل عدة مستخدمين .. نفترض انه يتم استخدامه من مستخدمين اثنين؛ المستخدم الأول هو البائع والمستخدم الثاني هو مدير النظام… نفترض ان البائع قام بجلب المنتج والذي رقمه 1 لبيعه.. وفي نفس اللحظة قام مدير النظام بجلب نفس رقم المنتج لتعديله او لحذفه.
فكر قليلا ما هي المشكلة هنا؟!!
دعنا نأخذ مثالا آخر.. نفترض انك تقرأ في هذه المقالة الآن وتريد إضافة تعليق، لكن قبل أن تقوم بعملة حفظ التعليق تمّ حذف المقالة من قاعدة البيانات من قبل مدير المنصة!
الآن أتوقع ان الصورة قد اكتملت في ذهنك، هذا هو مفهوم الـ Data Concurrency.
دعنا الآن نبدأ في شرح ماهي الطريقة المناسبة لوضع بعض الإجراءات التي تساعدنا على تلافي تلك المشاكل.
في هذه الحالة من تلك البرمجيات الكبيرة قد تختلف الأمور من برمجيات لأخرى.. الحلول هنا تعتمد على نوعية النظام نفسه وعلى الشئ الذي يريده صاحب النظام.. في حالة برنامج المبيعات الأول الذي أخذناه كمثال قد يقول لك صاحب المتجر ليس لدي مشكلة من ان يتم حذف المنتج او تعديله أثناء قيام البائع بجلبه في شاشة المبيعات. حسنا هنا أنت كمطور ستقوم بعمل الإجراءات المناسبة للتطبيق على تلك الحالة.
عندما أخذت مثال برنامج المبيعات وقلت ان صاحب المتجر يريد التعديل والحذف أثناء جلب المنتج في شاشة المبيعات، فقط أردت توضيح الحالة.. لكن في برمجيات المبيعات والبنوك تلك الآلية خاطئة ولا يتم استخدامها.. فالأصح ان لا يتم التعديل او الحذف اثناء جلب المنتج في شاشة المبيعات..حتى الإنتهاء من عملية بيعه.
هذه المقالة قد تكون مختلفة نوعا من مفهوم كيفية التعامل مع البرمجيات الضخمة وفي تقديري إذا كنت مطور مبتدئ قد ترى تلك المقالة ليست في إطار اهتمامك.. أيضا بعض المطورين لا يهتمون لموضوع الـ Data Concurrency لإعتبارت كثيرة منها عدم تلقيهم أي ملاحظات من العملاء الذين يعملون على أنظمتهم أو ان معظم برمجياتهم لايوجد بها هذا الشئ من الـ Data Concurrency لكن ما أن تدلف على برمجيات ضخمة عدد المستخدمين فيها ضخم ستواجه تلك الإشكالية ويجب أن تضعها في حسبانك وأنت تقوم بعمل برمجياتك.
او فرضنا أنك ذهبت للسوبر ماركت وأخذت قطعة شوكولاته وسعرها 10 جنيهات وذهبت لطاولة البائع (الكاشير) وقام البائع بقراءة باركود تلك الشوكولاته وظهرت على الشاشة سعرها 10 جنيهات… وقبل ان يقوم البائع بحفظ وطباعة الفاتورة لك قام مدير النظام في تلك اللحظة بتعديل سعر تلك الشوكولاته من 10 لـ 20 جنيها. وبعده قام البائع بحفظ وطباعة الفاتورة لك… عندها سيصبح إجمالي الفاتورة 20 جنيها وحينها ستبدأ أنت الزبون في الصياح في البائع.. قد تشتمه حتى وقد تقول له: لماذا في الباركود مسجلة 10 وعند بيعها 20؟ حسنا هنا يأتي دور الـ Data Concurrency.
سأقوم ببناء تطبيق بسيط جدا يساعدنا في وضع الـ Data Concurrency في سياقها وبعد بناء التطبيق سأتناول الـ Data Concurrency في بعض النقاط وسأوضح أيضا بعض الحالات البديلة التي يمكن استخدامها.
التطبيق يتكون من جدول وحيد في قاعدة بيانات SQL Sever واسمه Products وهذه هي الحقول الخاصة بالجدول كما موضحة بالصورة أدناه.
انتبه لحقل الـ ID قم بإعطاءه القيمة Identity وقم بجعلة مفتاح رئيسي PK
أيضا قمت بإنشاء Console App وبه فئة Class اسمها Product وبها الخصائص التالية:
[Table("Products")] public class Product { [Key] public int ID { get; set; } [StringLength(200)] public string ProductName { get; set; } [StringLength(200)] public String ProductCategory { get; set; } public Decimal SalesPrice { get; set; } [Timestamp] public byte[] Timestamp { get; set; } }
لاحظ لأخر خاصية وهي Timestamp هذه الخاصية مهمتها عمل نسخة من الحقل تساعد في حال قام المستخدم بجلب ذلك الحقل أو حذفه، وفي نفس اللحظة تقوم بعمل موازنة عندما يريد مستخدم آخر تعديل هذا الحقل. الخاصية تلك تم بلورتها بإضافة صفة Attribute واسمها Timestamp و بعض المطورين يقومون بتسمية ذلك الحقل بـ RowVersion .. وكما لاحظت اخي المطور هذا الحقل يأخذ النوع Timestamp في الـ SQL Server ويأخذ النوع Byte في الـ #C وهذا الحقل يجب ان يأخذ قيمة دائما حيث لا يتوجب ان يكون فارغ وقيمته دائما تكون Binary وهي مجموعة ارقام يقوم الـ SQL Server بتوليده فبالتالي قمنا بتعريفها كمصفوفة في فئة الـ Product.
لقد قمت بإستخدام Console App خصيصا لأنه يساعدني في التركيز في الشرح على موضوع المقالة فقط.. وبإمكانك أخذ ما سأكتبه هنا و إدراجه في برمجياتك سواء كانت ASP.NET MVC او WPF أو ASP.NET Web Form
قم بتثبيت مكتبة الـ Entity Framework داخل مشروعك كما موضح بالصورة التالية:
أيضا قمت بإنشاؤ فئة Class آخر بإسم ConcurrencyTestDvContext وهو الذي سيربط بين التطبيق وقاعد البيانات وهو كالتالي:
public class concurrenyTestDbContext : DbContext { public concurrenyTestDbContext() : base("name = concurrenyTestDbContext") { } public DbSet<Product> Products { get; set; } }
أيضا قمت بتعريف ملف الإتصال من خلال ملف الـ App.config كالتالي:
<connectionStrings> <add name="concurrenyTestDbContext" connectionString="Data Source=DEV01;Initial Catalog=Concurrency_DB;Integrated Security=True" providerName="System.Data.SqlClient"/> </connectionStrings>
قم بلصق الشفرة التالية في الاسلوب Main داخل الفئة Program:
لا تقم بمسح الـ Comment الموجود في جملة الـ Catch لأننا سنقوم بشرحه فيما بعد.
static void Main(string[] args) { using (var context = new concurrenyTestDbContext()) { var update = context.Products.FirstOrDefault(c => c.ProductName == "Milk"); update.ProductCategory = "Ahmed"; try { context.SaveChanges(); } catch(DbUpdateConcurrencyException ex) { // ex.Entries.Single().Reload(); context.SaveChanges(); } } Console.ReadLine(); }
قبل ان تقوم بتنفيذ هذه الشفرة قم بإدخال حقل في قاعدة البيانات وقم بإسناد الحقل كالتالي:
ProductName = Milk
Price = 12
ProductCategory = Test
ليس بالضرورة كتابة نفس قيم الحقول كما كتبتها هنا بإمكانك كتابة القيم التي نود كتابتها.. فقط كتبتها هنا لكي أستطيع التوضيح لك عند تنفيذ البرنامج.
الآن نريد تنفيذ عملية تحديث لقاعدة البيانات حيث نريد تغيير قيمة الحقل ProductCategory لـ Drinks في الحقل ProductName والذي يحمل القيمة Milk وهو ما كتبناه كالتالي:
var update = context.Products.FirstOrDefault(c => c.ProductName == "Milk"); update.ProductCategory = "Ahmed";
قم بوضع BreakPoint في جملة الـ ()Context.SaveChanges الـموجودة في جملة try وبعدها قم بالضغط على زر F5 لتنفيذ البرنامج.
ما ان يتوقف البرنامج قم بفتح الـ SQL Server وقم بتعديل قيمة الحقل ProductCategory لأي قيمة أخرى.. بعدها قم بالرجوع للفيجوال ستديو وقم بالضغط على زر F10 حتى نستطيع الإستمرار في تنفيذ البرنامج.
لاحظ الآن البرنامج سيقوم بعمل Throw وهذه الرسالة توضح لك ان هناك تعديل تم على قاعدة البيانات.. والبرنامج لن يستطيع التعديل.
وهنا تفاصيل أكثر للخطأ
دعنا نشرح العملية بصورة أدق
في شفرة البرنامج قمنا بإستدعاء الحقل ProductName والذي قيمته Milk وذلك حتى نقوم بتغيير تلك القيمة لـ Drinks ، هنا مجرد استدعاءنا لهذا الحقل سيقوم الـ SQL Server بإستدعاء حقل الـ Timestamp أيضا والذي يحمل رقم معين. لقد قمنا بعمل BreakPoint في جملة الـ ()Context.SaveChanges وقمنا بتغيير قيمة الحقل لقيمة أخري، مجرد تغييرنا لقيمة الحقل سيقوم الـ SQL Server بإنشاء قيمة أخرى لحقل الـ Timestamp وبرجوعنا للبرنامج لعملية استمرار التنفيذ لتعديل قيمة الحقل الذذي قمنا بإستدعاءه وهو الـ ProductCategory سيقوم البرنامج بعملية Throw لأن قيمة الـ Timestamp هنا مختلفة وبالتالي لن يستطيع التعديل.
لو قمنا بفتح الـ SQL Server Profiler سنرى ان الـ SQL Server قام بتوليد الشفرة التالية:
exec sp_executesql N'UPDATE [dbo].[Products] SET [ProductCategory] = @0 WHERE (([ID] = @1) AND ([Timestamp] = @2)) SELECT [Timestamp] FROM [dbo].[Products] WHERE @@ROWCOUNT > 0 AND [ID] = @1',N'@0 nvarchar(200),@1 int,@2 binary(8)',@0=N'Ahmed',@1=4,@2=0x00000000000007E8
هنا لقد قمت بتغيير الحقل بالقيمة Ahmed .. إنظر للرقم الأخير 0x00000000000007E8 هذا االرقم قام بتوليده الـ Timestamp فبمجرد حصول اي عملية أخرى في نفس الحقل سيقوم الـ SQL Server بتغيير ولن يستطيع المستخدم الأول الذي بإستدعاء الحقل والذي به الـ Timestamp القديم لن يستطيع التعديل
بإمكانك فتح الـ SQL Server Profiler من الـ SQL Server Management Studio من قائمة Tools كما مبين بالصورتين التاليتين.
الآن قم بحذف الـ Comment الموجود بجملة الـ Catch وقم بتعديل القم لأخرى وقم بتنفيذ البرنامج مع وضع BreakPoint في جملة الـ ()Context.SaveChanges وقم بتغيير الحقل في قاعدة البيانات مرة أخرى.
هنا لن يقوم البرنامج بعمل Throw لأنّ هذا السطر قام بعمل Refresh للـ Context وهنا تُسمى تلك العملية بـ Store Win وهي التي ستقوم بإرجاعنا لأنواع الـ Data Concurrency وتحديدا النوع Optimistic.
لا تنسى ان تقوم بوضع الرسائل المناسبة التي سوف تظهر للمستخدم أثناء حصول تضارب في العمل على قاعدة البيانات Conflict وحتى تصبح برمجياتك أكثر تفاعل مع المستخدمين.
في المقابل النوع الأول Pessimistic لا يُنصح بإستخدمه لأنه يقوم بعمل Lock للحقل الذي يستدعيه المستخدم وبالتالي إذا أراد مستخد آخر إستدعاء نفس الحقل لن يستيطع حتى يقوم المستخدم الأول الذي قام بإستدعاءه من إنهاء الإجراء الذي يريده.
هناك بعض المطورين يقوم بإستخدام الحقل Datemodefied بدلا من التعامل مع الـ Data Concurrency لكن هذه العملية مكلفة جدا لأنها تتطلب منك إستخدام مجموعة من الـ Logic للتأكد من كل عملية تعديل تتم على قاعدة البيانات.
تحياتي
أعجبني كثيرا شرحك كنت أبحث عنها و استخدامها ولم أتوقع أن أجد مقالة بالعربي بكل هذه التفاصيل شكرا جزيلا