المتابعون للمدونة

السبت، 20 مايو 2017

Optimistic Concurrency


التطابق المتفائل Optimistic Concurrency:

هذا هو الحل المفضل والأسهل في تقنية ADO.NET، وقد سمي بهذا الاسم لأنه يفترض أن المستخدمين لن يحاولوا تعديل قاعدة البيانات، أثناء تعاملك مع بياناتها في برنامجك، إلا في حالات نادرة.
لكن ماذا يحدث لو حدث التعارض فعلا؟.. كيف نحل المشكلة في هذه الحالة؟
في الحقيقة هناك عدة حلول متبعة، وعليك اختيار الحل الذي يناسبك منها تبعا لما يناسبك.. وهذه الحلول هي:

أ. الحل الأول: عندما تقوم بتحديث السجل، ابحث عنه في قاعدة البيانات بواسطة قيم كل حقوله، وليس فقط بواسطة المفتاح الأساسي.. هكذا مثلا ستكون جملة الاستعلام الخاصة بتحديث سجلات المؤلفين:
UPDATE Authors
SET Author = @Author,
        CountryID = @CountryID,
         Phone = @Phone,
        About = @About
WHERE (ID = @Original_ID) AND
        (Author = @Original_Author) AND
        (CountryID = @Original_CountryID) AND
         (@IsNull_Phone = 1) AND (Phone IS NULL) OR
        (ID = @Original_ID) AND
        (Author = @Original_Author) AND
        (CountryID = @Original_CountryID) AND
        (Phone = @Original_Phone)

هذا الاستعلام يضمن لك أنه لو حدث أي تغيير في السجل من قبل مستخدمين آخرين، فلن يعثر عليه برنامجك، وبالتالي لن يتم حفظ التغييرات التي قام بها مستخدم برنامجك.
لعلك تلاحظ في هذا الاستعلام وجود معاملين للتعامل مع كل حقل.. مثلا، يتم التعامل مع حقل المؤلفين من خلال المعاملين @Author و @Original_Author.. فما هو الفارق بينهما؟
لكي تفهم هذا الفارق، عليك أن تعرف أن مجموعة البيانات DataSet تحتفظ بنسختين من كل سجل يتم وضعه فيها:
- النسخة الأصلية Original Version:
وهي تحتوي على بيانات السجل الأصلي كما تم تحميلها من قاعدة البيانات، لاستخدامها بعد ذلك في البحث عن السجل الأصلي في قاعدة البيانات لتحديثه.

- النسخة الحالية Current Version:
وهي تحتوي على بيانات السجل المعدلة بعد التغييرات التي أجراها المستخدم عليها، وذلك لاستخدامها في تحديث السجل الأصلي في قاعدة البيانات.

وكل ما يفعله استعلام التحديث السابق، هو تعريف معاملين لكل حقل، أحدهما يقرأ قيمته الحالية (مثل @Author) ويتم استخدامه لحفظ التغييرات في قاعدة البيانات، والآخر يقرأ قيمته الأصلية (مثل @Original_Author) ويستخدم للبحث عن السجل الأصلي في قاعدة البيانات.. ويتم التفريق بين هذين المعاملين باستخدام الخاصية SourceVersion الخاصة بكائن المعامل DataParameter، والذي يمكن إرسال قيمتها من خلال المعامل التاسع لحدث الإنشاء New.. هكذا مثلا يتم تعريف المعامل @Author.. لاحظ أننا لن نرسل قيمة المعامل SourceVersion، لهذا سيقرأ القيمة المعدلة للسجل افتراضيا:
Dim P1 As New SqlParameter("@Author",
        SqlDbType.NVarChar, 0, "Author")
وهكذا يتم تعريف المعامل @Original_Author:
Dim P2 As New SqlParameter("@Original_Author",
        SqlDbType.NVarChar, 0,
        ParameterDirection.Input, False, 0, 0, "Author",
        DataRowVersion.Original, Nothing)
لكن.. لماذا لا يوجد شرط على الحقل About في المقطع Where؟
السبب في هذا أننا عرفنا هذا الحقل من النوع nvarchar(MAX)، وهذا معناه أنه يتسع لنص قد يصل إلى 2 مليار حرف، وهذا حجم هائل، وستكون مقارنة هذا الحقل مضيعة للوقت.. لكن لو كنت مصرا، فيمكنك تعديل الاستعلام.. لا أنصحك بفعل هذا من نافذة الخصائص، لأنها ستعجز عن إنشاء المعامل   @Original_About بشكل صحيح، وبدلا من هذا يمكنك إضافة هذا الكود في بداية حدث تحميل النموذج:
' إضافة شرط إلى نهاية استعلام التحديث
'سيحدث خطأ لو كان هناك استعلام تحديد في نهاية استعلام التحديث
DaAuthors.UpdateCommand.CommandText &=
        " And About = @Original_About"
' تعريف معامل جديد
Dim P As New SqlParameter("@Original_About",
        SqlDbType.NVarChar, -1,
        ParameterDirection.Input, False, 0, 0, "About",
        DataRowVersion.Original, Nothing)
