التطابق الواقعي Realistic Concurrency:
لحل مشاكل التطابق المتشائم والتطابق
المتفائل، أقترح عليك هذا الحل الذي ابتكرته أنا وأسميه بالتطابق الواقعي، ويتم
باستخدام الحدث RowUpdated
الخاص بموصل البيانات بالطريقة التالية:
- إذا كانت
قيمة الخاصية e.RecordsAffected تساوي صفرا، فهذا معناه أن أمر التحديث لم يؤثر على قاعدة
البيانات، لأنه لم يجد السجل المطلوب تحديثه، إما لأن مستخدما آخر حذفه أو عدل
بياناته.. هذا هو التعارض الذي نبحث عنه.. لاحظ أن وجود الجملة SELECT في نهاية أمر التحديث سيجعل الخاصية RecordsAffected تعيد الرقم 0 دائما.. لهذا إذا أردت أن تستفيد من هذه الخاصية في
معرفة إن كان التحديث قد تم أم لا، فعليك أن تزيل جملة التحديد من نهاية أمر
التحديث.. وسنعرف كيف نفعل هذا من خلال المعالج السحري لموصل البيانات لاحقا.
- ضع نصا
يدل على حدوث خطأ في الخاصية e.Row.RowError، مثل "لقد حدث تعارض مع السجل الأصلي لأن أحد المستخدمين قام
بتعديله أو حذفه".. هذا سيجعل أيقونة الخطأ تظهر بجوار السجل في جدول العرض،
وعند التحليق فوقها بالفأرة سيظهر تلميح على الشاشة يعرض للمستخدم النص الذي كتبته
في هذه الخاصية.
- إذا أردت أن يحدث خطأ في البرنامج في سطر استدعاء الوسيلة Update لتعالجه بطريقتك الخاصة، فضع في الخاصية e.Status القيمة ErrorsOccurred.. أما إذا أردت مواصلة عملية التحديث، فضع فيها القيمة Continue أو SkipCurrentRow.. دعنا نستخدم القيمة الأخيرة.
- يبدو الأمر رائعا حتى الآن، وسيلاحظ المستخدم ظهور أيقونات الخطأ
بجوار السجلات التي فشل تحديثها.. لكن المشكلة أن المستخدم لا يعرف التعديل الذي
أدخله المستخدمون الآخرون على السجل الأصلي في قاعدة البيانات.. لهذا سنلجأ إلى
طريقة مبتكرة، وهي استخدام موصل بيانات اسمه DaErrAuthor لتحميل السجل الأصلي مرة أخرى في مجموعة بيانات خاصة اسمها DsErr، وعرضه في جدول عرض آخر اسمه DgErrors، ليقارن المستخدم بين البيانات التي يريد حفظها، والبيانات التي
حفظها مستخدم آخر، ويتخذ قراره بناء على هذا، كما هو موضح في الصورة.
لاحظ أن السجلات التي
حذفها مستخدم آخر من قاعدة البيانات لن تظهر في جدول السجلات المعدلة.. من السهل
أن يفهم المستخدم أن السجل قد حذف إذا لم يجده، لكننا أيضا نستطيع التسهيل عليه،
بتغيير رسالة الخطأ إذا كان استعلام التحديث لا يعيد أية سجلات، وذلك كالتالي:
If
DaErrAuthor.Fill(DsErr, "Authors") > 0 Then
e.Row.RowError =
"حدث تعارض مع السجل الأصلي" &
" لأن أحد المستخدمين قام
بتعديله أو حذفه"
Else
e.Row.RowError =
"هذا السجل حذفه مستخدم" &
" آخر من قاعدة البيانات"
End If
- بقيت أمامنا خطوة أخيرة، وهي: كيف نسمح للمستخدم بتعديل السجل
المعدل أو إعادة السجل المحذوف، إن قرر هذا؟.. أنا أرى أن أفضل حل، هو عرض قائمة
موضعية Context
Menu للمستخدم حينما يضغط الصف الذي به خطأ في الجدول
العلوي، ومن هذه القائمة يختار ما يناسبه مما يلي:
أ. أريد حفظ
تعديلاتي:
سنستخدم هذا الأمر عندما يغير مستخدم آخر السجل،
وهو ينسخ القيم الأصلية من سجل قاعدة البيانات المعدل ويجعلها القيم الأصلية للسجل
الخاص بالمستخدم، وهذا حتى ينجح استعلام التحديث في العثور على السجل الأصلي في
قاعدة البيانات، ومن ثم لو ضغط المستخدم زر الحفظ يتم حفظ تعديلاته.. والمستخدم هو
المسئول عن نقل أية قيمة يدويا من السجل الأصلي المعروض في جدول العرض السفلي إلى
السجل الخاص به قبل ضغط زر الحفظ.. والسجل الذي تنجح محاولة حفظه مرة أخرى، علينا
أن نزيل سجل الخطأ المناظر له من جدول العرض السفلي.
لاحظ أنك في البرامج العملية قد تحتاج إلى
مراعاة أولويات المستخدمين في إجراء التغيير.. فمثلا: لو كان المدير هو من قام
بتعديل السجل، فسيستشيط غضبا لو قام أحد الموظفين بإلغاء تعديله!.. لهذا قد تحتاج
إلى إضافة حقل اسمه UserID
إلى الجدول، ليربطه بجدول المستخدمين Users، بحيث تضع رقم المستخدم الذي أجرى آخر تعديل، وعند حدوث التعارض
في البرنامج، لا تسمح للمستخدم باتخاذ قرار حفظ تعديلاته إلا إذا كان المستخدم
الآخر أقل أولوية منه أو على الأقل له نفس الأولوية في إجراء التعديلات.. ويمكنك
معرفة أولويات المستخدمين من الجدول User، الذي لا بد أن يحتوي على عمود يوضح وظيفة المستخدم، أو عمود يوضح
ترتيبه في السلم الوظيفي أو مدى صلاحياته.
ب. إلغاء
تعديلاتي:
سنستخدم هذا الأمر عندما يغير مستخدم آخر
السجل.. هذا الأمر مفيد عندما يقرر مستخدم البرنامج إلغاء تعديلاته هو، وكل ما
سنفعله هو نقل السجل المعدل إلى مجموعة البيانات ليحل محل السجل الذي عدله مستخدم
البرنامج، وهذا يلغي تعديلاته، ويحافظ على التعديل القادم من قاعدة البيانات.
ج. أريد إعادة إدراج السجل المحذوف:
سنستخدم هذا الأمر إذا حذف مستخدم آخر السجل من
قاعدة البيانات.. وكل ما يفعله، هو تغيير حالة السجل الحالي في مجموعة البيانات
إلى Added، ليعتبره أمر التحديث سجلا جديدا ويضيفه إلى قاعدة البيانات.
د. إزالة السجل المحذوف:
سنستخدم هذا الأمر إذا حذف مستخدم آخر السجل من
قاعدة البيانات.. وكل ما يفعله، هو حذف السجل الخاص بالمستخدم من مجموعة البيانات.
وستجد الكود
الكامل الذي ينفذ كل هذه الأفكار في المشروع OptimisticConcurrency.. وتوجد نسخة أخرى منه في المشروع OptimisticConcurrencyWithTimeStamp، نستخدم فيها طابع الوقت، حيث عرفنا عمودا اسمه RowVersion في جدول المؤلفين نوعه Timstamp.. لاحظ أن عرض طابع الوقت في جدول العرض DatagridView يسبب أخطاء لأنه يحاول رسم طابع الوقت باعتباره صورة!.. ولحل
المشكلة، عليك إخفاء عمود طابع الوقت، فلا يوجد مبرر أصلا لعرضه للمستخدم!.. لفعل
هذا استخدمنا الجملة التالية:
DgAuthors.Columns("RowVersion").Visible
= False
لاحظ أن هناك مشكلة ستواجهنا في هذا البرنامج،
بسبب عدم استخدامنا جملة تحديد Select بعد جملة التحديث Update، وذلك لأن طابع الوقت يتغير في قاعدة البيانات باستمرار بعد كل
عملية تحديث، ولو لم ننعش مجموعة البيانات بقيمه الجديدة، فستحدث مشكلة تطابق بلا
داع.. ولحل هذه المشكلة، استخدمنا موصل
بيانات اسمه DaTimestamp،
مهمته الحصول على السجل الذي تم تحديثه، ووضعه في مجموعة البيانات لإنعاشها.. لهذا
يستخدم أمر التحديد الخاص بهذا الموصل جملة التحديد التالية:
Select * From Authors
Where ID = @ID
وأنسب مكان لاستخدام هذا الموصل، هو الحدث RowUpdated، لأنه ينطلق مباشرة بعد تحديث الصف، لهذا يمكننا أن نقرأ الصف مرة
أخرى بعد أن غيرت قاعدة البيانات طابع الوقت الخاص به.. لهذا طورنا جملة الشرط
التي نستخدمها في هذا الحدث، بإضافة المقطع Else كالتالي:
If e.RecordsAffected = 0 Then
' الكود المناسب لحل مشكلة التطابق
ElseIf e.StatementType <>
StatementType.Delete Then
TimestampCmd.Parameters(0).Value = e.Row("ID")
DaTimestamp.Fill(Ds,
"Authors")
End If
لاحظ أننا استخدمنا شرطا لاستثناء حالة حذف سجل،
فالسجل سيحذف من مجموعة البيانات كما حذف من قاعدة البيانات، وليست لدينا مشكلة.
غير هذا لن تجد أي اختلاف في كود هذا المشروع عن
المشروع OptimisticConcurrency، فالفروق كلها تنحصر في صيغة استعلامات التحديث، التي تزيد كفاءة
برنامجك بسبب استخدامها لطابع الوقت، بديلا عن مقارنة كل القيم الموجودة في خانات
الصف.
من
كتاب: من الصفر إلى الاحتراف برمجة قواعد البيانات في فيجوال بيزيك دوت نت ADO .NET.. للتنزيل:
ليست هناك تعليقات:
إرسال تعليق
ملحوظة: يمكن لأعضاء المدونة فقط إرسال تعليق.