Skip to content

9. الوصول إلى قواعد البيانات

9.1. الموصل ADO.NET

لنعد إلى بنية الطبقات المستخدمة في مناسبات عديدة

في الأمثلة التي تمت دراستها، استخدمت الطبقة [dao] حتى الآن نوعين من مصادر البيانات:

  • البيانات المضمنة في الكود
  • البيانات الواردة من ملفات نصية

ندرس في هذا الفصل الحالة التي تأتي فيها البيانات من قاعدة بيانات. تتطور بنية الطبقات الثلاث عندئذٍ إلى بنية متعددة الطبقات. وهناك أنواع مختلفة منها. سندرس المفاهيم الأساسية باستخدام ما يلي:

في المخطط أعلاه، تتواصل الطبقة [dao] [1] مع SGBD [3] من خلالمكتبة فئات خاصة بـ SGBD المستخدمة والمرفقة معه. تنفذ هذه الطبقة وظائف قياسية مجمعة تحت مصطلح ADO (Active X Data Objects). يُطلق على هذه الطبقة اسم مزود (مزود الوصول إلى قاعدة البيانات هنا) أو موصل. تتوفر الآن معظم ملفات SGBD بموصل ADO.NET، وهو ما لم يكن الحال عليه في بدايات منصة .NET. لا توفر موصلات .NET واجهة قياسية لطبقة [dao]، لذا تحتوي هذه الطبقة في كودها على أسماء فئات الموصل. إذا تم تغيير SGBD، يتم تغيير الموصل والفئات، ومن ثم يجب تغيير الطبقة [dao]. هذه بنية فعالة لأنها مرنة، حيث إن الموصل .NET قد كُتبت من أجل SGBD معين، فهي تعرف كيف تستخدمه على النحو الأمثل، وهي صلبة أيضًا لأن تغيير SGBD يعني تغيير الطبقة [dao]. يجب وضع هذه الحجة الثانية في سياقها الصحيح: لا تقوم الشركات بتغيير SGBD كثيرًا. علاوة على ذلك، سنرى لاحقًا أنه منذ الإصدار 2.0 من .NET، يوجد موصل عام يوفر المرونة دون التضحية بالأداء.

9.2. طريقتان لاستخدام مصدر البيانات

تسمح منصة .NET باستغلال مصدر البيانات بطريقتين مختلفتين:

  1. الوضع المتصل
  2. الوضع غير المتصل

في الوضع المتصل، تقوم التطبيق

  1. اتصالاً بمصدر البيانات
  2. يعمل مع مصدر البيانات في وضع القراءة/الكتابة
  3. يغلق الاتصال

في وضع عدم الاتصال، يقوم التطبيق

  1. يفتح اتصالاً بمصدر البيانات
  2. يحصل على نسخة في الذاكرة من كل أو جزء من بيانات المصدر
  3. يغلق الاتصال
  4. يعمل مع نسخة الذاكرة من البيانات للقراءة/الكتابة
  5. عند انتهاء العمل، يفتح اتصالاً، ويرسل البيانات المعدلة إلى مصدر البيانات ليتم تضمينها، ويغلق الاتصال

نحن ندرس هنا الوضع المتصل فقط.

9.3. المفاهيم الأساسية لتشغيل قاعدة البيانات

سنعرض المفاهيم الرئيسية لاستخدام قاعدة البيانات باستخدام قاعدة بيانات SQL Server Compact 3.5. يأتي هذا SGBD مع Visual Studio Express. وهي قاعدة بيانات خفيفة لا تستطيع إدارة سوى مستخدم واحد في كل مرة. ومع ذلك، فهي كافية لتقديم البرمجة باستخدام قواعد البيانات. لاحقًا، سنقدم قواعد بيانات أخرى.

وستكون البنية المستخدمة كما يلي:

ستستخدم تطبيق وحدة التحكم [1] قاعدة بيانات من نوع SqlServer Compact [3,4] عبر موصل Ado.Net الخاص بـ SGBD [2].

9.3.1. : قاعدة بيانات نموذجية

سنقوم بإنشاء قاعدة البيانات مباشرة في Visual Studio Express. وللقيام بذلك، نقوم بإنشاء مشروع جديد من نوع console.

  • [1]: المشروع
  • [2]: نفتح نافذة "مستكشف قواعد البيانات"
  • [3]: ننشئ اتصالاً جديداً
  • [4]: يتم تحديد نوع SGBD
  • [5,6]: اختيار SGBD SQL Server Compact
  • [7]: يتم إنشاء قاعدة البيانات
  • [8]: يتم تغليف قاعدة بيانات SQL Server Compact في ملف واحد بامتداد .sdf. نحدد مكان إنشائها، هنا في مجلد مشروع C#.
  • [9]: تم تسمية قاعدة البيانات الجديدة باسم [dbarticles.sdf]
  • [10]: يتم تحديد اللغة الفرنسية. وهذا يؤثر على عمليات الفرز.
  • [11,12]: يمكن حماية قاعدة البيانات بكلمة مرور. هنا "dbarticles".
  • [13]: يتم تأكيد صفحة المعلومات. سيتم إنشاء قاعدة البيانات فعليًا:
  • [14]: اسم قاعدة البيانات التي تم إنشاؤها للتو
  • [15]: يتم تحديد خيار "Save my password" حتى لا تضطر إلى إعادة كتابته في كل مرة
  • [16]: نتحقق من الاتصال
  • [17]: كل شيء على ما يرام
  • [18]: نؤكد صفحة المعلومات
  • [19]: يظهر الاتصال في مستكشف قواعد البيانات
  • [20]: في الوقت الحالي، لا تحتوي قاعدة البيانات على أي جداول. نقوم بإنشاء واحدة. سيحتوي المقال على الحقول التالية:
    • id: معرف فريد - مفتاح أساسي
    • nom: اسم المادة - فريد
    • prix: سعر المادة
    • stockactuel: المخزون الحالي
    • stockminimum: الحد الأدنى للمخزون الذي يجب تجديد المنتج عنده
  • [21]: الحقل [id] هو من النوع الصحيح وهو المفتاح الأساسي [22] للجدول.
  • [23]: هذا المفتاح الأساسي من النوع Identity. يشير هذا المفهوم الخاص بـ SGBD SQL Server إلى أن المفتاح الأساسي سيتم إنشاؤه بواسطة SGBD نفسه. هنا سيكون المفتاح الأساسي عددًا صحيحًا يبدأ من 1 ويزداد بمقدار 1 مع كل مفتاح جديد.
  • [24]: يتم إنشاء الحقول الأخرى. تجدر الإشارة إلى أن الحقل [nom] له قيد فريدية [25].
  • [26]: يتم تسمية الجدول
  • [27]: بعد التحقق من صحة بنية الجدول، يظهر هذا الجدول في قاعدة البيانات.
  • [28]: نطلب عرض محتوى الجدول
  • [29]: الجدول فارغ في الوقت الحالي
  • [30]: نملأها ببعض البيانات. يتم التحقق من صحة السطر بمجرد الانتقال إلى إدخال السطر التالي. لا يتم إدخال الحقل [id]: يتم إنشاؤه تلقائيًا عند التحقق من صحة السطر.

يبقى لنا تكوين المشروع بحيث يتم نسخ هذه القاعدة، الموجودة حاليًا في جذر المشروع، تلقائيًا إلى مجلد تشغيل المشروع:

  • [1]: نطلب عرض جميع الملفات
  • [2]: تظهر قاعدة البيانات [dbarticles.sdf]
  • [3]: يتم تضمينها في المشروع
  • [4]: تؤدي عملية إضافة مصدر بيانات إلى مشروع إلى تشغيل معالج لا نحتاج إليه هنا [5].
  • [6]: أصبحت قاعدة البيانات الآن جزءًا من المشروع. نعود إلى الوضع العادي [7].
  • [8]: المشروع وقاعدة البيانات الخاصة به
  • [9]: في خصائص قاعدة البيانات، يمكننا أن نرى [10] أنها ستُنقل تلقائيًا إلى مجلد تشغيل المشروع. وهنا سيقوم البرنامج الذي سنكتبه باستردادها.

الآن بعد أن أصبح لدينا قاعدة بيانات متاحة، سنتمكن من الاستفادة منها. قبل ذلك، سنقوم ببعض التذكير SQL.

9.3.2. الأوامر الأساسية الأربعة للغة SQL

SQL (Structured Language Query) هي لغة، معيارية جزئيًا، للاستعلام عن قواعد البيانات وتحديثها. تتوافق جميع لغات SGBD مع الجزء المعياري من لغة SQL، لكنها تضيف إلى اللغة امتدادات خاصة تستفيد من بعض خصائص لغة SGBD. لقد صادفنا بالفعل مثالين على ذلك: غالبًا ما يعتمد الإنشاء التلقائي للمفاتيح الأساسية والأنواع المسموح بها لأعمدة الجدول على لغة SGBD.

الأوامر الأساسية الأربعة للغة SQL التي نقدمها هي أوامر موحدة ومقبولة من قبل جميع SGBD:

select col1, col2,... 
from table1, table2,...
where condition
order by expression
...
الاستعلام الذي يسمح بالحصول على البيانات الموجودة في قاعدة البيانات. الكلمات الرئيسية في السطر الأول هي فقط إلزامية، أما البقية فهي اختيارية. هناك كلمات رئيسية أخرى غير مذكورة هنا.
  1. يتم إجراء ربط مع جميع الجداول الموجودة خلف الكلمة الرئيسية from
  2. يتم الاحتفاظ فقط بالأعمدة الموجودة خلف الكلمة الرئيسية select
  3. يتم الاحتفاظ فقط بالصفوف التي تستوفي شرط الكلمة الرئيسية where
  4. تشكل الأسطر الناتجة المرتبة وفقًا للتعبير الوارد في الكلمة الرئيسية order by نتيجة الاستعلام. هذه النتيجة عبارة عن جدول.
insert into table(col1,col2, ...)
values (val1,val2, ...)
يُدرج سطراً في الجدول. (col1, col2, ...) يحدد أعمدة السطر المراد تهيئتها بالقيم (val1, val2, ...).
update table
set col1=val1, col2=val2
where condition
يُحدّث صفوف الجدول التي تستوفي الشرط (جميع الصفوف في حالة عدم وجود where). بالنسبة لهذه الصفوف، يتلقى العمود coli القيمة vali
delete from table
where condition
يحذف جميع صفوف الجدول التي تتحقق من الشرط

سنقوم بكتابة تطبيق وحدة تحكم يسمح بإصدار أوامر SQL على قاعدة البيانات [dbarticles] التي أنشأناها سابقًا. فيما يلي مثال على التنفيذ. يُطلب من القارئ فهم الأوامر SQL الصادرة ونتائجها.

Chaîne de connexion à la base : [Data Source=|DataDirectory|\dbarticles.sdf;Password=dbarticles;Persist Security Info=True]

Requête SQL (rien pour arrêter) : select id,nom,prix,stockactuel,stockminimum from articles

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

1 vélo 500 10 5
2 pompe 10 10 2
3 arc 600 4 1
4 flèches - lot de 6 100 12 20
5 combinaison de plongée 300 8 2
6 bouteilles d'oxygène 120 10 5

Requête SQL (rien pour arrêter) : insert into articles(nom,prix,stockactuel,stockminimum) values('x',100,10,1)
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (rien pour arrêter) : select id,nom,prix,stockactuel,stockminimum from articles

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

1 vélo 500 10 5
...
6 bouteilles d'oxygène 120 10 5
9 x 100 10 1

Requête SQL (rien pour arrêter) : update articles set prix=prix*1.1 where id=9
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (rien pour arrêter) : select id,nom,prix,stockactuel,stockminimum from articles

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

1 vélo 500 10 5
...
6 bouteilles d'oxygène 120 10 5
9 x 110 10 1

Requête SQL (rien pour arrêter) : delete from articles where id=9
Il y a eu 1 ligne(s) modifiée(s)

Requête SQL (rien pour arrêter) : select id,nom,prix,stockactuel,stockminimum from articles

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

1 vélo 500 10 5
...
6 bouteilles d'oxygène 120 10 5
  • السطر 1: سلسلة الاتصال: تحتوي على جميع المعلمات التي تسمح بالاتصال بقاعدة البيانات.
  • السطر 3: نطلب محتوى الجدول [articles]
  • السطر 16: يتم إدراج سطر جديد. تجدر الإشارة إلى أن الحقل id لم يتم تهيئته في هذه العملية لأن SGBD هو الذي سيولد قيمة هذا الحقل.
  • السطر 19: التحقق. السطر 28، تمت إضافة السطر بالفعل.
  • السطر 30: يتم زيادة سعر العنصر الذي تمت إضافته للتو بنسبة 10%.
  • السطر 33: يتم التحقق
  • السطر 42: تم بالفعل زيادة السعر
  • السطر 44: حذف المنتج الذي تمت إضافته سابقًا
  • السطر 47: التحقق
  • الأسطر 53-55: لم يعد المنتج موجودًا.

9.3.3. واجهات ADO.NET الأساسية للوضع المتصل

لنعد إلى مخطط تطبيق يستخدم قاعدة بيانات عبر موصل ADO.NET:

في الوضع المتصل، يقوم التطبيق بما يلي:

  1. يفتح اتصالاً بمصدر البيانات
  2. تعمل مع مصدر البيانات في وضع القراءة/الكتابة
  3. يغلق الاتصال

هناك ثلاث واجهات ADO.NET معنية بشكل أساسي بهذه العمليات:

  • IDbConnection التي تغلف خصائص وأساليب الاتصال.
  • IDbCommand التي تغلف خصائص وأساليب الأمر SQL الذي تم تنفيذه.
  • IDataReader التي تغلف خصائص وأساليب نتيجة أمر SQL Select.

الواجهة IDbConnection

تُستخدم لإدارة الاتصال بقاعدة البيانات. ستكون الطرق M والخصائص P لهذه الواجهة التي سنستخدمها هي التالية:

الاسم
النوع
الدور
ConnectionString
P
سلسلة الاتصال بالقاعدة. تحدد جميع المعلمات اللازمة لإقامة الاتصال بقاعدة معينة.
Open
M
يفتح الاتصال بالقاعدة المحددة بواسطة ConnectionString
Close
M
يغلق الاتصال
BeginTransaction
M
يبدأ معاملة.
State
P
حالة الاتصال: ConnectionState.Closed، ConnectionState.Open، ConnectionState.Connecting، ConnectionState.Executing، ConnectionState.Fetching، ConnectionState.Broken

إذا كانت Connection فئة تنفذ واجهة IDbConnection، فيمكن فتح الاتصال على النحو التالي:

1
2
3
IDbConnection connexion=new Connection();
connexion.ConnectionString=...;
connexion.Open();

الواجهة IDbCommand

تُستخدم لتنفيذ أمر SQL أو إجراء مخزن. ستكون الطرق M والخصائص P لهذه الواجهة التي سنستخدمها هي التالية:

الاسم
النوع
الدور
CommandType
P
يشير إلى ما يجب تنفيذه - يأخذ قيمه من قائمة:
- CommandType.Text: ينفذ الأمر SQL المحدد في الخاصية CommandText. هذه هي القيمة الافتراضية.
- CommandType.StoredProcedure: ينفذ إجراءً مخزّنًا في قاعدة البيانات
CommandText
P
- نص الأمر SQL المراد تنفيذه إذا كان CommandType= CommandType.Text
- اسم الإجراء المخزن المراد تنفيذه إذا كان CommandType= CommandType.StoredProcedure
Connection
P
اتصال IDbConnection المراد استخدامه لتنفيذ الأمر SQL
Transaction
P
المعاملة IDbTransaction التي سيتم فيها تنفيذ الأمر SQL
Parameters
P
قائمة معلمات أمر SQL الذي تم تعيين معلماته. الأمر update articles set price=price*1.1 where id=@id يحتوي على المعلمة @id.
ExecuteReader
M
لتنفيذ أمر SQL Select. نحصل على كائن IDataReader يمثل نتيجة Select.
ExecuteNonQuery
M
لتنفيذ الأمر SQL (تحديث، إدراج، حذف). يتم الحصول على عدد السطور التي تأثرت بالعملية (التي تم تحديثها، إدراجها، أو حذفها).
ExecuteScalar
M
لتنفيذ أمر SQL Select الذي يعطي نتيجة واحدة فقط كما في: select count(*) from articles.
CreateParameter
M
لإنشاء معلمات IDbParameter لأمر SQL الذي تم تعيين معلماته.
Prepare
M
يسمح بتحسين تنفيذ استعلام معلم عندما يتم تنفيذه عدة مرات بمعلمات مختلفة.

إذا كانت Command فئة تنفذ واجهة IDbCommand، فسيكون تنفيذ أمر SQL بدون معاملة على النحو التالي:

// فتح الاتصال 
IDbConnection connexion=...
connexion.Open();
// تحضير الأمر
IDbCommand commande=new Command();
commande.Connection=connexion;
// تنفيذ أمر select
commande.CommandText="select ...";
IDbDataReader reader=commande.ExecuteReader();
...
// تنفيذ أمر التحديث والإدراج والحذف
commande.CommandText="insert ...";
int nbLignesInsérées=commande.ExecuteNonQuery();
...
// إغلاق الاتصال
connexion.Close();

الواجهة IDataReader

تُستخدم لتغليف نتائج أمر SQL Select. يمثل الكائن IDataReader جدولًا يحتوي على صفوف وأعمدة، يتم استغلالها بالتسلسل: أولاً الصف الأول، ثم الثاني، ... ستكون الطرق M والخصائص P لهذه الواجهة التي سنستخدمها كما يلي:

الاسم
النوع
الدور
FieldCount
P
عدد أعمدة الجدول IDataReader
GetName
M
GetName(i) يعرض اسم العمود رقم i من الجدول IDataReader.
Item
P
Item[i] يمثل العمود رقم i من السطر الحالي في الجدول IDataReader.
Read
M
ينتقل إلى السطر التالي من الجدول IDataReader. يُرجع القيمة المنطقية True إذا تمت القراءة بنجاح، و False في حالة الفشل.
Close
M
يغلق الجدول IDataReader.
GetBoolean
M
GetBoolean(i): يعرض القيمة المنطقية للعمود رقم i في السطر الحالي من الجدول IDataReader. الطرق المماثلة الأخرى هي: GetDateTime، GetDecimal، GetDouble، GetFloat، GetInt16، GetInt32، GetInt64، GetString.
Getvalue
M
Getvalue(i): يعرض قيمة العمود رقم i من السطر الحالي في الجدول IDataReader كنوع object.
IsDBNull
M
IsDBNull(i) يُرجع True إذا كان العمود رقم i في السطر الحالي من الجدول IDataReader لا يحتوي على قيمة، وهو ما يُرمز إليه بالقيمة SQL NULL.

غالبًا ما يبدو استخدام كائن IDataReader كما يلي:

// فتح الاتصال 
IDbConnection connexion=...
connexion.Open();
// تحضير الأمر
IDbCommand commande=new Command();
commande.Connection=connexion;
// تنفيذ أمر select
commande.CommandText="select ...";
IDataReader reader=commande.ExecuteReader();
// تحليل النتائج
while(reader.Read()){
     // تحليل السطر الحالي
        ...
}
// إغلاق القارئ
reader.Close();
// إغلاق الاتصال
connexion.Close();

9.3.4. إدارة الأخطاء

لنعد إلى بنية تطبيق يعتمد على قاعدة بيانات:

قد تواجه الطبقة [dao] العديد من الأخطاء أثناء تشغيل قاعدة البيانات. سيتم رفع هذه الأخطاء كاستثناءات يتم إطلاقها بواسطة الموصل ADO.NET. يجب أن يتعامل كود الطبقة [dao] معها. يجب أن تتم أي عملية مع قاعدة البيانات في try / catch / finally لاعتراض وإدارة أي استثناء محتمل وتحرير الموارد التي يجب تحريرها. وبالتالي، يصبح الكود المذكور أعلاه لاستغلال نتيجة أمر Select كما يلي:

// تهيئة الاتصال
IDbConnection connexion=...
// تشغيل الاتصال
try{
     // فتح
    connexion.Open();
     // تحضير الأمر
    IDbCommand commande=new Command();
    commande.Connection=connexion;
     // تنفيذ أمر الاختيار
    commande.CommandText="select ...";
    IDbDataReader reader=commande.ExecuteReader();
     // معالجة النتائج
    try{
        while(reader.Read()){
         // استغلال السطر الحالي
        ...
    }finally{
         // إغلاق القارئ
        reader.Close();
    }
}catch(Exception ex){
     // إدارة الاستثناء
    ...
}finally{
     // إغلاق الاتصال
    connexion.Close();
}
...

مهما حدث، يجب إغلاق الكائنين IDataReader و IDbConnection. ولهذا السبب يتم هذا الإغلاق في الجمل finally.

يمكن أتمتة إغلاق الاتصال وإغلاق الكائن IDataReader باستخدام جملة using:

// استغلال الاتصال
try{
    using(IDbConnection connexion=...){
         // فتح
        connexion.Open();
         // تحضير الطلب
        IDbCommand commande=new Command();
        commande.Connection=connexion;
         // تنفيذ أمر التحديد
        commande.CommandText="select ...";
        using(IDbDataReader reader=commande.ExecuteReader()){
             // معالجة النتائج
            while(reader.Read()){
             // استغلال السطر الحالي
            ...
        }// باستخدام IData
    }//باستخدام IDbConnection
}catch(Exception ex){
     // إدارة الاستثناءات
    ...
}
..
  • السطر 3، تضمن جملة using أن الاتصال المفتوح في الكتلة using(...){...} سيتم إغلاقه خارجها، بغض النظر عن طريقة الخروج من الكتلة: بشكل طبيعي أو بحدوث استثناء. نوفر finally، لكن الفائدة لا تكمن في هذا التوفير الطفيف. إن استخدام using يغني المطور عن إغلاق الاتصال بنفسه. ومع ذلك، فإن نسيان إغلاق اتصال ما قد يمر دون أن يلاحظه أحد ويؤدي إلى "تعطل" التطبيق بطريقة تبدو عشوائية، في كل مرة يصل فيها SGBD إلى الحد الأقصى لعدد الاتصالات المفتوحة التي يمكنه تحملها.
  • السطر 11: يتم اتباع نفس الإجراء لإغلاق الكائن IDataReader.

9.3.5. تكوين المشروع النموذجي

سيكون المشروع النهائي كما يلي:

  • [1]: سيحتوي المشروع على ملف تكوين [App.config]
  • [2]: يستخدم فئتين من DLL غير مرجعيتين افتراضيًا، لذا يجب إضافتهما إلى مراجع المشروع:
    • [System.Configuration] لاستخدام ملف التكوين [App.config]
    • [System.Data.SqlServerCe] لاستخدام قاعدة بيانات Sql Server Compact
  • [3, 4]: تذكير بكيفية إضافة مراجع إلى مشروع.
  • [5, 6]: تذكير بكيفية إضافة الملف [App.config] إلى مشروع.

سيكون ملف التكوين [App.config] كما يلي:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <connectionStrings>
  <add name="dbSqlServerCe" connectionString="Data Source=|DataDirectory|\dbarticles.sdf;Password=dbarticles;" />
 </connectionStrings>
</configuration>
  • الأسطر 3-5: العلامة <connectionStrings> بصيغة الجمع تحدد سلاسل الاتصال بقواعد البيانات. سلسلة اتصال على شكل "المعلمة1=القيمة1؛المعلمة2=القيمة2؛...". وهي تحدد جميع المعلمات اللازمة لإنشاء اتصال بقاعدة بيانات معينة. تتغير سلاسل الاتصال هذه مع كل SGBD. يقدم الموقع [http://www.connectionstrings.com/] شكل هذه السلاسل لأهم SGBD.
  • السطر 4: يحدد سلسلة اتصال معينة، وهي هنا سلسلة قاعدة بيانات SQL Server Compact dbarticles.sdf التي أنشأناها سابقًا:
    • name = اسم سلسلة الاتصال. يتم استرداد سلسلة الاتصال بواسطة برنامج C# عبر هذا الاسم
    • connectionString: سلسلة الاتصال لقاعدة بيانات SQL Server Compact
    • DataSource: يشير إلى مسار قاعدة البيانات. تشير صيغة |DataDirectory| إلى مجلد تشغيل المشروع.
    • Password: كلمة مرور قاعدة البيانات. يتم تجاهل هذا المعامل في حالة عدم وجود كلمة مرور.

فيما يلي كود C# لاسترداد سلسلة الاتصال السابقة:


string connectionString = ConfigurationManager.ConnectionStrings["dbSqlServerCe"].ConnectionString;
  • ConfigurationManager هي فئة DLL [System.Configuration] التي تسمح باستغلال الملف [App.config].
  • ConnectionsStrings["nom"].ConnectionString: يشير إلى السمة connectionString لعلامة < add name="name" connectionString="..."> في القسم <connectionStrings> من [App.config]

تم الآن تكوين المشروع. ندرس الآن الفئة [Program.cs] التي رأينا سابقًا مثالًا على تنفيذها.

9.3.6. برنامج المثال

البرنامج [program.cs] هو التالي:


using System;
using System.Collections.Generic;
using System.Data.SqlServerCe;
using System.Text;
using System.Text.RegularExpressions;
using System.Configuration;

namespace Chap7 {
    class SqlCommands {
        static void Main(string[] args) {

            // وحدة التحكم في التطبيق - تنفيذ طلبات SQL المكتوبة على لوحة المفاتيح
            // على قاعدة بيانات يتم الحصول على سلسلة الاتصال الخاصة بها من ملف التكوين

            // استخدام ملف التكوين [App.config]
            string connectionString = null;
            try {
                connectionString = ConfigurationManager.ConnectionStrings["dbSqlServerCe"].ConnectionString;
            } catch (Exception e) {
                Console.WriteLine("Erreur de configuration : {0}", e.Message);
                return;
            }

            // عرض سلسلة الاتصال
            Console.WriteLine("Chaîne de connexion à la base : [{0}]\n", connectionString);

            // يتم إنشاء قاموس للأوامر sql المقبولة
            string[] commandesSQL = new string[] { "select", "insert", "update", "delete" };
            Dictionary<string, bool> dicoCommandes = new Dictionary<string, bool>();
            for (int i = 0; i < commandesSQL.Length; i++) {
                dicoCommandes.Add(commandesSQL[i], true);
            }

            // قراءة وتنفيذ الأوامر SQL المكتوبة على لوحة المفاتيح
            string requête = null;    // نص الاستعلام SQL
            string[] champs;            // حقول الاستعلام    
            Regex modèle = new Regex(@"\s+");    // سلسلة من المسافات

            // حلقة إدخال-تنفيذ الأوامر SQL المكتوبة على لوحة المفاتيح
            while (true) {
                // طلب الاستعلام
                Console.Write("\nRequête SQL (rien pour arrêter) : ");
                requête = Console.ReadLine().Trim().ToLower();
                // انتهى؟
                if (requête == "")
                    break;
                // تقسيم الاستعلام إلى حقول
                champs = modèle.Split(requête);
                // الاستعلام صالح؟
                if (champs.Length == 0 || ! dicoCommandes.ContainsKey(champs[0])) {
                    // رسالة خطأ
                    Console.WriteLine("Requête invalide. Utilisez select, insert, update, delete ou rien pour arrêter");
                    // الطلب التالي
                    continue;
                }
                // تنفيذ الطلب
                if (champs[0] == "select") {
                    ExecuteSelect(connectionString, requête);
                } else
                    ExecuteUpdate(connectionString, requête);
            }
        }

        // تنفيذ طلب تحديث
        static void ExecuteUpdate(string connectionString, string requête) {
    ...
        }

        // تنفيذ طلب Select
        static void ExecuteSelect(string connectionString, string requête) {
....
        }
    }
}
  • الأسطر 1-6: مساحات الأسماء المستخدمة في التطبيق. تتطلب إدارة قاعدة بيانات SQL Server Compact مساحة الأسماء [System.Data.SqlServerCe] الموجودة في السطر 3. هناك هنا تبعية لمساحة اسم مملوكة لـ SGBD. يمكننا أن نستنتج من ذلك أنه سيتعين تعديل البرنامج إذا تم تغيير SGBD.
  • السطر 18: يتم قراءة سلسلة الاتصال بالقاعدة في الملف [App.config] وعرضها في السطر 25. وستُستخدم لإنشاء اتصال بقاعدة البيانات.
  • الأسطر 28-32: قاموس يحفظ أسماء الأوامر الأربعة المسموح بها: select، insert، update، delete.
  • الأسطر 40-62: حلقة إدخال الأوامر SQL المكتوبة على لوحة المفاتيح وتنفيذها على قاعدة البيانات
  • السطر 48: يتم تقسيم السطر الذي تم إدخاله عبر لوحة المفاتيح إلى حقول لتحديد العنصر الأول الذي يجب أن يكون: select، insert، update، delete
  • الأسطر 50-55: إذا كان الاستعلام غير صالح، يتم عرض رسالة خطأ وننتقل إلى الاستعلام التالي.
  • الأسطر 57-61: يتم تنفيذ الأمر SQL الذي تم إدخاله. يختلف شكل هذا التنفيذ باختلاف الأمر، سواء كان الأمر select أو insert أو update أو delete. في الحالة الأولى، تسترد الأوامر البيانات من قاعدة البيانات دون تغييرها، وفي الحالة الثانية تقوم بتحديثها دون استرداد البيانات. في كلتا الحالتين، يتم تفويض التنفيذ إلى طريقة تحتاج إلى معلمتين:
    • سلسلة الاتصال التي ستسمح لها بالاتصال بقاعدة البيانات
    • الأمر SQL المراد تنفيذه على هذا الاتصال

9.3.7. تنفيذ استعلام SELECT

يتطلب تنفيذ الأوامر SQL الخطوات التالية:

  1. الاتصال بقاعدة البيانات
  2. إرسال الأوامر SQL إلى قاعدة البيانات
  3. معالجة نتائج الأمر SQL
  4. إغلاق الاتصال

يتم تنفيذ الخطوتين 2 و 3 بشكل متكرر، ولا يتم إغلاق الاتصال إلا في نهاية تشغيل قاعدة البيانات. الاتصالات المفتوحة هي موارد محدودة لـ SGBD. يجب توفيرها. لذلك، سنسعى دائمًا إلى الحد من مدة بقاء الاتصال المفتوح. في المثال المدروس، يتم إغلاق الاتصال بعد كل أمر SQL. يتم فتح اتصال جديد للأمر SQL التالي. فتح/إغلاق الاتصال مكلف. لتقليل هذه التكلفة، توفر بعض أنظمة SGBD مفهوم مجموعات الاتصالات المفتوحة: عند تهيئة التطبيق، يتم فتح N اتصال وتخصيصها للمجموعة. وستظل مفتوحة حتى نهاية التطبيق. عندما يفتح التطبيق اتصالاً، فإنه يتلقى أحد الاتصالات N المفتوحة بالفعل من المجموعة. وعندما يغلق الاتصال، يتم إعادته ببساطة إلى المجموعة. وتكمن فائدة هذا النظام في أنه شفاف بالنسبة للمطور: لا يتعين تعديل البرنامج لاستخدام مجموعة الاتصالات. يعتمد تكوين مجموعة الاتصالات على SGBD.

نحن مهتمون أولاً بتنفيذ الأوامر SQL Select. الطريقة ExecuteSelect في برنامجنا النموذجي هي كما يلي:


// تنفيذ طلب Select
        static void ExecuteSelect(string connectionString, string requête) {
            // معالجة الاستثناءات المحتملة
            try {
                using (SqlCeConnection connexion = new SqlCeConnection(connectionString)) {
                    // فتح اتصال
                    connexion.Open();
                    // تنفيذ sqlCommand باستخدام استعلام select
                    SqlCeCommand sqlCommand = new SqlCeCommand(requête, connexion);
                    SqlCeDataReader reader= sqlCommand.ExecuteReader();
                    // عرض النتائج
                    AfficheReader(reader);
                }
            } catch (Exception ex) {
                // رسالة خطأ
                Console.WriteLine("Erreur d'accès à la base de données (" + ex.Message + ")");
            }
        }

        // عرض القارئ
        static void AfficheReader(IDataReader reader) {
...
        }
  • السطر 2: تتلقى الطريقة معلمتين:
    • سلسلة الاتصال [connectionString] التي ستسمح لها بالاتصال بقاعدة البيانات
    • الأمر SQL Select [requête] المراد تنفيذه على هذا الاتصال
  • السطر 4: أي عملية مع قاعدة بيانات يمكن أن تولد استثناءً قد نرغب في معالجته. وهذا مهم بشكل خاص هنا لأن الأوامر SQL التي يعطيها المستخدم قد تكون خاطئة من الناحية النحوية. يجب أن نتمكن من إخباره بذلك. لذا فإن كل الكود موجود داخل try / catch.
  • السطر 5: هناك عدة أمور هنا:
    • يتم تهيئة الاتصال بقاعدة البيانات باستخدام سلسلة الاتصال [connectionString]. لم يتم فتحه بعد. سيتم فتحه في السطر 7.
    • جملة using (Ressource) {...} هي تسهيل نحوي يضمن تحرير المورد Ressource، وهو هنا اتصال، عند الخروج من الكتلة التي يتحكم فيها using.
    • الاتصال من نوع خاص: SqlCeConnection، خاص بـ SGBD SQL Server Compact.
  • السطر 7: تم فتح الاتصال. في هذه اللحظة يتم استخدام معلمات سلسلة الاتصال.
  • السطر 9: يتم إصدار أمر SQL عبر كائن خاص SqlCeCommand. يقوم السطر 9 بتهيئة هذا الكائن بمعلومتين: الاتصال المطلوب استخدامه والأمر SQL المطلوب إصداره عليه. يُستخدم الكائن SqlCeCommand لتنفيذ أمر Select وكذلك أمر Update و Insert و Delete. تم عرض خصائصه وأساليبه في الفقرة 9.3.3.
  • السطر 10: يتم تنفيذ الأمر SQL Select عبر طريقة ExecuteReader الخاصةالكائن SqlCeCommand الذي يُرجع كائن IDataReader الذي تم عرض أساليبه وخصائصه في الفقرة 9.3.3.
  • السطر 12: يتم عرض النتائج من خلال الطريقة التالية AfficheReader:

        // عرض القارئ
        static void AfficheReader(IDataReader reader) {
            using (reader) {
                // استغلال النتائج
                // -- الأعمدة
                StringBuilder ligne = new StringBuilder();
                int i;
                for (i = 0; i < reader.FieldCount - 1; i++) {
                    ligne.Append(reader.GetName(i)).Append(",");
                }
                ligne.Append(reader.GetName(i));
                Console.WriteLine("\n{0}\n{1}\n{2}\n", "".PadLeft(ligne.Length, '-'), ligne, "".PadLeft(ligne.Length, '-'));
                // -- البيانات
                while (reader.Read()) {
                    // تحليل السطر الحالي
                    ligne = new StringBuilder();
                    for (i = 0; i < reader.FieldCount; i++) {
                        ligne.Append(reader[i].ToString()).Append(" ");
                    }
                    Console.WriteLine(ligne);
                }
            }
}
  • السطر 2: تتلقى الطريقة كائن IDataReader. تجدر الإشارة إلى أننا استخدمنا هنا واجهة وليس فئة محددة.
  • السطر 3: تُستخدم الجملة using لإدارة إغلاق الكائن IDataReader تلقائيًا.
  • الأسطر 8-10: يتم عرض أسماء أعمدة جدول النتائج الخاص بـ Select. وهي أعمدة coli الخاصة بالاستعلام select col1, col2, ... from table ...
  • الأسطر 14-21: يتم تصفح جدول النتائج وعرض قيم كل سطر في الجدول.
  • السطر 18: لا نعرف نوع العمود رقم i في النتيجة لأننا لا نعرف الجدول الذي تم الاستعلام عنه. لذلك لا يمكننا استخدام صيغة reader.GetXXX(i) حيث XXX هو نوع العمود رقم i، لأننا لا نعرف هذا النوع. لذلك نستخدم صيغة reader.Item[i].ToString() للحصول على تمثيل العمود رقم i في شكل سلسلة أحرف. يمكن اختصار صيغة reader.Item[i].ToString() إلى reader[i].ToString().

9.3.8. تنفيذ أمر التحديث: INSERT، UPDATE، DELETE

رمز طريقة ExecuteUpdate هو كما يلي:


        // تنفيذ طلب تحديث
        static void ExecuteUpdate(string connectionString, string requête) {
            // إدارة الاستثناءات المحتملة
            try {
                using (SqlCeConnection connexion = new SqlCeConnection(connectionString)) {
                    // فتح اتصال
                    connexion.Open();
                    // تنفيذ sqlCommand مع طلب التحديث
                    SqlCeCommand sqlCommand = new SqlCeCommand(requête, connexion);
                    int nbLignes = sqlCommand.ExecuteNonQuery();
                    // عرض النتيجة
                    Console.WriteLine("Il y a eu {0} ligne(s) modifiée(s)", nbLignes);
                }
            } catch (Exception ex) {
                // رسالة خطأ
                Console.WriteLine("Erreur d'accès à la base de données (" + ex.Message + ")");
            }
}

لقد ذكرنا أن تنفيذ أمر الاستعلام Select لا يختلف عن تنفيذ أمر التحديث Update، Insert، Delete إلا في طريقة الكائن SqlCeCommand المستخدمة: ExecuteReader لـ Select، ExecuteNonQuery لـ Update، Insert، Delete. لن نعلق إلا على هذه الطريقة الأخيرة في الكود أعلاه:

  • السطر 10: يتم تنفيذ الأمر Update، Insert، Delete بواسطة الطريقة ExecuteNonQuery للكائن SqlCeCommand. إذا نجحت هذه الطريقة، فإنها تعرض عدد الأسطر التي تم تحديثها (update) أو إدراجها (insert) أو حذفها (delete).
  • السطر 12: يتم عرض عدد الأسطر هذا على الشاشة

يُطلب من القارئ مراجعة مثال لتنفيذ هذا الرمز، الفقرة 9.3.2.

9.4. موصلات أخرى ADO.NET

الرمز الذي درسناه هو رمز خاص: فهو يعتمد على مساحة الأسماء [System.Data.SqlServerCe] المخصصة لـ SGBD SQL Server Compact. سنقوم الآن بإنشاء نفس البرنامج باستخدام موصلات .NET مختلفة ونرى ما الذي يتغير.

9.4.1. موصل SQL Server 2005

ستكون البنية المستخدمة كما يلي:

يتم وصف تثبيت SQL Server 2005 في الملاحق في الفقرة 1.1.

نقوم بإنشاء مشروع ثانٍ في نفس الحل السابق، ثم نقوم بإنشاء قاعدة البيانات SQL Server 2005. يجب تشغيل SGBD SQL Server 2005 قبل العمليات التالية:

  • [1]: إنشاء مشروع جديد في الحل الحالي وجعله المشروع الحالي.
  • [2]: إنشاء اتصال جديد
  • [3]: اختيار نوع الاتصال
  • [4]: اختيار SGBD SQL Server
  • [5]: نتيجة الاختيار السابق
  • [6]: استخدم الزر [Browse] لتحديد مكان إنشاء قاعدة البيانات SQL Server 2005. يتم تغليف قاعدة البيانات في ملف .mdf.
  • [7]: اختر جذر المشروع الجديد واسمي قاعدة البيانات [dbarticles.mdf].
  • [8]: استخدم مصادقة Windows.
  • [9]: التحقق من صحة صفحة المعلومات
  • [11]: قاعدة بيانات SQL Server
  • [12]: إنشاء جدول. سيكون هذا الجدول مطابقًا لقاعدة بيانات Server Compact التي تم إنشاؤها مسبقًا.
  • [13]: الحقل [id]
  • [14]: الحقل [id] هو من النوع Identity.
  • [15,16]: الحقل [id] هو مفتاح أساسي
  • [17]: الحقول الأخرى في الجدول
  • [18]: قم بتسمية الجدول باسم [articles] عند حفظه (Ctrl+S).

يبقى لنا إدخال البيانات في الجدول:

نقوم بإدراج قاعدة البيانات في المشروع:

مراجع المشروع هي كما يلي:

ملف التكوين [App.config] هو التالي:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <connectionStrings>
  <add name="connectString1" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\dbarticles.mdf;Integrated Security=True;Connect Timeout=30;User Instance=True;" />
     <add name="connectString2" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\dbarticles.mdf;Uid=sa;Pwd=msde;Connect Timeout=30;" />
 </connectionStrings>
</configuration>
  • السطر 4: سلسلة الاتصال بقاعدة البيانات [dbarticles.mdf] مع مصادقة Windows
  • السطر 5: سلسلة الاتصال بقاعدة البيانات [dbarticles.mdf] مع مصادقة خادم SQL. [sa,msde] هو زوج (اسم المستخدم، كلمة المرور) لمسؤول خادم SQL Server كما هو محدد في الفقرة 1.1.

يتطور البرنامج [Program.cs] على النحو التالي:


using System.Data.SqlClient;
...

namespace Chap7 {
    class SqlCommands {
        static void Main(string[] args) {
...
            // استخدام ملف التكوين [App.config]
            string connectionString = null;
            try {
                connectionString = ConfigurationManager.ConnectionStrings["connectString2"].ConnectionString;
            } catch (Exception e) {
...
            }
...
            // قراءة وتنفيذ الأوامر SQL المكتوبة على لوحة المفاتيح
...
        }

        // تنفيذ طلب تحديث
        static void ExecuteUpdate(string connectionString, string requête) {
            // إدارة الاستثناءات المحتملة
            try {
                using (SqlConnection connexion = new SqlConnection(connectionString)) {
                    // فتح الاتصال
                    connexion.Open();
                    // تنفيذ sqlCommand مع طلب التحديث
                    SqlCommand sqlCommand = new SqlCommand(requête, connexion);
                    int nbLignes = sqlCommand.ExecuteNonQuery();
                    // عرض النتيجة
                    Console.WriteLine("Il y a eu {0} ligne(s) modifiée(s)", nbLignes);
                }
            } catch (Exception ex) {
....
            }
        }

        // تنفيذ طلب Select
        static void ExecuteSelect(string connectionString, string requête) {
            // معالجة الاستثناءات المحتملة
            try {
                using (SqlConnection connexion = new SqlConnection(connectionString)) {
                    // فتح اتصال
                    connexion.Open();
                    // تنفيذ sqlCommand مع استعلام select
                    SqlCommand sqlCommand = new SqlCommand(requête, connexion);
                    SqlDataReader reader = sqlCommand.ExecuteReader();
                    // استغلال النتائج
...
                }
            } catch (Exception ex) {
...
            }
        }
    }
}
  • السطر 1: يحتوي مساحة الأسماء [System.Data.SqlClient] على الفئات التي تسمح بإدارة قاعدة بيانات SQL Server 2005
  • السطر 24: الاتصال من النوع SQLConnection
  • السطر 28: الكائن الذي يغلف الأوامر SQL هو من النوع SQLCommand
  • السطر 47: الكائن الذي يغلف نتيجة أمر SQL Select هو من النوع SQLDataReader

الرمز مطابق للرمز المستخدم مع SGBD SQL Server Compact باستثناء أسماء الفئات. لتنفيذه، يمكن استخدام (السطر 11) أي من سلسلتي الاتصال المحددتين في [App.config].

9.4.2. الموصل MySQL5

ستكون البنية المستخدمة كما يلي:

يتم وصف تثبيت MySQL5 في الملاحق في الفقرة 1.2 وتثبيت الموصل Ado.Net في الفقرة 1.2.5.

نقوم بإنشاء مشروع ثالث في نفس الحل السابق ونضيف إليه المراجع التي يحتاجها:

  • [1]: المشروع الجديد
  • [2]: الذي نضيف إليه المراجع
  • [3]: DLL [MySQL.Data] من الموصل Ado.Net من MySql5 بالإضافة إلى تلك الخاصة بـ [System.Configuration] [4].

نقوم الآن بإنشاء قاعدة البيانات [dbarticles] وجدولها [articles]. يجب تشغيل SGBD و MySQL5. بالإضافة إلى ذلك، يتم تشغيل العميل [Query Browser] (انظر الفقرة 1.2.3).

  • [1]: في [Query Browser]، انقر بزر الماوس الأيمن في منطقة [Schemata] [2] لإنشاء مخطط جديد باسم [3]، وهو مصطلح يشير إلى قاعدة بيانات.
  • [4]: ستسمى قاعدة البيانات [dbarticles]. في [5]، يمكننا رؤيتها. وهي خالية من الجداول في الوقت الحالي. سنقوم بتنفيذ البرنامج النصي SQL التالي:
/* اختيار قاعدة البيانات الحالية */
USE dbarticles;
/* إنشاء جدول المواد */
CREATE TABLE ARTICLES (
    ID            INTEGER PRIMARY KEY AUTO_INCREMENT,
    NOM           VARCHAR(20) NOT NULL,
    PRIX          DOUBLE PRECISION NOT NULL,
    STOCKACTUEL   INTEGER NOT NULL,
    STOCKMINIMUM  INTEGER NOT NULL
);
/* إدراج البيانات في الجدول */
INSERT INTO ARTICLES (NOM, PRIX, STOCKACTUEL, STOCKMINIMUM) VALUES ('article1', 100, 10, 1);
INSERT INTO ARTICLES (NOM, PRIX, STOCKACTUEL, STOCKMINIMUM) VALUES ('article2', 200, 20, 2);
INSERT INTO ARTICLES (NOM, PRIX, STOCKACTUEL, STOCKMINIMUM) VALUES ('article3', 300, 30, 3);
/* إضافة قيود */
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_ID check (ID>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_PRIX check (PRIX>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKACTUEL check (STOCKACTUEL>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_STOCKMINIMUM check (STOCKMINIMUM>0);
ALTER TABLE ARTICLES ADD CONSTRAINT CHK_NOM check (NOM<>'');
ALTER TABLE ARTICLES ADD CONSTRAINT UNQ_NOM UNIQUE (NOM);
  • السطر 1: تصبح قاعدة البيانات [dbarticles] هي القاعدة الحالية. سيتم تنفيذ الأوامر SQL التالية عليها.
  • الأسطر 4-10: تعريف الجدول [ARTICLES]. تجدر الإشارة إلى أن SQL هو المالك لـ MySQL. تختلف أنواع الأعمدة، والتوليد التلقائي للمفتاح الأساسي (السمة AUTO_INCREMENT) عما تمت ملاحظته مع SGBD و SQL في Server Compact و Express.
  • الأسطر 12-14: إدراج ثلاثة أسطر
  • الأسطر 16-21: إضافة قيود التكامل على الأعمدة.

يتم تنفيذ هذا البرنامج النصي في [MySQL Query Browser]:

  • في [MySQL Query Browser] [6]، يتم تحميل البرنامج النصي [7]. يمكن رؤيته في [8]. في [9]، يتم تنفيذه.
  • في [10]، تم إنشاء الجدول [articles]. نضغط عليها مرتين. يظهر نافذة [11] مع الاستعلام [12] بداخلها جاهز للتنفيذ بواسطة [13]. في [14]، نرى نتيجة التنفيذ. لدينا بالفعل الأسطر الثلاثة المتوقعة. تجدر الإشارة إلى أن قيم الحقل [ID] تم إنشاؤها تلقائيًا (السمة AUTO_INCREMENT للحقل).

الآن بعد أن أصبحت قاعدة البيانات جاهزة، يمكننا العودة إلى تطوير التطبيق في Visual Studio.

في [1]، برنامج [Program.cs] وملف التكوين [App.config]. وهذا هو كما يلي:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <connectionStrings>
     <add name="dbArticlesMySql5" connectionString="Server=localhost;Database=dbarticles;Uid=root;Pwd=root;" />
 </connectionStrings>
</configuration>

في السطر 4، عناصر سلسلة الاتصال هي كما يلي:

  • Server: اسم الجهاز الذي يوجد عليه SGBD MySQL، هنا localhost، c.a.d. الجهاز الذي سيتم تشغيل البرنامج عليه.
  • Database: اسم قاعدة البيانات المدارة، هنا dbarticles
  • Uid: اسم المستخدم، هنا root
  • Pwd: كلمة المرور الخاصة به، وهنا هي root. تشير هاتان المعلومتان إلى المسؤول الذي تم إنشاؤه في الفقرة 1.2.

البرنامج [Program.cs] مطابق للبرنامج في الإصدارات السابقة باستثناء التفاصيل التالية:

espace de noms
MySql.Data.MySqlClient
classe Connection
MySqlConnection
classe Command
MySqlCommand
classe DataReader
MySqlDataReader

يستخدم البرنامج سلسلة الاتصال المسماة dbArticlesMySql5 في الملف [App.config]. يعطي التنفيذ النتائج التالية:

Chaîne de connexion à la base : [Server=localhost;Database=dbarticles;Uid=root;Pwd=root;]

Requête SQL (rien pour arrêter) : select * from articles

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

1 article1 100 10 1
2 article2 200 20 2
3 article3 300 30 3

9.4.3. الموصل ODBC

ستكون البنية المستخدمة كما يلي:

تكمن أهمية الموصلات ODBC في أنها توفر واجهة قياسية للتطبيقات التي تستخدمها. وبالتالي، سيتمكن التطبيق الجديد، باستخدام رمز واحد، من التواصل مع أي SGBD مزود بموصل ODBC أو c.a.d. ومعظم SGBD. أداء موصلات ODBC أقل جودة من أداء الموصلات "المملوكة" التي تستطيع استغلال جميع خصائص جهاز SGBD معين. في المقابل، نحصل على مرونة كبيرة في التطبيق: يمكننا تغيير SGBD دون تغيير الكود.

نحن ندرس مثالاً حيث يستخدم التطبيق قاعدة بيانات MySQL5 أو قاعدة بيانات SQL Server Express وفقاً لسلسلة الاتصال التي يتم تزويده بها. فيما يلي، نفترض أن:

  • تم تشغيل قواعد البيانات SGBD و SQL و Server Express و MySQL5
  • أن برنامج التشغيل ODBC لـ MySQL5 موجود على الجهاز (انظر الفقرة 1.2.6). برنامج التشغيل الخاص بـ SQL Server 2005 موجود بشكل افتراضي.
  • قواعد البيانات المستخدمة هي تلك المذكورة في الفقرة 9.4.2 لقاعدة البيانات MySQL5، وتلك المذكورة في الفقرة 9.4.1 لقاعدة البيانات SQL Server Express.

المشروع الجديد في Visual Studio هو التالي:

أعلاه، تم نسخ قاعدة البيانات SQL Server [dbarticles.mdf] التي تم إنشاؤها في الفقرة 9.4.1 إلى مجلد المشروع.

ملف التكوين [App.config] هو التالي:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <connectionStrings>
     <add name="dbArticlesOdbcMySql5" connectionString="Driver={MySQL ODBC 3.51 Driver};Server=localhost;Database=dbarticles; User=root;Password=root;" />
     <add name="dbArticlesOdbcSqlServer2005" connectionString="Driver={SQL Native Client};Server=.\SQLExpress;AttachDbFilename=|DataDirectory|\dbarticles.mdf;Uid=sa;Pwd=msde;" />
 </connectionStrings>
</configuration>
  • السطر 4: سلسلة اتصال المصدر ODBC MySQL5. هذه سلسلة تمت دراستها سابقًا وتحتوي على معلمة جديدة هي Driver تحدد برنامج التشغيل ODBC المطلوب استخدامه.
  • السطر 5: سلسلة اتصال المصدر ODBC SQL Server Express. هذه هي السلسلة التي تم استخدامها بالفعل في مثال سابق والتي تمت إضافة المعلمة Driver إليها.

برنامج [Program.cs] مطابق لبرنامج الإصدارات السابقة باستثناء التفاصيل التالية:

espace de noms
System.Data.Odbc
classe Connection
OdbcConnection
classe Command
OdbcCommand
classe DataReader
OdbcDataReader

يستخدم البرنامج إحدى سلسلتي الاتصال المحددتين في الملف [App.config]. يعطي التنفيذ النتائج التالية:

مع سلسلة الاتصال [dbArticlesOdbcSqlServer2005]:

Chaîne de connexion à la base : [Driver={SQL Native Client};Server=.\SQLExpress;AttachDbFilename=|DataDirectory|\dbarticles.mdf;Uid=sa;Pwd=msde;]

Requête SQL (rien pour arrêter) : select * from articles

------------------------------------
id,nom,prix,stockactuel,stockminimum
------------------------------------

1 vélo 500,0000 10 5
2 pompe 10,0000 10 2
3 arc 610,0000 4 1
4 flèches - lot de 6 100,0000 12 20
5 combinaison de plongée 300,0000 8 2
6 Bouteilles d'oxygène 120,0000 10 5

مع سلسلة الاتصال [dbArticlesOdbcMySql5]:

Chaîne de connexion à la base : [Driver={MySQL ODBC 3.51 Driver};Server=localhost;Database=dbarticles; User=root;Password=root;]

Requête SQL (rien pour arrêter) : select * from articles

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

1 article1 100 10 1
2 article2 200 20 2
3 article3 300 30 3

9.4.4. الموصل OLE DB

ستكون البنية المستخدمة كما يلي:

مثل الموصلات ODBC، فإن الموصلات OLE DB (ربط الكائنات وتضمينها DataBase) تقدم واجهة قياسية للتطبيقات التي تستخدمها. تسمح برامج التشغيل ODBC بالوصول إلى قواعد البيانات. أما مصادر البيانات لبرامج التشغيل OLE و DB فهي أكثر تنوعًا: قواعد البيانات، والبريد الإلكتروني، والدلائل، ... يمكن أن تكون أي مصدر بيانات موضوعًا لبرامج تشغيل Ole DB إذا قرر الناشر ذلك. وبذلك يكون لدينا وصول قياسي إلى مجموعة كبيرة ومتنوعة من البيانات.

ندرس مثالاً حيث يستخدم التطبيق قاعدة بيانات ACCESS أو قاعدة بيانات SQL Server Express وفقاً لسلسلة الاتصال التي يتم تزويده بها. فيما يلي، نفترض أن خادم Express SGBD SQL قد تم تشغيله وأن قاعدة البيانات المستخدمة هي تلك الموجودة في المثال السابق.

المشروع الجديد في Visual Studio هو التالي:

  • في [1]: مساحة الأسماء اللازمة للموصلات OLE DB هي [System.Data.OleDb] الموجودة في المرجع [System.Data] أعلاه. تم نسخ قاعدة بيانات SQL Server [dbarticles.mdf] من المشروع السابق. تم إنشاء قاعدة البيانات [dbarticles.mdb] باستخدام Access.
  • في [2]: مثل قاعدة البيانات SQL Server، تمتلك قاعدة البيانات ACCESS الخاصية [Copy to Output Directory=Copy Always] بحيث يتم نسخها تلقائيًا إلى مجلد تشغيل المشروع.

قاعدة البيانات ACCESS [dbarticles.mdb] هي كما يلي:

في [1]، توجد بنية الجدول [articles] وفي [2] محتواه.

ملف التكوين [App.config] هو التالي:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <connectionStrings>
     <add name="dbArticlesOleDbAccess"  connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\dbarticles.mdb;"/>
     <add name="dbArticlesOleDbSqlServer2005" connectionString="Provider=SQLNCLI;Server=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\dbarticles.mdf;Uid=sa;Pwd=msde;" />
 </connectionStrings>
</configuration>
  • السطر 4: سلسلة اتصال المصدر OLE DB ACCESS. يوجد فيه المعلمة Provider التي تحدد برنامج التشغيل OLE DB المطلوب استخدامه بالإضافة إلى مسار قاعدة البيانات
  • السطر 5: سلسلة اتصال مصدر OLE DB Server Express.

البرنامج [Program.cs] مطابق للإصدارات السابقة باستثناء التفاصيل التالية:

espace de noms
System.Data.OleDb
classe Connection
OleDbConnection
classe Command
OleDbCommand
classe DataReader
OleDbDataReader

يستخدم البرنامج أحد سلسلتي الاتصال المحددتين في الملف [App.config]. يعطي التنفيذ النتائج التالية باستخدام سلسلة الاتصال [dbArticlesOleDbAccess]:

Chaîne de connexion à la base : [Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\dbarticles.mdb;]

Requête SQL (rien pour arrêter) : select * from articles

------------------------------------
id,nom,prix,stockactuel,stockminimum
------------------------------------

1 vélo 1202 5 2
2 arc 5000 10 2
3 canoé 1502 12 6
4 fusil 3000 10 2
5 skis nautiques 1800 5 2
6 essai3 3 3 3
7 cachalot 200000 1 0
8 léopard 500000 1 1
9 panthère 800000 1 1

9.4.5. موصل عام

ستكون البنية المستخدمة كما يلي:

مثل الموصلات ODBC و OLE و DB، يقدم الموصل العام واجهة قياسية للتطبيقات التي تستخدمه ولكنه يحسن الأداء دون التضحية بالمرونة. في الواقع، يعتمد الموصل العام على الموصلات الخاصة بـ SGBD. يستخدم التطبيق فئات من الموصل العام. تعمل هذه الفئات كوسطاء بين التطبيق والموصل الخاص.

فيما سبق، عندما يطلب التطبيق، على سبيل المثال، اتصالاً بالموصل العام، فإن هذا الأخير يرد عليه بمثيل IDbConnection، واجهة الاتصالات الموصوفة في الفقرة 9.3.3، والتي يتم تنفيذها بواسطة فئة MySQLConnection أو SQLConnection وفقًا لطبيعة الطلب الذي تم توجيهه إليها. يُقال إن الموصل العام يحتوي على فئات من نوع المصنع: نستخدم فئة المصنع لنطلب منه إنشاء كائنات وتقديم مراجع (مؤشرات) لها. ومن هنا جاء اسمه (factory = مصنع، مصنع لإنتاج الكائنات).

لا يوجد موصل عام لجميع SGBD (أبريل 2008). لمعرفة تلك المثبتة على جهاز ما، يمكن استخدام البرنامج التالي:


using System;
using System.Data;
using System.Data.Common;

namespace Chap7 {
    class Providers {
        public static void Main() {
            DataTable dt = DbProviderFactories.GetFactoryClasses();
            foreach (DataColumn col in dt.Columns) {
                Console.Write("{0}|", col.ColumnName);
            }
            Console.WriteLine("\n".PadRight(40, '-'));
            foreach (DataRow row in dt.Rows) {
                foreach (object item in row.ItemArray) {
                    Console.Write("{0}|", item);
                }
                Console.WriteLine("\n".PadRight(40, '-'));
            }
        }
    }
}
  • السطر 8: تعرض الطريقة الثابتة [DbProviderFactories.GetFactoryClasses()] قائمة بالموصلات العامة المثبتة، في شكل جدول قاعدة بيانات موجود في الذاكرة (DataTable).
  • الأسطر 9-11: تعرض أسماء أعمدة الجدول dt:
    • dt.Columns هي قائمة أعمدة الجدول. العمود C هو من النوع DataColumn
    • [DataColumn].ColumnName هو اسم العمود
  • السطور 13-18: تعرض سطور الجدول dt:
    • dt.Rows هي قائمة أسطر الجدول. السطر L هو من النوع DataRow
    • [DataRow].ItemArray هو مصفوفة من الكائنات حيث يمثل كل كائن عمودًا في السطر

نتيجة التنفيذ على جهازي هي كما يلي:

Name|Description|InvariantName|AssemblyQualifiedName|
---------------------------------------
Odbc Data Provider|.Net Framework Data Provider for Odbc|System.Data.Odbc|System.Data.Odbc.OdbcFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089|
---------------------------------------
OleDb Data Provider|.Net Framework Data Provider for OleDb|System.Data.OleDb|System.Data.OleDb.OleDbFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089|
---------------------------------------
OracleClient Data Provider|.Net Framework Data Provider for Oracle|System.Data.OracleClient|System.Data.OracleClient.OracleClientFactory, System.Data.OracleClient, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089|
---------------------------------------
SqlClient Data Provider|.Net Framework Data Provider for SqlServer|System.Data.SqlClient|System.Data.SqlClient.SqlClientFactory, System.Data, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089|
---------------------------------------
Microsoft SQL Server Compact Data Provider|.NET Framework Data Provider for Microsoft SQL Server Compact|System.Data.SqlServerCe.3.5|System.Data.SqlServerCe.SqlCeProviderFactory, System.Data.SqlServerCe, Version=3.5.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91|
---------------------------------------
MySQL Data Provider|.Net Framework Data Provider for MySQL|MySql.Data.MySqlClient|MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=5.2.1.0, Culture=neutral, PublicKeyToken=c5687fc88969c44d|
  • السطر 1: تحتوي الجدولة على أربعة أعمدة. الأعمدة الثلاثة الأولى هي الأكثر فائدة لنا هنا.

يُظهر العرض التالي أن لدينا الموصلات العامة التالية:

الاسم
المعرف
Odbc Data Provider
System.Data.Odbc
OleDb Data Provider
System.Data.OleDb
OracleClient Data Provider
System.Data.OracleClient
SqlClient Data Provider
System.Data.SqlClient
Microsoft SQL Server Compact Data Provider
System.Data.SqlServerCe.3.5
MySQL Data Provider
MySql.Data.MySqlClient

يمكن الوصول إلى موصل عام في برنامج C# عبر معرّفه.

نحن ندرس مثالاً حيث تستخدم التطبيق قواعد البيانات المختلفة التي أنشأناها حتى الآن. سيتلقى التطبيق معلمتين:

  • تحدد المعلمة الأولى نوع SGBD المستخدم حتى يتم استخدام مكتبة الفئات الصحيحة
  • تحدد المعلمة الثانية قاعدة البيانات التي يتم إدارتها، عبر سلسلة اتصال.

المشروع الجديد في Visual Studio هو التالي:

  • في [1]: مساحة الأسماء اللازمة للموصلات العامة هي [System.Data.common] الموجودة في المرجع [System.Data].

ملف التكوين [App.config] هو التالي:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
        <add name="dbArticlesSqlServerCe" connectionString="Data Source=|DataDirectory|\dbarticles.sdf;Password=dbarticles;" />
        <add name="dbArticlesSqlServer" connectionString="Data Source=.\SQLEXPRESS;AttachDbFilename=|DataDirectory|\dbarticles.mdf;Uid=sa;Pwd=msde;" />
        <add name="dbArticlesMySql5" connectionString="Server=localhost;Database=dbarticles;Uid=root;Pwd=root;" />
        <add name="dbArticlesOdbcMySql5" connectionString="Driver={MySQL ODBC 3.51 Driver};Server=localhost;Database=dbarticles; User=root;Password=root;Option=3;" />
        <add name="dbArticlesOleDbSqlServer2005" connectionString="Provider=SQLNCLI;Server=.\SQLExpress;AttachDbFilename=|DataDirectory|\dbarticles.mdf;Uid=sa;Pwd=msde;" />
        <add name="dbArticlesOdbcSqlServer2005" connectionString="Driver={SQL Native Client};Server=.\SQLExpress;AttachDbFilename=|DataDirectory|\dbarticles.mdf;Uid=sa;Pwd=msde;" />
        <add name="dbArticlesOleDbAccess"  connectionString="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\dbarticles.mdb;Persist Security Info=True"/>
    </connectionStrings>
    <appSettings>
        <add key="factorySqlServerCe" value="System.Data.SqlServerCe.3.5"/>
        <add key="factoryMySql" value="MySql.Data.MySqlClient"/>
        <add key="factorySqlServer" value="System.Data.SqlClient"/>
        <add key="factoryOdbc" value="System.Data.Odbc"/>
        <add key="factoryOleDb" value="System.Data.OleDb"/>
    </appSettings>
</configuration>
  • الأسطر 3-11: سلاسل الاتصال لمختلف قواعد البيانات المستخدمة.
  • الأسطر 13-17: أسماء الموصلات العامة التي يجب استخدامها

البرنامج [Program.cs] هو التالي:


...
using System.Data.Common;

namespace Chap7 {
    class SqlCommands {
        static void Main(string[] args) {

            // تطبيق وحدة التحكم - ينفذ استعلامات SQL المكتوبة على لوحة المفاتيح
            // على قاعدة بيانات يتم الحصول على سلسلة الاتصال الخاصة بها من ملف التكوين بالإضافة إلى اسم موصل SGBD المرتبط

            // التحقق من المعلمات
            if (args.Length != 2) {
                Console.WriteLine("Syntaxe : pg factory connectionString");
                return;
            }

            // استخدام ملف التكوين
            string factory = null;
            string connectionString = null;
            DbProviderFactory connecteur = null;
            try {
                // المصنع
                factory = ConfigurationManager.AppSettings[args[0]];
                // سلسلة التوصيل
                connectionString = ConfigurationManager.ConnectionStrings[args[1]].ConnectionString;
                // يتم استرداد موصل عام لـ SGBD
                connecteur = DbProviderFactories.GetFactory(factory);
            } catch (Exception e) {
                Console.WriteLine("Erreur de configuration : {0}", e.Message);
                return;
            }

            // عمليات العرض
            Console.WriteLine("Provider factory : [{0}]\n", factory);
            Console.WriteLine("Chaîne de connexion à la base : [{0}]\n", connectionString);

...
                // تنفيذ الاستعلام
                if (champs[0] == "select") {
                    ExecuteSelect(connecteur,connectionString, requête);
                } else
                    ExecuteUpdate(connecteur, connectionString, requête);
            }
        }

        // تنفيذ طلب تحديث
        static void ExecuteUpdate(DbProviderFactory connecteur, string connectionString, string requête) {
            // إدارة الاستثناءات المحتملة
            try {
                using (DbConnection connexion = connecteur.CreateConnection()) {
                    // تكوين الاتصال
                    connexion.ConnectionString = connectionString;
                    // فتح الاتصال
                    connexion.Open();
                    // تكوين الأمر 
                    DbCommand sqlCommand = connecteur.CreateCommand();
                    sqlCommand.CommandText = requête;
                    sqlCommand.Connection = connexion;
                    // تنفيذ الطلب
                    int nbLignes = sqlCommand.ExecuteNonQuery();
                    // عرض النتيجة
                    Console.WriteLine("Il y a eu {0} ligne(s) modifiée(s)", nbLignes);
                }
            } catch (Exception ex) {
                // رسالة خطأ
                Console.WriteLine("Erreur d'accès à la base de données (" + ex.Message + ")");
            }
        }

        // تنفيذ استعلام Select
        static void ExecuteSelect(DbProviderFactory connecteur, string connectionString, string requête) {
            // معالجة الاستثناءات المحتملة
            try {
                using (DbConnection connexion = connecteur.CreateConnection()) {
                    // تكوين الاتصال
                    connexion.ConnectionString = connectionString;
                    // فتح الاتصال
                    connexion.Open();
                    // تكوين الأمر 
                    DbCommand sqlCommand = connecteur.CreateCommand();
                    sqlCommand.CommandText = requête;
                    sqlCommand.Connection = connexion;
                    // تنفيذ الاستعلام
                    DbDataReader reader = sqlCommand.ExecuteReader();
                    // عرض النتائج
...
                }
            } catch (Exception ex) {
                // رسالة خطأ
                Console.WriteLine("Erreur d'accès à la base de données (" + ex.Message + ")");
            }
        }
    }
}
  • الأسطر 12-14: يتلقى التطبيق معلمتين: اسم الموصل العام وسلسلة الاتصال بقاعدة البيانات في شكل مفاتيح من الملف [App.config].
  • السطور 23 و25: يتم استرداد اسم الموصل العام وسلسلة الاتصال من [App.config]
  • السطر 27: يتم إنشاء مثيل للموصل العام. ومنذ هذه اللحظة، يتم ربطه بـ SGBD معين.
  • السطور 39-43: يتم تفويض تنفيذ الأمر SQL الذي تم إدخاله عبر لوحة المفاتيح إلى طريقتين يتم تمرير:
    • الاستعلام المراد تنفيذه
    • سلسلة الاتصال التي تحدد قاعدة البيانات التي سيتم تنفيذ الطلب عليها
    • الموصل العام الذي يحدد الفئات التي سيتم استخدامها للتواصل مع SGBD الذي يدير قاعدة البيانات.
  • الأسطر 50-54: يتم الحصول على اتصال باستخدام الطريقة CreateConnection (السطر 50) للموصل العام ثم يتم تكوينه باستخدام سلسلة اتصال قاعدة البيانات المراد إدارتها (السطر 52). ثم يتم فتحه (السطر 54).
  • الأسطر 56-58: يتم إنشاء الكائن Command الضروري لتنفيذ الأمر SQL باستخدام الطريقة CreateCommand للموصل العام. ثم يتم تكوينه باستخدام نص الأمر SQL المراد تنفيذه (السطر 57) والاتصال الذي سيتم تنفيذه عليه (السطر 58).
  • السطر 60: يتم تنفيذ الأمر SQL للتحديث
  • الأسطر 74-87: يوجد رمز مشابه. الجديد موجود في السطر 84. الكائن Reader الذي تم الحصول عليه من خلال تنفيذ الأمر Select هو من النوع DbDataReader الذي يُستخدم مثل الكائنات OleDbDataReader، OdbcDataReader، ... التي سبق أن صادفناها.

فيما يلي بعض أمثلة التنفيذ.

مع القاعدة MySQL5:

نفتح صفحة خصائص المشروع [1] ونختار علامة التبويب [Debug] [2]. في [3]، مفتاح الموصل في السطر 14 من [App.config]. في [4]، مفتاح سلسلة الاتصال في السطر 6 من [App.config]. نتائج التنفيذ هي كما يلي:

Provider factory : [MySql.Data.MySqlClient]
Chaîne de connexion à la base : [Server=localhost;Database=dbarticles;Uid=root;Pwd=root;]

Requête SQL (rien pour arrêter) : select * from articles

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

1 article1 100 10 1
2 article2 200 20 2
3 article3 300 30 3

مع قاعدة SQL Server Compact:

في [1]، مفتاح الموصل في السطر 13 من [App.config]. في [2]، مفتاح سلسلة الاتصال في السطر 4 من [App.config]. نتائج التنفيذ هي كما يلي:

Provider factory : [System.Data.SqlServerCe.3.5]
Chaîne de connexion à la base : [Data Source=|DataDirectory|\dbarticles.sdf;Password=dbarticles;]

Requête SQL (rien pour arrêter) : select * from articles

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

1 vélo 500 10 5
2 pompe 10 10 2
3 arc 600 4 1
4 flèches - lot de 6 100 12 20
5 combinaison de plongée 300 8 2
6 bouteilles d'oxygène 120 10 5

يُطلب من القارئ اختبار قواعد البيانات الأخرى.

9.4.6. ما هو الموصل الذي يجب اختياره؟

لنعد إلى بنية تطبيق يستخدم قواعد البيانات:

لقد رأينا أنواعًا مختلفة من الموصلات ADO.NET:

  • الموصلات الخاصة هي الأكثر أداءً ولكنها تجعل الطبقة [dao] تعتمد على فئات خاصة. تغيير SGBD يعني تغيير الطبقة [dao].
  • تسمح الموصلات ODBC أو OLE DB بالعمل مع قواعد بيانات متعددة دون تغيير الطبقة [dao]. وهي أقل أداءً من الموصلات الخاصة.
  • يعتمد الموصل العام على الموصلات الخاصة مع تقديم واجهة قياسية لطبقة [dao].

لذلك يبدو أن الموصل العام هو الموصل المثالي. لكن في الواقع، لا يستطيع الموصل العام إخفاء جميع خصائص SGBD خلف واجهة قياسية. سنرى في الفقرة التالية مفهوم الاستعلام المعلم. مع خادم SQL، يكون الاستعلام المعلم بالشكل التالي:

insert into articles(nom,prix,stockactuel,stockminimum) values(@nom,@prix,@sa,@sm)

مع MySQL5، يُكتب نفس الاستعلام على النحو التالي:

insert into articles(nom,prix,stockactuel,stockminimum) values(?,?,?,?)

لذلك هناك اختلاف في الصيغة. خاصية واجهة IDbCommand الموصوفة في الفقرة 9.3.3، والمتعلقة بالمعلمات هي كما يلي:

Parameters
قائمة معلمات أمر SQL الذي تم تعيين معلماته. يحتوي الأمر update articles set prix=prix*1.1 where id=@id على المعلمة @id.

الخاصية Parameters هي من النوع IDataParameterCollection، وهي واجهة. وهي تمثل مجموعة معلمات الأمر SQL CommandText. تحتوي الخاصية Parameters على طريقة Add لإضافة معلمات من النوع IDataParameter، وهي واجهة أيضًا. وتحتوي هذه الواجهة على الخصائص التالية:

  • ParameterName: اسم المعلمة
  • DbType: نوع المعلمة SQL
  • Value: القيمة المخصصة للمعلمة
  • ...

يناسب النوع IDataParameter المعلمات من النوع SQL

insert into articles(nom,prix,stockactuel,stockminimum) values(@nom,@prix,@sa,@sm)

لأنه يحتوي على معلمات مسماة. يمكن استخدام الخاصية ParameterName.

النوع IDataParameter غير مناسب للأمر SQL

insert into articles(nom,prix,stockactuel,stockminimum) values(?,?,?,?)

لأن المعلمات غير مسماة. وفي هذه الحالة، يتم أخذ ترتيب إضافة المعلمات في المجموعة [IDbCommand.Parameters] في الاعتبار. في هذا المثال، يجب إدراج المعلمات الأربعة بالترتيب nom، prix، stockactuel، stockminimum. في الاستعلام الذي يحتوي على معلمات مسماة، لا يهم ترتيب إضافة المعلمات. في النهاية، لا يمكن للمطور تجاهل SGBD تمامًا الذي يستخدمه عند تهيئة معلمات استعلام معلم. هذه إحدى القيود الحالية للموصل العام.

هناك frameworks تتجاوز هذه القيود وتوفر أيضًا ميزات جديدة لطبقة [dao]:

الإطار هو مجموعة من مكتبات الفئات التي تهدف إلى تسهيل طريقة معينة لتصميم التطبيق. هناك العديد منها التي تسمح بكتابة طبقات [dao] عالية الأداء وغير حساسة لتغيير SGBD:

  • Spring.Net [http://www.springframework.net/] الذي تم عرضه سابقًا في هذا المستند يقدم ما يعادل الموصل العام الذي تمت دراسته، دون قيوده، بالإضافة إلى ميزات متنوعة تبسط الوصول إلى البيانات. توجد نسخة Java.
  • iBatis.Net [http://ibatis.apache.org] أقدم وأكثر ثراءً من Spring.Net. توجد نسخة Java.
  • NHibernate [http://www.hibernate.org/] هو نسخة معدلة من إصدار Java Hibernate الشهير في عالم Java. يسمح NHibernate للطبقة [dao] بالتبادل مع SGBD دون إصدار أوامر SQL. تعمل الطبقة [dao] مع كائنات Hibernate. تسمح لغة الاستعلامات HBL (لغة استعلامات Hibernate) بالاستعلام عن الكائنات التي تديرها Hibernate. وهذه الكائنات هي التي تصدر أوامر SQL. يستطيع Hibernate التكيف مع SQL المالكين لـ SGBD.
  • LINQ (لغة استعلام INtegrated)، مدمجة في الإصدار 3.5 .NET ومتوفرة في C# 2008. LINQ يسير على خطى NHibernate، ولكن في الوقت الحالي (مايو 2008) لا يتم دعم سوى خادم SGBD SQL. ومن المتوقع أن يتغير هذا مع مرور الوقت. يتجاوز LINQ نطاق NHibernate: حيث تتيح لغة الاستعلامات الخاصة به الاستعلام بشكل قياسي عن ثلاثة أنواع مختلفة من مصادر البيانات:
    • مجموعات الكائنات (LINQ to Objects)
    • ملف Xml (LINQ to Xml)
    • قاعدة بيانات (LINQ to SQL)

لن يتم تناول هذه الأطر في هذا المستند. ومع ذلك، يُنصح بشدة باستخدامها في التطبيقات الاحترافية.

9.5. الاستعلامات المعلمة

لقد تطرقنا في الفقرة السابقة إلى الاستعلامات المعلمة. نقدمها هنا مع مثال لـ SGBD SQL Server Compact. المشروع هو التالي

  • في [1]، المشروع. يتم استخدام [App.config] و [Article.cs] و [Parametres.cs] فقط. تجدر الإشارة أيضًا إلى قاعدة SQL Server هذا [dbarticles.sdf].
  • في [2]، تم تكوين المشروع لتنفيذ [Parametres.cs]
  • في [3]، مراجع المشروع

يحدد ملف التكوين [App.config] سلسلة الاتصال بقاعدة البيانات:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
        <add name="dbArticlesSqlServerCe" connectionString="Data Source=|DataDirectory|\dbarticles.sdf;Password=dbarticles;" />
    </connectionStrings>
</configuration>

يحدد الملف [Article.cs] فئة [Article]. سيتم استخدام كائن Article لتغليف معلومات سطر من الجدول ARTICLES في قاعدة البيانات [dbarticles.sdf]:


namespace Chap7 {
    class Article {
        // الخصائص
        public int Id { get; set; }
        public string Nom { get; set; }
        public decimal Prix { get; set; }
        public int StockActuel { get; set; }
        public int StockMinimum { get; set; }

        // المنشئون
        public Article() { 
        }

        public Article(int id, string nom, decimal prix, int stockActuel, int stockMinimum) {
            Id = id;
            Nom = nom;
            Prix = prix;
            StockActuel = stockActuel;
            StockMinimum = stockMinimum;
        }

    }
}

تنفذ التطبيق [Parametres.cs] الاستعلامات المعلمة:


using System;
using System.Data.SqlServerCe;
using System.Text;
using System.Data;
using System.Configuration;

namespace Chap7 {
    class Parametres {
        static void Main(string[] args) {

            // استخدام ملف التكوين
            string connectionString = null;
            try {
                // سلسلة الاتصال
                connectionString = ConfigurationManager.ConnectionStrings["dbArticlesSqlServerCe"].ConnectionString;
            } catch (Exception e) {
                Console.WriteLine("Erreur de configuration : {0}", e.Message);
                return;
            }

            // العروض
            Console.WriteLine("Chaîne de connexion à la base : [{0}]\n", connectionString);

            // إنشاء جدول المواد
            Article[] articles = new Article[5];
            for (int i = 1; i <= articles.Length; i++) {
                articles[i-1] = new Article(0, "article" + i, i * 100, i * 10, i);
            }

            // إدارة الاستثناءات المحتملة
            try {

                // حذف العناصر الموجودة من قاعدة البيانات
                ExecuteUpdate(connectionString, "delete from articles");

                // عرض العناصر الموجودة في الجدول
                ExecuteSelect(connectionString, "select id,nom,prix,stockactuel,stockminimum from articles");

                // إدراج جدول المواد في قاعدة البيانات
                InsertArticles(connectionString, articles);

                // عرض سجلات الجدول
                ExecuteSelect(connectionString, "select id,nom,prix,stockactuel,stockminimum from articles");
            } catch (Exception ex) {
                // رسالة خطأ
                Console.WriteLine("Erreur d'accès à la base de données (" + ex.Message + ")");
            }
        }

        // إدراج جدول العناصر
        static void InsertArticles(string connectionString, Article[] articles) {
            using (SqlCeConnection connexion = new SqlCeConnection(connectionString)) {
                // فتح الاتصال
                connexion.Open();
                // تكوين الأمر
                string requête = "insert into articles(nom,prix,stockactuel,stockminimum) values(@nom,@prix,@sa,@sm)";
                SqlCeCommand sqlCommand = new SqlCeCommand(requête, connexion);
                sqlCommand.Parameters.Add("@nom",SqlDbType.NVarChar,30);
                sqlCommand.Parameters.Add("@prix", SqlDbType.Money);
                sqlCommand.Parameters.Add("@sa", SqlDbType.Int);
                sqlCommand.Parameters.Add("@sm", SqlDbType.Int);
                // تجميع الطلب
                sqlCommand.Prepare();
                // إدراج الأسطر
                for (int i = 0; i < articles.Length; i++) {
                    // تهيئة المعلمات
                    sqlCommand.Parameters["@nom"].Value = articles[i].Nom;
                    sqlCommand.Parameters["@prix"].Value = articles[i].Prix;
                    sqlCommand.Parameters["@sa"].Value = articles[i].StockActuel;
                    sqlCommand.Parameters["@sm"].Value = articles[i].StockMinimum;
                    // تنفيذ الاستعلام
                    sqlCommand.ExecuteNonQuery();
                }
            }
        }

        // تنفيذ طلب تحديث
        static void ExecuteUpdate(string connectionString, string requête) {
...
        }

        // تنفيذ استعلام Select
        static void ExecuteSelect(string connectionString, string requête) {
...
        }

        // عرض القارئ
        static void AfficheReader(IDataReader reader) {
...
    }
}

الجديد مقارنة بما سبق هو الإجراء [InsertArticles] في الأسطر 51-75:

  • السطر 51: تتلقى الإجراء معلمتين:
    • سلسلة الاتصال connectionString التي ستسمح للإجراء بالاتصال بقاعدة البيانات
    • مصفوفة كائنات Article التي يجب إضافتها إلى الجدول Articles في قاعدة البيانات
  • السطر 56: طلب إدراج كائن [Article]. له أربعة معلمات:
    • @nom: اسم العنصر
    • @prix: سعره
    • @sa: مخزونه الحالي
    • @sm: الحد الأدنى للمخزون

صيغة هذا الطلب المعلم مملوكة لـ SQL Server Compact. وقد رأينا في الفقرة السابقة أنه مع MySQL5، ستكون الصيغة كما يلي:

insert into articles(nom,prix,stockactuel,stockminimum) values(?,?,?,?)

مع SQL Server Compact، يجب أن يسبق كل معلمة الحرف @. يمكن تسمية المعلمات بحرية.

  • الأسطر 58-61: يتم تحديد خصائص كل معلمة من المعلمات الأربع وإضافتها، واحدة تلو الأخرى، إلى قائمة معلمات الكائن SqlCeCommand الذي يضم الأمر SQL المقرر تنفيذه.

نستخدم هنا الطريقة [SqlCeCommand].Parameters.Add التي تحتوي على ستة توقيعات. نستخدم التوقيعين التاليين:

Add(string parameterName, SQLDbType type)

تضيف وتُهيئ المعلمة المسماة parameterName. يجب أن يكون هذا الاسم أحد أسماء الاستعلام المُهيأ المُهيأ: (@name, ...). تشير type إلى النوع SQL للعمود المعني بالمعلمة. تتوفر أنواع عديدة منها ما يلي:

النوع SQL
النوع C#
تعليق
BigInt
Int64
 
DateTime
DateTime
 
Decimal
Decimal
 
Float
Double
 
Int
Int32
 
Money
عشري
 
NChar
سلسلة
سلسلة ذات طول ثابت
NVarChar
سلسلة
سلسلة متغيرة الطول
Real
Single
 

Add(string parameterName, SQLDbType type, int size)

يحدد المعلمة الثالثة size حجم العمود. هذه المعلومة مفيدة فقط لبعض الأنواع SQL، مثل النوع NVarChar على سبيل المثال.

  • السطر 63: يتم تجميع الاستعلام المعلم. يُقال أيضًا أنه يتم إعداده، ومن هنا جاء اسم الطريقة. هذه العملية ليست ضرورية. وهي موجودة لتحسين الأداء. عندما يقوم SGBD بتنفيذ أمر SQL، فإنه يقوم ببعض أعمال التحسين قبل تنفيذه. الاستعلام المعلم مخصص للتنفيذ عدة مرات بمعلمات مختلفة. نص الاستعلام نفسه لا يتغير. وبالتالي، يمكن إجراء عملية التحسين مرة واحدة فقط. تتمتع بعض عمليات SGBD بالقدرة على "تحضير" أو "تجميع" الاستعلامات المعلمة. يتم عندئذٍ تحديد خطة تنفيذ لهذا الاستعلام. هذه هي مرحلة التحسين التي تحدثنا عنها. بمجرد تجميع الاستعلام، يتم تنفيذه بشكل متكرر مع معلمات فعالة جديدة في كل مرة ولكن بنفس خطة التنفيذ.

التجميع ليس الميزة الوحيدة للاستعلامات المعلمة. لنعد إلى الاستعلام الذي درسناه:

insert into articles(nom,prix,stockactuel,stockminimum) values(@nom,@prix,@sa,@sm)

قد نرغب في إنشاء نص الاستعلام برمجيًا:

string requête="insert into articles(nom,prix,stockactuel,stockminimum) values('"+nom+"',"+prix+","+sa+","+sm+")";

فيما سبق، إذا كان (name,price,sa,sm) يساوي ("article1",100,10,1)، فإن الاستعلام السابق يصبح:

string requête="insert into articles(nom,prix,stockactuel,stockminimum) values('article1',100,10,1)";

الآن إذا كانت (الاسم، السعر، sa، sm) تساوي ("المادة 1"، 100، 10، 1)، فإن الاستعلام السابق يصبح:

string requête="insert into articles(nom,prix,stockactuel,stockminimum) values('l'article1',100,10,1)";

وتصبح غير صحيحة من الناحية النحوية بسبب الفاصلة العليا في الاسم l'article1. إذا كان nom ناتجًا عن إدخال من المستخدم، فهذا يعني أننا مضطرون للتحقق مما إذا كان الإدخال خاليًا من الفواصل العليا، وإذا كان يحتوي عليها، فيجب إبطالها. يعتمد هذا الإلغاء على SGBD. وتكمن فائدة الاستعلام المُعد في أنه يقوم بهذه المهمة بنفسه. وهذه الميزة وحدها تبرر استخدام استعلام مُعد.

  • الأسطر 65-73: يتم إدراج عناصر الجدول واحدة تلو الأخرى
  • الأسطر 67-70: يتلقى كل معلم من المعلمات الأربعة للاستعلام قيمته عبر خاصيته Value.
  • السطر 72: يتم تنفيذ استعلام الإدراج الذي أصبح الآن كاملاً بالطريقة المعتادة.

فيما يلي مثال على التنفيذ:

Chaîne de connexion à la base : [Data Source=|DataDirectory|\dbarticles.sdf;Password=dbarticles;]

Il y a eu 5 ligne(s) modifiée(s)

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------


------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

117 article1 100 10 1
118 article2 200 20 2
119 article3 300 30 3
120 article4 400 40 4
121 article5 500 50 5
  • السطر 3: رسالة بعد حذف جميع الأسطر من الجدول
  • الأسطر 5-7: تظهر أن الجدول فارغ
  • الأسطر 10-18: تظهر الجدول بعد إدراج 5 عناصر

9.6. Transactions

9.6.1. معلومات عامة

المعاملة هي سلسلة من الأوامر SQL التي يتم تنفيذها بشكل "أتمي":

  • إما أن تنجح جميع العمليات
  • إما أن تفشل إحداها، وفي هذه الحالة يتم إلغاء جميع العمليات التي سبقتها

في النهاية، إما أن يتم تنفيذ جميع عمليات المعاملة بنجاح، أو لا يتم تنفيذ أي منها. عندما يكون المستخدم هو من يتحكم في المعاملة، فإنه يصادق عليها بأمر COMMIT أو يلغيها بأمر ROLLBACK.

في الأمثلة السابقة، لم نستخدم أي معاملة. ومع ذلك، كانت هناك معاملات، لأن الأمر SQL في SGBD يتم تنفيذه دائمًا ضمن معاملة. إذا لم يبدأ العميل .NET بنفسه معاملة صريحة، فإن SGBD يستخدم معاملة ضمنية. وهناك حالتان شائعتان:

  1. يخضع كل أمر SQL فردي لمعاملة، تبدأ بواسطة SGBD قبل الأمر وتُغلق بعده. يُقال إننا في وضع autocommit. لذلك، تسير الأمور كما لو أن العميل .NET يقوم بمعاملات لكل أمر SQL.
  2. لا يعمل SGBD في وضع autocommit ويبدأ معاملة ضمنية عند الأمر الأول SQL الذي يصدره العميل .NET خارج نطاق معاملة، ويترك للعميل مهمة إغلاقها. تصبح جميع الأوامر SQL التي يصدرها العميل .NET جزءًا من المعاملة الضمنية. ويمكن أن تنتهي هذه المعاملة بناءً على أحداث مختلفة: يقوم العميل بإغلاق الاتصال، أو يبدأ معاملة جديدة، ... ولكننا نكون عندئذ في وضع يعتمد على SGBD. وهذا وضع يجب تجنبه.

يتم تحديد الوضع الافتراضي عمومًا من خلال تكوين SGBD. بعض SGBD تكون في وضع autocommit بشكل افتراضي، والبعض الآخر لا. SQLServer Compact تكون في وضع autocommit بشكل افتراضي.

يتم تنفيذ أوامر SQL الخاصة بالمستخدمين المختلفين في نفس الوقت في معاملات تعمل بالتوازي. قد تؤثر العمليات التي تقوم بها إحدى المعاملات على تلك التي تقوم بها معاملة أخرى. هناك أربعة مستويات من العزل بين معاملات المستخدمين المختلفين:

  • قراءة غير ملتزم بها
  • قراءة ملتزم بها
  • قراءة قابلة للتكرار
  • قراءة قابلة للتسلسل

قراءة غير ملتزم بها

يُطلق على وضع العزل هذا أيضًا اسم "قراءة غير نظيفة". فيما يلي مثال على ما قد يحدث في هذا الوضع:

  1. يبدأ المستخدم U1 معاملة على الجدول T
  2. يبدأ المستخدم U2 معاملة على نفس الجدول T
  3. يقوم المستخدم U1 بتعديل صفوف الجدول T ولكنه لم يقم بتأكيدها بعد
  4. المستخدم U2 "يرى" هذه التعديلات ويتخذ قرارات بناءً على ما يراه
  5. يقوم المستخدم بإلغاء معاملته بواسطة ROLLBACK

نلاحظ أنه في الخطوة 4، اتخذ المستخدم U2 قرارًا بناءً على بيانات ستتضح لاحقًا أنها خاطئة.

القراءة الملتزم بها

يمنع وضع العزل هذا المعضلة السابقة. في هذا الوضع، لن "يرى" المستخدم U2 في الخطوة 4 التعديلات التي أجراها المستخدم U1 على الجدول T. ولن يراها إلا بعد أن يقوم U1 بإجراء COMMIT لمعاملته.

في هذا الوضع، الذي يُعرف أيضًا باسم "Unrepeatable Read"، قد نواجه مع ذلك الحالات التالية:

  1. يبدأ المستخدم U1 معاملة على الجدول T
  2. يبدأ مستخدم U2 معاملة على نفس الجدول T
  3. يقوم المستخدم U2 بإجراء SELECT للحصول على متوسط العمود C من صفوف T التي تستوفي شرطًا معينًا
  4. يقوم المستخدم U1 بتعديل (UPDATE) بعض القيم في العمود C من T والتحقق من صحتها (COMMIT)
  5. يقوم المستخدم U2 بإعادة تنفيذ نفس SELECT كما في 3. وسيكتشف أن متوسط العمود C قد تغير بسبب التعديلات التي أجراها U1.

الآن لا يرى المستخدم U2 سوى التعديلات التي "تمت الموافقة عليها" بواسطة U1. ولكن بينما يظل في نفس المعاملة، تعطي عمليتان متطابقتان 3 و 5 نتائج مختلفة. يشير مصطلح "Unrepeatable Read" إلى هذه الحالة. إنها حالة مزعجة لمن يرغب في الحصول على صورة ثابتة للجدول T.

القراءة القابلة للتكرار

في وضع العزل هذا، يضمن المستخدم الحصول على نفس النتائج لقراءاته من قاعدة البيانات طالما بقي في نفس المعاملة. فهو يعمل على نسخة لا تنعكس عليها أبدًا التعديلات التي أجرتها المعاملات الأخرى، حتى لو تم تأكيدها. ولن يرى هذه التعديلات إلا عندما ينهي المعاملة بنفسه باستخدام COMMIT أو ROLLBACK.

ومع ذلك، فإن وضع العزل هذا ليس مثاليًا بعد. بعد العملية 3 أعلاه، يتم قفل الأسطر التي استشارها المستخدم U2. أثناء العملية 4، لن يتمكن المستخدم U1 من تعديل (UPDATE) قيم العمود C في هذه الأسطر. ومع ذلك، يمكنه إضافة أسطر (INSERT). إذا كانت بعض الأسطر المضافة تستوفي الشرط الذي تم اختباره في الخطوة 3، فستعطي العملية 5 متوسطًا مختلفًا عن المتوسط الذي تم الحصول عليه في الخطوة 3 بسبب الأسطر المضافة. تسمى هذه الأسطر أحيانًا بالأسطر الوهمية.

لحل هذه المشكلة الجديدة، يجب الانتقال إلى العزل "Serializable".

Serializable

في وضع العزل هذا، تكون المعاملات معزولة تمامًا عن بعضها البعض. ويضمن أن نتيجة معاملتين يتم إجراؤهما في وقت واحد ستعطي نفس النتيجة كما لو تم إجراؤهما واحدة تلو الأخرى. للوصول إلى هذه النتيجة، في العملية 4 حيث يريد المستخدم U1 إضافة أسطر من شأنها تغيير نتيجة SELECT للمستخدم U1، سيتم منعه من ذلك. ستظهر له رسالة خطأ تفيد بأن الإدراج غير ممكن. وسيصبح ذلك ممكنًا عندما يقوم المستخدم U2 بتأكيد معاملته.

مستويات عزل المعاملات الأربعة SQL غير متوفرة في جميع SGBD. عادةً ما يكون مستوى العزل الافتراضي هو مستوى Committed Read. يمكن تحديد مستوى العزل المطلوب لمعاملة ما بشكل صريح عند إنشاء معاملة صريحة بواسطة عميل .NET.

9.6.2. إدارة المعاملات API

يُنفذ الاتصال واجهة IDbConnection الموضحة في الفقرة 9.3.3. تحتوي هذه الواجهة على الطريقة التالية:

BeginTransaction
M
تبدأ معاملة.

هذه الطريقة لها توقيعان:

  1. IDbTransaction BeginTransaction(): تبدأ معاملة وتُرجع الكائن IDbTransaction الذي يسمح بالتحكم فيها
  2. IDbTransaction BeginTransaction(IsolationLevel level): تحدد أيضًا مستوى السرية المطلوب للمعاملة. تأخذ level قيمها من التعداد التالي:
ReadUncommitted
يمكن للمعاملة قراءة البيانات المكتوبة بواسطة معاملة أخرى لم تقم هذه الأخيرة بالتحقق من صحتها بعد - يجب تجنب ذلك
ReadCommitted
لا يمكن للمعاملة قراءة البيانات التي كتبتها معاملة أخرى ولم تقم هذه الأخيرة بالتحقق منها بعد. ومع ذلك، قد تتغير البيانات التي تمت قراءتها مرتين متتاليتين في المعاملة (قراءات غير قابلة للتكرار) لأن معاملة أخرى قد قامت بتعديلها في غضون ذلك (السطور التي تمت قراءتها غير مقفلة - فقط السطور التي تم تحديثها هي المقفلة). علاوة على ذلك، قد تكون معاملة أخرى قد أضافت أسطرًا (أسطر وهمية) سيتم تضمينها في القراءة الثانية.
RepeatableRead
يتم قفل الصفوف التي تقرأها المعاملة تمامًا مثل الصفوف التي يتم تحديثها. وهذا يمنع أي معاملة أخرى من تعديلها. لكنه لا يمنع إضافة صفوف جديدة.
Serializable
يتم قفل الجداول التي تستخدمها المعاملة، مما يمنع إضافة أسطر جديدة بواسطة معاملة أخرى. كل شيء يسير كما لو كانت المعاملة هي الوحيدة. يقلل من الأداء لأن المعاملات لم تعد تعمل بالتوازي.
Snapshot
تعمل المعاملة على نسخة من البيانات تم إنشاؤها في الوقت T. تُستخدم عندما تكون المعاملة للقراءة فقط. تعطي نفس نتيجة serializable مع تجنب تكلفتها.

بمجرد بدء المعاملة، يتم التحكم فيها بواسطة كائن من النوع IDbTransaction، وهي واجهة سنستخدم خصائصها P وطرقها M التالية:

الاسم
النوع
الدور
Connection
P
اتصال IDbConnection الذي يدعم المعاملة
Commit
M
تقوم بالتحقق من صحة المعاملة - يتم نسخ نتائج الأوامر SQL الصادرة في المعاملة إلى قاعدة البيانات.
Rollback
M
إلغاء صحة المعاملة - لا يتم نسخ نتائج الأوامر SQL الصادرة في المعاملة إلى قاعدة البيانات.

9.6.3. برنامج المثال

نعود إلى المشروع السابق لنركز الآن على البرنامج [Transactions.cs]:

  • في [1]، المشروع.
  • في [2]، تم تكوين المشروع لتنفيذ [Transactions.cs]

رمز [Transactions.cs] هو التالي:


using System;
using System.Configuration;
using System.Data;
using System.Data.SqlServerCe;
using System.Text;

namespace Chap7 {
    class Transactions {
        static void Main(string[] args) {

            // استخدام ملف التكوين
            string connectionString = null;
            try {
                // سلسلة الاتصال
                connectionString = ConfigurationManager.ConnectionStrings["dbArticlesSqlServerCe"].ConnectionString;
            } catch (Exception e) {
                Console.WriteLine("Erreur de configuration : {0}", e.Message);
                return;
            }

            // عمليات العرض
            Console.WriteLine("Chaîne de connexion à la base : [{0}]\n", connectionString);

            // إنشاء جدول من عنصرين يحملان نفس الاسم
            Article[] articles = new Article[2];
            for (int i = 1; i <= articles.Length; i++) {
                articles[i - 1] = new Article(0, "article", i * 100, i * 10, i);
            }
            // إدارة الاستثناءات المحتملة
            try {
                Console.WriteLine("Insertion sans transaction...");
                // إدراج جدول العناصر في قاعدة البيانات أولاً بدون معاملة
                ExecuteUpdate(connectionString, "delete from articles");
                try {
                    InsertArticlesOutOfTransaction(connectionString, articles);
                } catch (Exception ex) {
                    // رسالة خطأ
                    Console.WriteLine("Erreur d'accès à la base de données (" + ex.Message + ")");
                }
                ExecuteSelect(connectionString, "select id,nom,prix,stockactuel,stockminimum from articles");

                // إعادة نفس العملية ولكن في معاملة هذه المرة
                Console.WriteLine("\n\nInsertion dans une transaction...");
                ExecuteUpdate(connectionString, "delete from articles");
                InsertArticlesInTransaction(connectionString, articles);
                ExecuteSelect(connectionString, "select id,nom,prix,stockactuel,stockminimum from articles");
            } catch (Exception ex) {
                // رسالة خطأ
                Console.WriteLine("Erreur d'accès à la base de données (" + ex.Message + ")");
            }
        }

        // إدراج جدول المواد بدون معاملة
        static void InsertArticlesOutOfTransaction(string connectionString, Article[] articles) {
....
        }

        // إدراج جدول منتجات في معاملة
        static void InsertArticlesInTransaction(string connectionString, Article[] articles) {
....
        }

        // تنفيذ طلب تحديث
        static void ExecuteUpdate(string connectionString, string requête) {
....
        }

        // تنفيذ استعلام Select
        static void ExecuteSelect(string connectionString, string requête) {
...
        }

        // عرض القارئ
        static void AfficheReader(IDataReader reader) {
...
            }
        }
    }
}
  • الأسطر 12-19: سلسلة الاتصال بقاعدة البيانات SQLServer يتم قراءتها في [App.config]
  • السطور 25-28: يتم إنشاء مصفوفة من كائنين Article. هذان العنصران لهما نفس الاسم "article". ومع ذلك، فإن قاعدة البيانات [dbarticles.sdf] لها قيد تفرد على عمودها [nom] (انظر الفقرة 9.3.1). لذلك لا يمكن أن يتواجد هذان العنصران في نفس الوقت في قاعدة البيانات. يتم إضافة المقالتين اللتين تحملان اسم "article" إلى الجدول articles. وبالتالي، ستحدث مشكلة، c.a.d. استثناء تم إطلاقه بواسطة SGBD وتم تمريره بواسطة موصلها ADO.NET. لإظهار تأثير المعاملة، سيتم إدراج المقالتين في بيئتين مختلفتين:
    • أولاً خارج أي معاملة. يجب أن نتذكر هنا أنه، في هذه الحالة، يعمل SQLServer Compact في وضع autocommit، حيث يقوم c.a.d بإدراج كل أمر SQL في معاملة ضمنية. سيتم إدراج المقالة الأولى. أما الثانية فلن يتم إدراجها.
    • ثم في معاملة صريحة تغلف الإدراجين. ولأن الإدراج الثاني سيفشل، فسيتم التراجع عن الأول. وفي النهاية لن يتم أي إدراج.
  • السطر 33: يتم إفراغ الجدول articles
  • السطر 35: إدراج العنصرين بدون معاملة صريحة. ونظرًا لأننا نعلم أن الإدراج الثاني سيؤدي إلى استثناء، يتم التعامل مع هذا الاستثناء بواسطة try / catch
  • السطر 46: عرض الجدول articles
  • الأسطر 44-46: نكرر نفس التسلسل ولكن هذه المرة يتم استخدام معاملة صريحة لإجراء عمليات الإدراج. يتم هنا معالجة الاستثناء الذي تم مواجهته بواسطة الطريقة InsertArticlesInTransaction.
  • الأسطر 54-56: الطريقة InsertArticlesOutOfTransaction هي الطريقة InsertArticles من البرنامج [Parametres.cs] الذي تمت دراسته سابقًا.
  • الأسطر 64-66: الطريقة ExecuteUpdate هي نفسها المذكورة سابقًا. يتم تنفيذ الأمر SQL في معاملة ضمنية. وهذا ممكن هنا لأننا نعلم أن في هذه الحالة، يعمل SQLServer Compact في وضع autocommit.
  • السطور 69-71: الأمر نفسه بالنسبة للطريقة ExecuteSelect.

الطريقة InsertArticlesInTransaction هي كما يلي:


// إدراج جدول المواد في معاملة
        static void InsertArticlesInTransaction(string connectionString, Article[] articles) {
            using (SqlCeConnection connexion = new SqlCeConnection(connectionString)) {
                // فتح اتصال
                connexion.Open();
                // تكوين الأمر
                string requête = "insert into articles(nom,prix,stockactuel,stockminimum) values(@nom,@prix,@sa,@sm)";
                SqlCeCommand sqlCommand = new SqlCeCommand(requête, connexion);
                sqlCommand.Parameters.Add("@nom", SqlDbType.NVarChar, 30);
                sqlCommand.Parameters.Add("@prix", SqlDbType.Money);
                sqlCommand.Parameters.Add("@sa", SqlDbType.Int);
                sqlCommand.Parameters.Add("@sm", SqlDbType.Int);
                // تجميع الأمر
                sqlCommand.Prepare();
                // معاملة
                SqlCeTransaction transaction = null;
                try {
                    // بدء المعاملة
                    transaction = connexion.BeginTransaction(IsolationLevel.ReadCommitted);
                    // يجب تنفيذ الأمر SQL في هذه المعاملة
                    sqlCommand.Transaction = transaction;
                    // إدراج الأسطر
                    for (int i = 0; i < articles.Length; i++) {
                        // تهيئة المعلمات
                        sqlCommand.Parameters["@nom"].Value = articles[i].Nom;
                        sqlCommand.Parameters["@prix"].Value = articles[i].Prix;
                        sqlCommand.Parameters["@sa"].Value = articles[i].StockActuel;
                        sqlCommand.Parameters["@sm"].Value = articles[i].StockMinimum;
                        // تنفيذ الاستعلام
                        sqlCommand.ExecuteNonQuery();
                    }
                    // تتم المصادقة على المعاملة
                    transaction.Commit();
                    Console.WriteLine("transaction validée...");
                } catch {
                    // إلغاء المعاملة
                    if (transaction != null)transaction.Rollback();
                    Console.WriteLine("transaction invalidée...");
                }
            }
        }

نكتفي بتفصيل ما يميزها عن الطريقة InsertArticles في البرنامج [Parametres.cs] الذي تمت دراسته سابقًا:

  • السطر 16: يتم الإعلان عن معاملة SqlCeTransaction.
  • السطران 17 و35: try / catch لإدارة الاستثناء الذي سيظهر في نهاية الإدراج الثاني
  • السطر 19: يتم إنشاء المعاملة. وهي تنتمي إلى الاتصال الحالي.
  • السطر 21: يتم وضع الأمر SQL المُعد في المعاملة
  • الأسطر 23-31: يتم إجراء عمليات الإدراج
  • السطر 33: سارت الأمور على ما يرام - تم التحقق من صحة المعاملة - سيتم دمج عمليات الإدراج بشكل نهائي في قاعدة البيانات.
  • السطر 37: حدثت مشكلة. يتم إلغاء المعاملة إن كانت موجودة.

يؤدي التنفيذ إلى النتائج التالية:

Chaîne de connexion à la base : [Data Source=|DataDirectory|\dbarticles.sdf;Password=dbarticles;]

Insertion sans transaction...
Il y a eu 0 ligne(s) modifiée(s)
Erreur d'accès à la base de données (A duplicate value cannot be inserted into a unique index. [ Table name = ARTICLES,Constraint name = UQ__ARTICLES__0000000000000010 ])

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

126 article 100 10 1


Insertion dans une transaction...
Il y a eu 1 ligne(s) modifiée(s)
transaction invalidée...

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------
  • السطر 4: معروض بواسطة ExecuteUpdate("delete from articles") - لم تكن هناك أية أسطر في الجدول
  • السطر 5: الاستثناء الناتج عن الإدراج الثاني. تشير الرسالة إلى أن القيد UQ__ARTICLES__0000000000000010 لم يتم التحقق منه. يمكن معرفة المزيد من خلال الاطلاع على خصائص قاعدة البيانات:
  • في [1] في عرض [Database Explorer] في Visual Studio، تم إنشاء اتصال [2] على قاعدة البيانات [dbarticles.sdf]. ويحتوي هذا الاتصال على فهرس UQ__ARTICLES__0000000000000010. بالنقر بزر الماوس الأيمن على هذا الفهرس، يمكن الوصول إلى خصائصه (Index properties)
  • في [3,4]، نرى أن الفهرس UQ__ARTICLES__0000000000000010 يتوافق مع قيد التفرد على العمود [NOM]
  • السطور 7-11: عرض الجدول articles بعد الإدراجين. إنه ليس فارغًا: تم إدراج المقالة الأولى.
  • السطر 15: معروض بواسطة ExecuteUpdate("delete from articles") - كان هناك سطر في الجدول
  • السطر 16: رسالة معروضة بواسطة InsertArticlesInTransaction عند فشل المعاملة.
  • الأسطر 18-20: تظهر أنه لم يتم إجراء أي إدراج. قام Rollback الخاص بالمعاملة بإلغاء الإدراج الأول.

9.7. طريقة ExecuteScalar

9.7.1. من بين طرق واجهة IDbCommand الموصوفة في الفقرة 9.3.3، كانت هناك الطريقة التالية:

ExecuteScalar
M
لتنفيذ أمر SQL Select الذي يعطي نتيجة واحدة فقط كما في: select count(*) from articles.

نعرض هنا مثالاً على استخدام هذه الطريقة. لنعد إلى المشروع:

  • في [1]، المشروع.
  • في [2]، تم تكوين المشروع لتنفيذ [ExecuteScalar.cs]

البرنامج [ExecuteScalar.cs] هو التالي:


...
namespace Chap7 {
    class Scalar {
        static void Main(string[] args) {

            // استخدام ملف التكوين
            string connectionString = null;
...

            // عمليات العرض
            Console.WriteLine("Chaîne de connexion à la base : [{0}]\n", connectionString);

            // إنشاء جدول من 5 عناصر
            Article[] articles = new Article[5];
            for (int i = 1; i <= articles.Length; i++) {
                articles[i - 1] = new Article(0, "article" + i, i * 100, i * 10, i);
            }

            // إدارة الاستثناءات المحتملة
            try {
                // إدراج جدول العناصر في معاملة
                ExecuteUpdate(connectionString, "delete from articles");
                InsertArticlesInTransaction(connectionString, articles);
                ExecuteSelect(connectionString, "select id,nom,prix,stockactuel,stockminimum from articles");
                // حساب متوسط أسعار العناصر
                decimal prixMoyen = (decimal)ExecuteScalar(connectionString, "select avg(prix) from articles");
                Console.WriteLine("Prix moyen des articles={0}", prixMoyen);
                // أو عدد العناصر
                int nbArticles = (int)ExecuteScalar(connectionString, "select count(id) from articles");
                Console.WriteLine("Nombre d'articles={0}", nbArticles);
            } catch (Exception ex) {
                // رسالة خطأ
                Console.WriteLine("Erreur d'accès à la base de données (" + ex.Message + ")");
            }
        }

        // إدراج جدول المواد في معاملة
        static void InsertArticlesInTransaction(string connectionString, Article[] articles) {
...
        }


        // تنفيذ طلب تحديث
        static object ExecuteScalar(string connectionString, string requête) {
            using (SqlCeConnection connexion = new SqlCeConnection(connectionString)) {
                // فتح اتصال
                connexion.Open();
                // تنفيذ الاستعلام
                return new SqlCeCommand(requête, connexion).ExecuteScalar();
            }
        }

        // تنفيذ طلب تحديث
        static void ExecuteUpdate(string connectionString, string requête) {
...
        }

        // تنفيذ استعلام Select
        static void ExecuteSelect(string connectionString, string requête) {
...
        }

        // عرض القارئ
        static void AfficheReader(IDataReader reader) {
...
        }
    }
}
  • الأسطر 14-17: إنشاء جدول مكون من 5 عناصر
  • السطر 22: يتم إفراغ الجدول articles
  • السطر 23: يتم ملؤها بالـ 5 عناصر
  • السطر 24: يتم عرضها
  • السطر 26: طلب متوسط سعر العناصر
  • السطر 29: يطلب عدد العناصر
  • السطر 49: استخدام الطريقة [IDbCommand].ExecuteScalar() لحساب كل من هذه القيم.

نتائج التنفيذ هي كما يلي:

Chaîne de connexion à la base : [Data Source=|DataDirectory|\dbarticles.sdf;Password=dbarticles;]

Il y a eu 5 ligne(s) modifiée(s)
transaction validée...

------------------------------------
ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM
------------------------------------

145 article1 100 10 1
146 article2 200 20 2
147 article3 300 30 3
148 article4 400 40 4
149 article5 500 50 5
Prix moyen des articles=300
Nombre d'articles=5

تُظهر السطران 15 و16 القيمتين اللتين ترجعها الطريقة ExecuteScalar.

9.8. تطبيق نموذجي - الإصدار 7

نعود إلى تطبيق المثال IMPOTS. تمت دراسة الإصدار الأخير في الفقرة 7.6. كان هذا هو التطبيق ثلاثي الطبقات التالي:

  • كانت الطبقة [ui] عبارة عن واجهة رسومية [A]، وكانت الطبقة [dao] تستمد بياناتها من ملف نصي [B].
  • كان Spring هو المسؤول عن إنشاء مثيلات الطبقات ودمجها في التطبيق.

نقوم بتعديل الطبقة [dao] بحيث تسترجع بياناتها من قاعدة بيانات.

9.8.1. قاعدة البيانات

يتم وضع محتوى الملف النصي [B] السابق في قاعدة بيانات MySQL5. نوضح كيفية القيام بذلك:

  • [1] :MySQL تم تشغيل Administrator
  • [2,3]: في المنطقة [Schemata]، انقر بزر الماوس الأيمن واختر الخيار [Create Schema] لإنشاء قاعدة بيانات جديدة
  • [4]: ستسمى قاعدة البيانات [bdimpots]
  • [5]: تمت إضافتها إلى قواعد منطقة [Schemata].
  • [6,7]: انقر بزر الماوس الأيمن على الجدول واختر الخيار [Create New Table] لإنشاء جدول
  • [8]: سيُسمى الجدول [tranches]. وسيحتوي على أعمدة [id, limite, coeffR, coeffN].
  • [9,10]: [id] هو المفتاح الأساسي من النوع INTEGER وله السمة AUTO_INCREMENT [10]: سيتولى SGBD ملء هذا العمود عند إضافة أسطر.
  • الأعمدة [limite, coeffR, coeffN] هي من النوع DOUBLE.
  • [11,12]: تظهر الجدولة الجديدة في علامة التبويب [Schema Tables] في قاعدة البيانات.
  • [13,14]: لإدخال البيانات في الجدول
  • [15]: تم تشغيل [Query Browser]
  • [16]: تم إدخال البيانات والتحقق من صحتها للأعمدة [limite, coeffR, coeffN]. تم ملء العمود [id] بواسطة SGBD. تم التحقق من الصحة باستخدام [17].
  • دائمًا في [Query Browser] [18]، يتم تنفيذ [20] لطلب [19]. وهذا يخلق مستخدمًا 'admimpots' بكلمة مرور 'mdpimpots' ويمنحه جميع الامتيازات (grant all privileges) على جميع كائنات قاعدة البيانات bdimpots (on bdimpots.*). سيسمح لنا ذلك بالعمل على قاعدة البيانات [bdimpots] باستخدام المستخدم [admimpots] بدلاً من المسؤول [root].

9.8.2. حل Visual Studio

سنتبع النهج الذي تمت دراسته للإصدار 5 من التطبيق النموذجي (انظر الفقرة 6.4). سنقوم ببناء حل Visual Studio التالي تدريجيًا:

  • في [1]: يتكون الحل ImpotsV7 من ثلاثة مشاريع، واحد لكل طبقة من طبقات التطبيق الثلاث
  • في [2]: مشروع [dao] للطبقة [dao] التي ستستخدم الآن قاعدة بيانات
  • في [3]: مشروع [metier] للطبقة [metier]. نستعيد هنا الطبقة [metier] من الإصدار 5، الموصوفة في الفقرة 6.4.4.
  • في [4]: مشروع [ui] من الطبقة [ui]. نستأنف هنا الطبقة [ui] من الإصدار 6، الموصوفة في الفقرة 7.6.

نستند إلى ما تم إنجازه لاستعادة طبقتين مكتوبتين بالفعل، وهما الطبقتان [ui] و [metier]. وقد أصبح ذلك ممكنًا بفضل بنية الطبقات المختارة. ومع ذلك، سنحتاج إلى أكواد المصدر للطبقات [ui] و [metier]. ففي الواقع، لا يمكن الاكتفاء بـ DLL للطبقات. عندما تم إنشاء DLL للطبقة [metier] في الإصدار 5، كانت تعتمد على DLL للطبقة [dao]. تم تضمين هذه التبعية بشكل ثابت في DLL من الطبقة [metier] (اسم DLL من الطبقة [dao]، الإصدار، رمز الهوية، ...). وبالتالي، فإن DLL من الإصدار 5 [ImpotsV5-metier.dll] لا تقبل العمل إلا مع DLL [ImpotsV5-dao.dll] التي تم تجميعها معها. إذا تم تغيير DLL من الطبقة [dao]، فيجب إعادة تجميع الطبقة [metier] لإنشاء DLL جديدة لها. وينطبق الأمر نفسه على الطبقة [ui]. وبالتالي، لن يتم تعديل الطبقتين [ui] و [metier]، ولكن سيتم إعادة تجميعهما للعمل مع DLL من الطبقة الجديدة [dao].

9.8.3. الطبقة [dao]

مراجع المشروع (انظر [1] في المشروع)

  • nunit.framework: للاختبار NUnit
  • System.Configuration: لاستخدام ملف التكوين [App.config]
  • System.Data: لأننا نستخدم قاعدة بيانات.

الكيانات (انظر [2] في المشروع)

الفئات [TrancheImpot] و [ImpotException] هي تلك الموجودة في الإصدارات السابقة.

الطبقة [dao] (انظر [3] في المشروع)

لم تتغير واجهة [IImpotDao]:


using Entites;

namespace Dao {
    public interface IImpotDao {
        // شرائح الضريبة
        TrancheImpot[] TranchesImpot{get;}
    }
}

فئة التنفيذ [DataBaseImpot] لهذه الواجهة هي التالية:


using System;
using System.Collections.Generic;
using System.Data.Common;
using Entites;

namespace Dao {
    public class DataBaseImpot : IImpotDao {
        // شرائح الضريبة
        private TrancheImpot[] tranchesImpot;
        public TrancheImpot[] TranchesImpot { get { return tranchesImpot; } }

        // الشركة المصنعة
        public DataBaseImpot(string factory, string connectionString, string requête) {
            // المصنع: مصنع SGBD المستهدف
            // connectionString: سلسلة الاتصال بقاعدة شرائح الضرائب
            // نقوم بإدارة الاستثناءات المحتملة
            try {
                // يتم استرداد موصل عام لـ SGBD
                DbProviderFactory connecteur = DbProviderFactories.GetFactory(factory);
                using (DbConnection connexion = connecteur.CreateConnection()) {
                    // تكوين الاتصال
                    connexion.ConnectionString = connectionString;
                    // فتح الاتصال
                    connexion.Open();
                    // تكوين الأمر 
                    DbCommand sqlCommand = connecteur.CreateCommand();
                    sqlCommand.CommandText = requête;
                    sqlCommand.Connection = connexion;
                    // تنفيذ الطلب
                    List<TrancheImpot> listTrancheImpot = new List<TrancheImpot>();
                    using (DbDataReader reader = sqlCommand.ExecuteReader()) {
                        while (reader.Read()) {
                            // يتم إنشاء شريحة ضريبية جديدة
                            listTrancheImpot.Add(new TrancheImpot() { Limite = reader.GetDecimal(0), CoeffR = reader.GetDecimal(1), CoeffN = reader.GetDecimal(2) });
                        }
                    }
                    // وضع شرائح الضريبة في مثيلها
                    tranchesImpot = listTrancheImpot.ToArray();
                }
            } catch (Exception ex) {
                // تغليف الاستثناء في نوع ImpotException
                throw new ImpotException("Erreur de lecture des tranches d'impôt", ex) { Code = 101 };
            }

        }
    }
}
  • السطر 7: فئة [DataBaseImpot] تنفذ واجهة [IImpotDao].
  • السطر 10: تنفيذ طريقة [TranchesImpot] للواجهة. تكتفي هذه الطريقة بإرجاع مرجع إلى جدول شرائح الضريبة في السطر 9. سيتم إنشاء هذا الجدول بواسطة منشئ الفئة.
  • السطر 13: المنشئ. يستخدم موصلًا عامًا (انظر الفقرة 9.4.5) لاستغلال قاعدة بيانات شرائح الضرائب. يتلقى المنشئ ثلاثة معلمات:
    • اسم "المصنع" الذي سيطلب منه الفئات للاتصال بقاعدة البيانات، وإصدار أوامر SQL، واستخدام نتيجة أمر Select.
    • سلسلة الاتصال التي يجب أن يستخدمها للاتصال بقاعدة البيانات
    • الأمر SQL Select الذي يجب عليه تنفيذه للحصول على شرائح الضرائب.
  • السطر 19: يطلب موصلًا من "المصنع"
  • السطر 20: ينشئ اتصالاً باستخدام هذا الموصل. يتم إنشاؤه ولكنه ليس جاهزاً للعمل بعد
  • السطر 22: يتم تهيئة سلسلة اتصال الاتصال. يمكن الآن الاتصال.
  • السطر 24: يتم الاتصال
  • السطر 26: يطلب من الموصل كائن [DbCommand] لتنفيذ الأمر SQL
  • السطر 27: يحدد الأمر SQL المراد تنفيذه
  • السطر 28: يحدد الاتصال الذي سيتم التنفيذ عليه
  • السطر 30: يتم إنشاء قائمة [listTrancheImpot] من الكائنات من النوع [TrancheImpot] فارغة.
  • السطر 31: يتم تنفيذ الأمر SQL Select
  • الأسطر 32-35: يتم استغلال الكائن [DbDataReader] الناتج عن Select. تُستخدم كل سطر من جدول نتائج Select لإنشاء مثيل لكائن من النوع [TrancheImpot] الذي يُضاف إلى القائمة [listTrancheImpot].
  • السطر 38: يتم نقل قائمة الكائنات من النوع [TrancheImpot] إلى المصفوفة في السطر 9.
  • الأسطر 40-43: يتم تغليف أي استثناء محتمل في نوع [ImpotException] ويتم تعيين رمز الخطأ 101 (تعسفي) له.

الاختبار [Test1] (انظر [4] في المشروع)

تكتفي الفئة [Test1] بعرض شرائح الضريبة على الشاشة. وهي الفئة المستخدمة بالفعل في الإصدار 5 (الفقرة 6.4.3) باستثناء التعليمات التي تنشئ مثيل الطبقة [dao] (السطر 14).


using System;
using Dao;
using Entites;
using System.Configuration;

namespace Tests {
    class Test1 {
        static void Main() {

            // يتم إنشاء الطبقة [dao]
            IImpotDao dao = null;
            try {
                // إنشاء الطبقة [dao]
                dao = new DataBaseImpot(ConfigurationManager.AppSettings["factoryMySql5"], ConfigurationManager.ConnectionStrings["dbImpotsMySql5"].ConnectionString, ConfigurationManager.AppSettings["requete"]);
            } catch (ImpotException e) {
                // عرض خطأ
                string msg = e.InnerException == null ? null : String.Format(", Exception d'origine : {0}", e.InnerException.Message);
                Console.WriteLine("L'erreur suivante s'est produite : [Code={0},Message={1}{2}]", e.Code, e.Message, msg == null ? "" : msg);
                // إيقاف البرنامج
                Environment.Exit(1);
            }
            // يتم عرض شرائح الضريبة
            TrancheImpot[] tranchesImpot = dao.TranchesImpot;
            foreach (TrancheImpot t in tranchesImpot) {
                Console.WriteLine("{0}:{1}:{2}", t.Limite, t.CoeffR, t.CoeffN);
            }
        }
    }
}

يستخدم السطر 14 ملف التكوين [App.config] التالي:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <connectionStrings>
        <add name="dbImpotsMySql5" connectionString="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;" />
    </connectionStrings>
    <appSettings>
        <add key="requete" value="select limite, coeffr, coeffn from tranches"/>
        <add key="factoryMySql5" value="MySql.Data.MySqlClient"/>
    </appSettings>
</configuration>
  • السطر 4: سلسلة الاتصال بقاعدة البيانات MySQL5. تجدر الإشارة إلى أن المستخدم [admimpots] هو الذي سيقوم بإنشاء الاتصال.
  • السطر 8: "المصنع" للعمل مع SGBD MySQL5
  • السطر 7: الاستعلام SQL Select للحصول على شرائح الضريبة.

تم تكوين المشروع لتنفيذ [Test1.cs]:

Image

يؤدي تنفيذ الاختبار إلى النتائج التالية:

1
2
3
4
5
6
7
4962:0:0
8382:0,068:291,09
14753:0,191:1322,92
23888:0,283:2668,39
38868:0,374:4846,98
47932:0,426:6883,66
0:0,481:9505,54

الاختبار NUnit [NUnit1] (انظر [4] في المشروع)

اختبار الوحدة [NUnit1] هو الاختبار المستخدم بالفعل في الإصدار 5 (الفقرة 6.4.3) باستثناء التعليمات التي تنشئ الطبقة [dao] (السطر 16).


using System;
using System.Configuration;
using Dao;
using Entites;
using NUnit.Framework;

namespace Tests {
    [TestFixture]
    public class NUnit1 : AssertionHelper{
        // طبقة [dao] المراد اختبارها
        private IImpotDao dao;

        // المُنشئ
        public NUnit1() {
            // تهيئة الطبقة [dao]
            dao = new DataBaseImpot(ConfigurationManager.AppSettings["factoryMySql5"], ConfigurationManager.ConnectionStrings["dbImpotsMySql5"].ConnectionString, ConfigurationManager.AppSettings["requete"]);
        }

        // اختبار
        [Test]
        public void ShowTranchesImpot(){
            // يتم عرض شرائح الضريبة
            TrancheImpot[] tranchesImpot = dao.TranchesImpot;
            foreach (TrancheImpot t in tranchesImpot) {
                Console.WriteLine("{0}:{1}:{2}", t.Limite, t.CoeffR, t.CoeffN);
            }
            // بعض الاختبارات
            Expect(tranchesImpot.Length,EqualTo(7));
            Expect(tranchesImpot[2].Limite,EqualTo(14753).Within(1e-6));
            Expect(tranchesImpot[2].CoeffR, EqualTo(0.191).Within(1e-6));
            Expect(tranchesImpot[2].CoeffN, EqualTo(1322.92).Within(1e-6));
        }
    }
}

لتنفيذ هذا الاختبار الفردي، يجب أن يكون المشروع من النوع [Class Library]:

  • في [1]: تم تغيير طبيعة المشروع
  • إلى [2]: سيُسمى DLL الذي تم إنشاؤه بـ [ImpotsV7-dao.dll]
  • في [3]: بعد إنشاء (F6) المشروع، يحتوي المجلد [dao/bin/Release] على DLL و [ImpotsV7-dao.dll]. كما يحتوي على ملف التكوين [App.config] الذي تمت إعادة تسميته إلى [nom DLL].config. وهذا أمر معتاد في Visual Studio.

ثم يتم تحميل DLL و [ImpotsV7-dao.dll] في إطار العمل NUnit ويتم تنفيذهما:

  • في [1]: نجحت الاختبارات. نعتبر الآن الطبقة [dao] جاهزة للعمل. تحتوي DLL على جميع فئات المشروع بما في ذلك فئات الاختبار. هذه الفئات غير ضرورية. نقوم بإعادة بناء DLL لاستبعاد فئات الاختبار منها.
  • في [2]: تم استبعاد المجلد [tests] من المشروع
  • في [3]: المشروع الجديد. يتم إعادة إنشاء هذا المشروع بواسطة F6 من أجل إنشاء ملف DLL جديد. وهذا الملف DLL هو الذي ستستخدمه طبقات [metier] و [ui] في التطبيق.

9.8.4. الطبقة [metier]

  • في [1]، أصبح المشروع [metier] هو المشروع النشط للحل
  • في [2]: مراجع المشروع. تجدر الإشارة إلى المرجع على DLL للطبقة [dao] التي تم إنشاؤها مسبقًا. تم وصف إجراء إضافة المرجع هذا في الإصدار 5، في الفقرة 6.4.4.
  • في [3]: الطبقة [metier]. وهي الطبقة الخاصة بالإصدار 5، الموصوفة في الفقرة 6.4.4.

تم تكوين المشروع [metier] لإنشاء DLL:

  • [1]: المشروع من نوع "مكتبة الفئات"
  • [2]: سيؤدي إنشاء المشروع إلى إنتاج DLL [ImpotsV7-metier.dll] [3].

تم إنشاء المشروع (F6).

9.8.5. الطبقة [ui]

  • في [1]، أصبح المشروع [ui] هو المشروع النشط للحل
  • في [2]: مراجع المشروع. تجدر الإشارة إلى المراجع في DLL للطبقات [dao] و [metier].
  • في [3]: الطبقة [ui]. وهي طبقة الإصدار 6 الموصوفة في الفقرة 7.6.
  • في [4]، ملف التكوين [App.config] مشابه لملف الإصدار 6. ولا يختلف عنه إلا في الطريقة التي يتم بها إنشاء مثيل الطبقة [dao] بواسطة Spring:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
                <constructor-arg index="0" value="MySql.Data.MySqlClient"/>
                <constructor-arg index="1" value="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;"/>
                <constructor-arg index="2" value="select limite, coeffr, coeffn from tranches"/>
            </object>
            <object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
                <constructor-arg index="0" ref="dao"/>
            </object>
        </objects>
    </spring>
</configuration>
  • الأسطر 11-25: تكوين Spring
  • الأسطر 15-24: الكائنات التي تم إنشاء مثيل لها بواسطة Spring
  • الأسطر 16-20: إنشاء مثيل للطبقة [dao]
  • السطر 16: يتم إنشاء مثيل للطبقة [dao] بواسطة الفئة [Dao.DataBaseImpot] الموجودة في DLL [ImpotsV7-Dao]
  • الأسطر 17-19: المعلمات الثلاثة (مصنع SGBD المستخدم، سلسلة الاتصال، طلب SQL) التي يجب توفيرها لمُنشئ الفئة [Dao.DataBaseImpot]
  • الأسطر 21-23: إنشاء مثيل للطبقة [metier]. هذه هي نفس التهيئة الموجودة في الإصدار 6.

الاختبارات

تم تكوين المشروع [ui] على النحو التالي:

  • [1]: المشروع من نوع "تطبيق Windows"
  • [2]: سيؤدي إنشاء المشروع إلى إنتاج الملف القابل للتنفيذ [ImpotsV7-ui.exe]

يوجد مثال للتنفيذ في [3].

9.8.6. تغيير قاعدة البيانات

تمت كتابة الطبقة [dao] المذكورة أعلاه باستخدام موصل عام وقاعدة MySQL5. ونقترح هنا الانتقال إلى قاعدة SQL Server Compact لإثبات أن التغيير سيقتصر على التكوين فقط.

وستكون قاعدة SQL Server Compact كما يلي:

  • [1]: قاعدة البيانات [dbimpots.sdf] في عرض [DataBase Explorer] في Visual Studio [2]. تم إنشاؤها بدون كلمة مرور.
  • [3]: الجدول [data] الذي يحتوي على البيانات. تم اختيار أسماء مختلفة للجدول والأعمدة عن تلك المستخدمة مع قاعدة البيانات MySQL5 عن قصد للتأكيد مرة أخرى على أهمية وضع هذا النوع من التفاصيل في ملف التكوين بدلاً من الكود.
  • [4]: العمود [id] هو مفتاح أساسي وله السمة Identity: وهو SGBD الذي سيحدد قيمه.
  • [5]: محتوى الجدول [data].
  • [6]: تم وضع قاعدة البيانات [dbimpots.sdf] في مجلد مشروع [ui] ودمجها في هذا المشروع.
  • [7]: سيتم نسخ قاعدة البيانات [dbimpots.sdf] إلى مجلد تشغيل المشروع.

ملف التكوين [App.config] للقاعدة الجديدة هو التالي:


<?xml version="1.0" encoding="utf-8" ?>
<configuration>

    <configSections>
        <sectionGroup name="spring">
            <section name="context" type="Spring.Context.Support.ContextHandler, Spring.Core" />
            <section name="objects" type="Spring.Context.Support.DefaultSectionHandler, Spring.Core" />
        </sectionGroup>
    </configSections>

    <spring>
        <context>
            <resource uri="config://spring/objects" />
        </context>
        <objects xmlns="http://www.springframework.net">
            <!--
            <object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
                <constructor-arg index="0" value="MySql.Data.MySqlClient"/>
                <constructor-arg index="1" value="Server=localhost;Database=bdimpots;Uid=admimpots;Pwd=mdpimpots;"/>
                <constructor-arg index="2" value="select limite, coeffr, coeffn from tranches"/>
            </object>
            -->
            <object name="dao" type="Dao.DataBaseImpot, ImpotsV7-dao">
                <constructor-arg index="0" value="System.Data.SqlServerCe.3.5"/>
                <constructor-arg index="1" value="Data Source=|DataDirectory|\dbimpots.sdf;" />
                <constructor-arg index="2" value="select data1, data2, data3 from data"/>
            </object>
            <object name="metier" type="Metier.ImpotMetier, ImpotsV7-metier">
                <constructor-arg index="0" ref="dao"/>
            </object>
        </objects>
    </spring>
</configuration>
  • الأسطر 23-27: تكوين الطبقة [dao] لاستغلال قاعدة البيانات [dbimpots.sdf].

نتائج التنفيذ مطابقة للنتائج السابقة. تجدر الإشارة إلى فائدة استخدام موصل عام لجعل الطبقة [dao] غير متأثرة بتغيير SGBD. لكننا رأينا أن هذا الموصل لا يناسب جميع الحالات، لا سيما تلك التي تُستخدم فيها الاستعلامات المعلمة. وهناك حلول أخرى مثل تلك التي تم ذكرها، وهي أطر عمل تابعة لجهات خارجية للوصول إلى البيانات (Spring، iBatis، NHibernate، LINQ، ...).

9.9. لمزيد من المعلومات...

  • يتم عرض LINQ في العديد من المؤلفات، لا سيما في كتاب: C# 3.0 in a Nutshell، جوزيف وبن ألباهاري، دار نشر O'Reilly المذكورة سابقًا في مقدمة هذا المستند.
  • يتم عرض iBatis في الكتاب: iBatis in Action، كلينتون بيغين، دار نشر مانينغ
  • Nhibernate in Action من دار نشر Manning من المقرر صدوره في يوليو 2008

تتوفر كتيبات مرجعية لـ Spring و iBatis و NHibernate على مواقع هذه الأطر المختلفة.