' إضافة المعامل إلى مجموعة معاملات أمر التحديث
DaAuthors.UpdateCommand.Parameters.Add(P)
أو يمكنك فتح ملف تصميم النموذج Form1_Designer.vb، وتعديل استعلام التحديث مباشرة، وإن كنت لا أنصح بهذا.
لاحظ أن من الأفضل تغيير نوع الحقل About ليكون أكثر ملاءمة لوظيفته.. يمكنك افتراض أن أطول نبذة لا تزيد عن 500 حرف مثلا، وتعريف هذا الحقل من النوع nvarchar(50).
دعنا نعد إلى استعلام التحديث السابق، فمازال هناك نوع ثالث من المعاملات لم نتطرق إليه.. هذا المعامل مخصص لللتعامل مع القيم المنعدمة NULL (مثل المعامل @IsNull_Phone).. وسبب احتياجنا إلى هذا المعامل، هو أن أي عملية مقارنة مع خانة منعدمة تعطي دائما False، لهذا لو كانت خانة الهاتف فارغة في قاعدة البيانات، وكانت فارغة أيضا في النسخة الأصلية من السجل، فإن مقارنتهما ستعطي False، وهذا يعني أن برنامجك لن يستطيع تحديث خانة الهاتف أبدا!
لحل هذه المشكلة، نستخدم الشرط التالي:
(@IsNull_Phone = 1) AND (Phone IS NULL)
OR (ID = @Original_ID)
هذا الشرط يتأكد من أن خانة الهاتف فارغة في مجموعة البيانات، وأنها فارغة أيضا في قاعدة البيانات، أو أن الخانتين فيهما قيمتان متساويتان.
ويتم تعريف المعامل @IsNull_Phone بوضع القيمة True في الخاصية SourceColumnNullMapping الخاصة بكائن المعامل، وهو ما يمكن فعله بإرسال القيمة True إلى المعامل التاسع في إحدى صيغ حدث الإنشاء New كالتالي:
Dim P3 As New SqlParameter("@IsNull_Phone",
         SqlDbType.Int, 0, ParameterDirection.Input,
         0, 0, "Phone", DataRowVersion.Original,
         True, Nothing, "", "", "")
لاحظ أن موصل البيانات يستخدم استعلام التحديث السابق بصورة افتراضية، لكن هذا قد يهبط بكفاءة برنامجك، إذا كان الجدول يحتوي على عدد كبير من السجلات، ما يعقد استعلام التحديث، ويستهلك وقتا ملموسا من سيكويل سيرفر للبحث عن السجل في قاعدة البيانات، لأنه سيقارن هنا كل الخانات، وليس من المتوقع وجود فهارس لكل أعمدة قاعدة البيانات.

ب. الحل الثاني: عندما تقوم بتحديث السجل، ابحث عنه في قاعدة البيانات بواسطة مفتاحه الأساسي فقط (كما فعلنا في التطابق المتشائم).. ميزة هذه الطريقة أنها تبسط استعلام التحديث، وتجعل العثور على السجل في قاعدة البيانات أسرع لأن المفتاح الأساسي مفهرس Indexed، وهي ميزة هائلة في قواعد البيانات الضخمة.. لكن عيب هذه الطريقة هي أنها تستخدم مبدأ "آخر تحديث يكسب!".. حيث يتمّ حفظ السجلات إلى قاعدة البيانات، حتّى ولو كانت هناك تعديلات قد أجراها مستخدم آخر عليها.. إنّك تفرض سجلاتك على قاعدة البيانات رغم أنف الجميع (وهذا سيخرب بيت مدير المخازن عند تعديل سعر كتاب "عصا الحكيم").. لكن أحيانا تكون هذه الطريقة مقبولة، كما في أنظمة حجز رحلات الطيران، لأنّ آخر تعديل في مواعيد الحجز هو الأولى بالاعتبار.

ج. الحل الثالث: عندما تقوم بتحديث السجل، ابحث عنه في قاعدة البيانات بواسطة المفتاح الأساسي وطابع الوقت Timestamp.. لفعل هذا عليك إضافة عمود من النوع  timestamp إلى الجدول.. هذا العمود يتغير تلقائيا كلما تم تعديل السجل، وبهذا لو كان طابع الوقت الذي تحتفظ به مجموعة البيانات مختلفا عن طابع الوقت الموجود في قاعدة البيانات، فهذا معناه أن السجل قد تغير، وفي هذه الحالة لن يحفظ برنامجك التغييرات في هذا السجل.. لاحظ أن المعالج السحري لموصل البيانات ينتج أوامر تحديث تعتمد على طابع الوقت إذا وجده في استعلام التحديد، أما إذا لم يجده، فإنه ينتج أوامر تحديث تقارن كل الحقول كما في الطريقة أ. 

لاحظ أن الطريقتين أ و ج هما الأكثر شيوعا، لكن بهما مشكلة كبيرة، وهي أن برنامجك سيكتفي بعرض رسالة خطأ للمستخدم تخبره بأن أحد الصفوف يتعارض مع قاعدة البيانات، دون أن يعرف التغييرات التي حدثت في قاعدة البيانات، ودون أن يستطيع إعادة حفظ السجل، لأن نفس الخطأ سيستمر في الحدوث!!

من كتاب: من الصفر إلى الاحتراف برمجة قواعد البيانات في فيجوال بيزيك دوت نت ADO .NET.. للتنزيل:

ليست هناك تعليقات:

إرسال تعليق

ملحوظة: يمكن لأعضاء المدونة فقط إرسال تعليق.

صفحة الشاعر