Skip to content

2. الجزء 2

2.1. Introduction

سنبدأ بتذكير ما تم إنجازه في الجزء 1، ولا سيما بنية الطبقات الثلاث [web, domain, dao] المستخدمة. في الحل المقترح، كانت الطبقة [dao] طبقة اختبار: تم تنفيذ مصدر البيانات بواسطة كائن [ArrayList]. سنركز في هذه المقالة على الطبقة [dao]، حيث سنعرض مختلف طرق التنفيذ الممكنة لها عندما تكون البيانات موجودة في كائن SGBD.

الأدوات المستخدمة:

  • كائن SGBD لـ Firebird - انظر الفقرة 3.5 من الملحق.
  • SGBD MSDE (محرك بيانات Microsoft) - انظر الملحق الفقرة 3.12.
  • IBExpert، الإصدار الشخصي لإدارة SGBD Firebird بيانياً - انظر الفقرة 3.6 من الملحق.
  • EMS MS SQL Manager لإدارة SGBD MSDE - انظر الفقرة 3.14 من الملحق.
  • Ibatis SqlMap لطبقة الوصول إلى البيانات في SGBD - انظر الفقرة 2.5.6.2.

في تصنيف «مبتدئ - متوسط - متقدم»، يقع هذا المستند في الجزء [intermédiaire-avancé]. ويتطلب فهمه استيفاء شروط مسبقة متنوعة. ويمكن اكتساب بعض هذه الشروط من المستندات التي قمت بكتابتها. وفي هذه الحالة، أقوم بالإشارة إليها. ومن الواضح تمامًا أن هذا مجرد اقتراح، وأنه يمكن للقارئ استخدام المستندات المفضلة لديه.

  • لغة VB.net: [Introduction au langage VB.NET par l'exemple ]
  • البرمجة على الويب بلغة VB.net: [Développement WEB avec ASP.NET 1.1 ]
  • استخدام مكون Spring IoC: [Spring IoC pour .NET ]
  • وثائق Ibatis SqlMap: [http://prdownloads.sourceforge.net/ibatisnet/DevGuide.pdf?download]
  • وثائق Firebird: [http://firebird.sourceforge.net/pdfmanual/Firebird-1.5-QuickStart.pdf]
  • وثائق Spring.net: [http://www.springframework.net/documentation.html]

2.2. تطبيق webarticles - تذكير

نقدم هنا عناصر تطبيق الويب المبسط للتجارة الإلكترونية الذي تمت دراسته في الجزء الأول. يتيح هذا التطبيق لعملاء الويب:

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

2.2.1. طرق عرض التطبيق

فيما يلي طرق العرض المختلفة التي تظهر للمستخدم:

  • شاشة [ERREURS] التي تُبلغ عن أي خطأ في التطبيق

Image

2.2.2. البنية العامة للتطبيق

التطبيق الذي تم إنشاؤه في الجزء 1 هو بنية ثلاثية الطبقات:

  • وقد تم جعل الطبقات الثلاث مستقلة عن بعضها البعض بفضل استخدام الواجهات
  • تم تحقيق التكامل بين الطبقات المختلفة باستخدام Spring
  • كل طبقة لها مساحة أسماء منفصلة: web (طبقة UIdomain (طبقة الأعمال) و dao (طبقة الوصول إلى البيانات).

يتبع التطبيق بنية MVC (النموذج - العرض - وحدة التحكم). إذا عدنا إلى مخطط الطبقات أعلاه، فإن بنية MVC تندمج فيه على النحو التالي:

تتم معالجة طلب العميل وفقًا للخطوات التالية:

  1. يقدم العميل طلبًا إلى وحدة التحكم. وحدة التحكم هذه هي هنا صفحة .aspx تُسند إليها وظيفة محددة. فهي تستقبل جميع طلبات العملاء. وهي بمثابة بوابة الدخول إلى التطبيق. وهي تمثل الحرف C في MVC.
  2. يقوم وحدة التحكم بمعالجة هذا الطلب. وللقيام بذلك، قد يحتاج إلى مساعدة من طبقة الأعمال، وهو ما يُسمى بالنموذج M في بنية MVC.
  3. يتلقى وحدة التحكم ردًا من طبقة الأعمال. وقد تمت معالجة طلب العميل. وقد يؤدي ذلك إلى عدة ردود محتملة. ومن الأمثلة الشائعة على ذلك
    • صفحة أخطاء إذا تعذر معالجة الطلب بشكل صحيح
    • صفحة تأكيد في الحالات الأخرى
  4. يختار وحدة التحكم الرد (= العرض) الذي سيتم إرساله إلى العميل. وغالبًا ما يكون هذا الرد عبارة عن صفحة تحتوي على عناصر ديناميكية. وتقوم وحدة التحكم بتزويد العرض بهذه العناصر.
  5. يتم إرسال العرض إلى العميل. وهذا هو الحرف V في MVC.

2.2.3. القالب

يتكون النموذج M الخاص بـ MVC هنا من العناصر التالية:

  1. فئات الأعمال
  2. فئات الوصول إلى البيانات
  3. قاعدة البيانات

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

لا تحتوي قاعدة البيانات إلا على جدول واحد يسمى ARTICLES تم إنشاؤه باستخدام الأوامر التالية لـ SQL:

CREATE TABLE ARTICLES (
    ID            INTEGER NOT NULL,
    NOM           VARCHAR(20) NOT NULL,
    PRIX          NUMERIC(15,2) NOT NULL,
    STOCKACTUEL   INTEGER NOT NULL,
    STOCKMINIMUM  INTEGER NOT NULL
);
/* القيود */
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);
/* المفتاح الأساسي */
ALTER TABLE ARTICLES ADD CONSTRAINT PK_ARTICLES PRIMARY KEY (ID);
id
المفتاح الأساسي الذي يحدد هوية المنتج بشكل فريد
nom
اسم المنتج
prix
سعره
stockactuel
المخزون الحالي
stockminimum
المخزون الذي يجب عنده تقديم طلب إعادة التزويد

2.2.3.2. مساحات أسماء النموذج

يتم توفير النموذج M في شكل مساحتين للأسماء:

  • istia.st.articles.dao: يحتوي على فئات الوصول إلى البيانات الخاصة بالطبقة [dao]
  • istia.st.articles.domain: يحتوي على فئات الأعمال الخاصة بالطبقة [domain]

يتم تضمين كل مساحة أسماء من هذه المساحات ضمن ملف «تجميع» خاص بها:

assembly
contenu
rôle
webarticles-dao
- [IArticlesDao]: واجهة الوصول إلى الطبقة [dao]. وهي الواجهة الوحيدة التي تراها الطبقة [domain]. ولا ترى أي واجهة أخرى.
- [Article]: فئة تُعرّف المقالة
- [ArticlesDaoArrayList]: فئة لتنفيذ واجهة [IArticlesDao] باستخدام فئة [ArrayList]
طبقة الوصول إلى البيانات - تقع بالكامل في طبقة [dao] للبنية ثلاثية المستويات لتطبيق الويب
webarticles-domain
- [IArticlesDomain]: واجهة الوصول إلى الطبقة [domain]. وهي الواجهة الوحيدة التي تراها طبقة الويب. ولا ترى أي واجهة أخرى.
- [AchatsArticles]: فئة تُنفذ [IArticlesDomain]
- [Achat]: فئة تمثل عملية شراء أحد العملاء
- [Panier]: فئة تمثل مجموع مشتريات العميل
تمثل نموذج عمليات الشراء على الويب - وتقع بالكامل في الطبقة [domain] من بنية التطبيق الويب ثلاثية الطبقات

2.2.4. نشر واختبار التطبيق [webarticles]

2.2.4.1. النشر

نقوم بنشر التطبيق الذي تم تطويره في الجزء الأول من المقالة في مجلد يسمى [runtime]:

 

تعليقات:

يحتوي المجلد [runtime] على ثلاثة ملفات ومجلدين فرعيين:

  • وحدات التحكم [global.asax] و [main.aspx]
  • ملف التكوين [web.config]
  • المجلد [bin] الذي يحتوي على:
    • ملفات DLL الخاصة بالطبقات الثلاث [webarticles-dao.dll] و [webarticles-domain.dll] و [webarticles-web.dll]
    • الملفات اللازمة لـ Spring: [Spring-Core.*]، [log4net.dll]
  • المجلد [vues] الذي يحتوي على كود عرض الطرز المختلفة.
  • لا حاجة لوجود ملفات الكود .vb لأن نسختها المُجمَّعة موجودة في DLL.

2.2.4.2. الاختبارات

نقوم بتكوين خادم الويب [Cassini] بالطريقة التالية:

Image

مع:

المسار الفعلي: D:\data\serge\travail\2004-2005\aspnet\webarticles-010405\runtime\

المسار الافتراضي: /webarticles

باستخدام متصفح، نطلب الوصول إلى URL و [http://localhost/webarticles/main.aspx]

Image

تجدر الإشارة إلى أن الطبقة [dao] يتم تنفيذها بواسطة فئة تخزن المقالات في كائن [ArrayList]. تقوم هذه الفئة بإنشاء قائمة أولية مكونة من أربعة مقالات. انطلاقًا من العرض أعلاه، نستخدم روابط القائمة لإجراء العمليات. وفيما يلي بعض منها. يمثل العمود الأيسر طلب العميل، بينما يمثل العمود الأيمن الرد المقدم له.

2.2.5. إعادة النظر في الطبقة [dao]

في أول تطبيق لنا لطبقة [dao]، تم تنفيذ واجهة [IArticlesDao] للوصول إلى البيانات بواسطة فئة تخزن المقالات في كائن [ArrayList]. وقد سمح لنا ذلك بعدم الخوض في تفاصيل هذه الطبقة وإثبات أن ما يهم هو واجهتها فقط وليس تنفيذها. وبذلك تمكنا من بناء تطبيق ويب جاهز للعمل. ويتكون هذا التطبيق من ثلاث طبقات هي [web] و[domain] و[dao]. سنقترح هنا تطبيقات مختلفة للطبقة [dao]. ويمكن لكل منها أن تحل محل الطبقة الحالية [dao] دون إجراء أي تعديل على الطبقتين [domain] و [web]. ويتم تحقيق هذه المرونة للأسباب التالية:

  • الطبقة [domain] لا تستهدف فئة ملموسة بل واجهة [IArticlesDao]
  • بفضل Spring، تمكنا من إخفاء اسم فئة تنفيذ الواجهة [IArticlesDao] عن الطبقة [domain].

2.2.5.1. عناصر الطبقة [dao]

دعونا نستعرض بعض عناصر الطبقة [dao] التي سيتم الاحتفاظ بها في عمليات التنفيذ الجديدة:

  • - [IArticlesDao]: واجهة الوصول إلى الطبقة [dao]
  • - [Article]: فئة تُعرّف عنصرًا

2.2.5.2. الفئة [Article]

الفئة التي تُعرّف المقالة هي التالية:

Imports System

Namespace istia.st.articles.dao

    Public Class Article

         ' الحقول الخاصة
        Private _id As Integer
        Private _nom As String
        Private _prix As Double
        Private _stockactuel As Integer
        Private _stockminimum As Integer

         ' رقم المنتج
        Public Property id() As Integer
            Get
                Return _id
            End Get
            Set(ByVal Value As Integer)
                If Value <= 0 Then
                    Throw New Exception("Le champ id [" + Value.ToString + "] est invalide")
                End If
                Me._id = Value
            End Set
        End Property

         ' اسم المنتج
        Public Property nom() As String
            Get
                Return _nom
            End Get
            Set(ByVal Value As String)
                If Value Is Nothing OrElse Value.Trim.Equals("") Then
                    Throw New Exception("Le champ nom [" + Value + "] est invalide")
                End If
                Me._nom = Value
            End Set
        End Property

         ' سعر المنتج
        Public Property prix() As Double
            Get
                Return _prix
            End Get
            Set(ByVal Value As Double)
                If Value < 0 Then
                    Throw New Exception("Le champ prix [" + Value.ToString + "] est invalide")
                End If
                Me._prix = Value
            End Set
        End Property

         ' المخزون الحالي للمنتج
        Public Property stockactuel() As Integer
            Get
                Return _stockactuel
            End Get
            Set(ByVal Value As Integer)
                If Value < 0 Then
                    Throw New Exception("Le champ stockActuel [" + Value.ToString + "] est invalide")
                End If
                Me._stockactuel = Value
            End Set
        End Property

         ' الحد الأدنى لمخزون المنتج
        Public Property stockminimum() As Integer
            Get
                Return _stockminimum
            End Get
            Set(ByVal Value As Integer)
                If Value < 0 Then
                    Throw New Exception("Le champ stockMinimum [" + Value.ToString + "] est invalide")
                End If
                Me._stockminimum = Value
            End Set
        End Property

         ' الشركة المصنعة الافتراضية
        Public Sub New()
        End Sub

         ' الشركة المصنعة مع الخصائص
        Public Sub New(ByVal id As Integer, ByVal nom As String, ByVal prix As Double, ByVal stockactuel As Integer, ByVal stockminimum As Integer)
            Me.id = id
            Me.nom = nom
            Me.prix = prix
            Me.stockactuel = stockactuel
            Me.stockminimum = stockminimum
        End Sub

         ' طريقة تحديد المنتج
        Public Overrides Function ToString() As String
            Return "[" + id.ToString + "," + nom + "," + prix.ToString + "," + stockactuel.ToString + "," + stockminimum.ToString + "]"
        End Function
    End Class
End Namespace

توفر هذه الفئة:

  1. منشئًا يسمح بتحديد المعلومات الخمس الخاصة بمقال ما: [id, nom, prix, stockactuel, stockminimum]
  2. خصائص عامة تتيح قراءة وكتابة المعلومات الخمس.
  3. التحقق من البيانات التي تم إدخالها في العنصر. في حالة وجود بيانات خاطئة، يتم إصدار استثناء.
  4. طريقة toString التي تتيح الحصول على قيمة عنصر ما في شكل سلسلة أحرف. وغالبًا ما يكون ذلك مفيدًا لتصحيح أخطاء التطبيق.

2.2.5.3. الواجهة [IArticlesDao]

يتم تعريف الواجهة [IArticlesDao] على النحو التالي:

Imports System
Imports System.Collections

Namespace istia.st.articles.dao

    Public Interface IArticlesDao
         ' قائمة بجميع المنتجات
        Function getAllArticles() As IList
         ' إضافة عنصر
        Function ajouteArticle(ByVal unArticle As Article) As Integer
         ' حذف عنصر
        Function supprimeArticle(ByVal idArticle As Integer) As Integer
         ' تعديل منتج
        Function modifieArticle(ByVal unArticle As Article) As Integer
         ' البحث عن منتج
        Function getArticleById(ByVal idArticle As Integer) As Article
         ' حذف جميع المنتجات
        Sub clearAllArticles()
         ' يغير مخزون أحد المنتجات
        Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer
    End Interface
End Namespace

دور الطرق المختلفة للواجهة هو كما يلي:

getAllArticles
تُرجع جميع المقالات الموجودة في مصدر البيانات
clearAllArticles
إفراغ مصدر البيانات
getArticleById
يعرض الكائن [Article] المحدد برقمه
ajouteArticle
يسمح بإضافة عنصر إلى مصدر البيانات
modifieArticle
يسمح بتعديل عنصر من مصدر البيانات
supprimerArticle
يسمح بحذف عنصر من مصدر البيانات
changerStockArticle
يسمح بتعديل مخزون أحد العناصر في مصدر البيانات

توفر الواجهة لبرامج العملاء عددًا معينًا من الطرق المحددة فقط من خلال توقيعاتها. ولا تهتم بالكيفية التي سيتم بها تنفيذ هذه الطرق فعليًا. وهذا يوفر مرونة في التطبيق. حيث يقوم برنامج العميل بإجراء استدعاءاته على الواجهة وليس على تنفيذ محدد لها.

يتم اختيار تنفيذ محدد عن طريق ملف تكوين Spring.

2.3. فئة التنفيذ [ArticlesDaoPlainODBC]

نقدم تنفيذًا جديدًا للطبقة [dao] الذي يفترض أن البيانات موجودة في مصدر ODBC. من المعروف أنه في نظام Windows، تحتوي جميع أجهزة SGBD المتوفرة في السوق تقريبًا على برنامج تشغيل ODBC. وتكمن ميزة هذا الحل في إمكانية تغيير جهاز SGBD بطريقة شفافة بالنسبة للتطبيق. أما العيب فهو أن برنامج التشغيل ODBC الذي يستفيد فقط من الميزات المشتركة بين جميع ملفات SGBD يكون عادةً أقل أداءً من برنامج التشغيل المكتوب خصيصًا للاستفادة من كامل إمكانات ملف SGBD معين. يمكن الرجوع إلى الفقرة 3.7 للاطلاع على مثال لإنشاء مصدر ODBC.

2.3.1. الكود

2.3.1.1. الهيكل الأساسي

تقوم الفئة [ArticlesDaoPlainODBC] بتنفيذ واجهة [IArticlesDao] بالطريقة التالية:

Imports System
Imports System.Collections
Imports System.Data.Odbc

Namespace istia.st.articles.dao

    Public Class ArticlesDaoPlainODBC
        Implements istia.st.articles.dao.IArticlesDao

         ' الحقول الخاصة
        Private connexion As OdbcConnection = Nothing
        Private DSN As String
        Private insertCommand As OdbcCommand
        Private updatecommand As OdbcCommand
        Private deleteSomeCommand As OdbcCommand
        Private selectSomeCommand As OdbcCommand
        Private updateStockCommand As OdbcCommand
        Private deleteAllCommand As OdbcCommand
        Private selectAllCommand As OdbcCommand

         ' الشركة المصنعة
        Public Sub New(ByVal DSN As String, ByVal uid As String, ByVal password As String)
             ' DSN: اسم المصدر ODBC
             ' uid: معرّف المستخدم
             ' password: كلمة المرور الخاصة به
...
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
...
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
...
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
...
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
...
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
...
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
...
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
....
        End Function

        Private Function executeQuery(ByVal query As OdbcCommand) As IList
             ' تنفيذ استعلام SELECT 
....
        End Function

        Private Function executeUpdate(ByVal sqlCommand As OdbcCommand) As Integer
....
    End Class

End Namespace

تعليقات:

  • في السطر 3، يتم استيراد مساحة الأسماء التي تحتوي على الفئات .NET للوصول إلى المصادر ODBC
  • السطر 11 - سيخزن الاتصال بمصدر البيانات ODBC
  • السطر 12 - سيخزن اسم مصدر البيانات DSN
  • الأسطر 13-19 - متغيرات خاصة من النوع [OdbcCommand] تحدد الاستعلامات SQL التي تستخدمها الطرق المختلفة للفئة
  • الأسطر 22-27 - المنشئ. يتلقى العناصر التي تسمح له بإنشاء الكائن [OdbcConnection] الذي سيربط الكود بمصدر البيانات ODBC
  • الأسطر 29-31 - الطريقة الخاصة بإضافة عنصر
  • الأسطر 33-35 - الطريقة الخاصة بتغيير مخزون عنصر
  • الأسطر 37-39 - الطريقة التي تحذف جميع الأصناف من مصدر البيانات ODBC
  • الأسطر 41-43 - الطريقة التي تحصل على قائمة بجميع الأصناف من مصدر البيانات ODBC
  • الأسطر 45-47 - الطريقة التي تسمح بالحصول على سلعة معينة
  • الأسطر 49-51 - الطريقة التي تسمح بتعديل حقول معينة في عنصر ما إذا كان رقمه معروفًا
  • الأسطر 53-55 - الطريقة التي تسمح بحذف عنصر نعرف رقمه
  • الأسطر 57-60 - طريقة مساعدة تسمح بتنفيذ [SELECT] على مصدر البيانات وعرض النتيجة
  • الأسطر 62-64 - طريقة مساعدة تسمح بتنفيذ [INSERT, UPDATE, DELETE] على مصدر البيانات وعرض النتيجة

2.3.1.2. المُنشئ

         ' المنشئ
        Public Sub New(ByVal DSN As String, ByVal uid As String, ByVal password As String)
             ' DSN: اسم المصدر ODBC
             ' uid: هوية المستخدم
             ' password: كلمة المرور الخاصة به

             'يتم استرداد اسم قاعدة البيانات التي تم تمريرها كمعلمة
            Me.DSN = DSN
            Dim connectString As String = String.Format("DSN={0};UID={1};PASSWORD={2}", DSN, uid, password)
             'يتم إنشاء مثيل للاتصال
            connexion = New OdbcConnection(connectString)
             ' يتم إعداد الاستعلامات SQL
            insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
            updatecommand = New OdbcCommand("update ARTICLES set nom=?, prix=?, stockactuel=?, stockminimum=? where id=?", connexion)
            deleteSomeCommand = New OdbcCommand("delete from ARTICLES where id=?", connexion)
            selectSomeCommand = New OdbcCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=?", connexion)
            updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
            selectAllCommand = New OdbcCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New OdbcCommand("delete from ARTICLES", connexion)
        End Sub

تعليقات:

  • السطر 2 - يتلقى المنشئ المعلومات الثلاث التي يحتاجها للاتصال بمصدر ODBC: اسم المصدر DSN، والمعرف الذي يجب استخدامها للاتصال، وكلمة المرور المرتبطة به.
  • السطر 8 - يتم تخزين اسم المصدر DSN حتى يمكن إعادة ذكره في رسائل الأخطاء.
  • السطر 9 - يتم إنشاء مثيل للكائن [OdbcConnection]. إن إنشاء مثيل للاتصال لا يعني فتحه. فالطريقة [open] هي التي تقوم بفتح الاتصال.
  • الأسطر 12-19 - يتم إعداد الاستعلامات SQL ضمن الكائنات [OdbcCommand]. وهذا سيوفر علينا إعادة إنشائها في كل مرة نحتاج إليها. وسيتم استبدال المعلمات الشكلية «?» في الاستعلامات بقيم فعلية عند تنفيذ الاستعلام.

2.3.1.3. الطريقة executeQuery

        Private Function executeQuery(ByVal query As OdbcCommand) As IList
             ' تنفيذ استعلام SELECT 
             ' إعلان الكائن الذي يتيح الوصول إلى جميع صفوف جدول النتائج
            Dim myReader As OdbcDataReader = Nothing
            Try
                 'إنشاء اتصال بـ BDD
                connexion.Open()
                 'يتم تنفيذ الاستعلام
                myReader = query.ExecuteReader()
                 'يتم تعريف قائمة العناصر لإرجاعها لاحقًا
                Dim articles As IList = New ArrayList
                Dim unArticle As Article
                While myReader.Read()
                     'يتم إعداد عنصر باستخدام قيم القارئ
                    unArticle = New Article
                    unArticle.id = myReader.GetInt32(0)
                    unArticle.nom = myReader.GetString(1)
                    unArticle.prix = myReader.GetDouble(2)
                    unArticle.stockactuel = myReader.GetInt32(3)
                    unArticle.stockminimum = myReader.GetInt32(4)
                     ': تتم إضافة العنصر إلى القائمة
                    articles.Add(unArticle)
                End While
                 'يتم إرجاع النتيجة
                Return articles
            Finally
                 ' تحرير الموارد
                If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function

تعليقات:

  • الطريقة [executeQuery] هي طريقة مساعدة تقوم بما يلي:
    • تنفذ استعلامًا [SELECT id, nom, prix, stockactuel, stockminimum from ARTICLES ...] على مصدر البيانات
    • تُرجع النتيجة في شكل قائمة من الكائنات [Article]
  • السطر 1 - المعلمة الوحيدة للطريقة هي الكائن [OdbcCommand] الذي يحتوي على الاستعلام [Select] المراد تنفيذه.
  • السطر 7 - يتم فتح الاتصال. وسيتم إغلاقه في السطر 29 سواء حدث خطأ أم لا.
  • السطر 9 - يتم إنشاء مثيل للكائن [OdbcDataReader] اللازم لمعالجة نتيجة [Select]
  • الأسطر 13-23 - يتم وضع كل سطر من نتائج [Select] في كائن [Article] الذي سينضم إلى العناصر الأخرى في كائن [ArrayList]
  • يتم عرض قائمة العناصر في السطر 25
  • لم تتم معالجة أي استثناءات. يجب معالجتها بواسطة الكود الذي يستدعي هذه الطريقة.

2.3.1.4. الطريقة executeUpdate

        Private Function executeUpdate(ByVal sqlCommand As OdbcCommand) As Integer
             ' تنفيذ طلب التحديث
            Try
                 'إنشاء اتصال بـ BDD
                connexion.Open()
                 'تنفيذ الاستعلام
                Return sqlCommand.ExecuteNonQuery()
            Finally
                 ' تحرير الموارد
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function
    End Class

تعليقات:

  • تستقبل الطريقة كائنًا [OdbcCommand] يحتوي على استعلام SQL من النوع [Insert, Update, Delete].
  • يتم فتح الاتصال في السطر 5. وسيتم إغلاقه في السطر 10 سواء حدث استثناء أم لا.
  • يتم تنفيذ طلب التحديث في السطر 7. ويتم إرجاع النتيجة على الفور، وهي عدد الأسطر في الجدول ARTICLES التي تم تعديلها بواسطة الطلب.

2.3.1.5. الطريقة ajouteArticle

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب الإدراج
                With insertCommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("id", unArticle.id))
                    .Add(New OdbcParameter("nom", unArticle.nom))
                    .Add(New OdbcParameter("prix", unArticle.prix))
                    .Add(New OdbcParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OdbcParameter("stockminimum", unArticle.stockminimum))
                End With
                Try
                     'يتم تنفيذها
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function

تعليقات:

  • السطر 1 - تتلقى الطريقة العنصر المراد إضافته إلى مصدر البيانات ODBC. وتُرجع عدد الأسطر المتأثرة بهذه العملية، c.a.d. 1 أو 0
  • السطران 3 و20 - الدالة متزامنة. وسيكون هذا هو الحال بالنسبة لجميع دوال الوصول إلى البيانات. وهذا يعني أنه لن يتمكن سوى خيط واحد في كل مرة من العمل على مصدر البيانات. وربما يكون هذا نهجًا متحفظًا للغاية. فهناك بدائل أفضل، لا سيما تضمين هذه العمليات في معاملات. في هذه الحالة، يتولى SGBD إدارة عمليات الوصول المتزامنة. لم نرغب في إدخال مفهوم المعاملات في الوقت الحالي. يوفر لنا Spring إمكانية إدخالها في الطبقة [domain]. ربما تتاح لنا الفرصة للعودة إلى هذا الموضوع في مقال آخر.
  • في الأسطر 5-12، يتم تعيين قيم للمعلمات الشكلية لطلب الكائن [insertCommand] الذي تم تهيئته بواسطة المنشئ. وللتذكير، هذا هو الطلب:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)

يتم توفير القيم الخمس اللازمة للاستعلام في الأسطر 7-11.

  • في الأسطر 13-19، يتم تنفيذ الاستعلام. إذا سار الأمر على ما يرام، يتم إرجاع النتيجة. وإلا، يتم إلقاء استثناء عام مع رسالة خطأ واضحة

2.3.1.6. الطريقة modifieArticle

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب التحديث
                With updatecommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("nom", unArticle.nom))
                    .Add(New OdbcParameter("prix", unArticle.prix))
                    .Add(New OdbcParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OdbcParameter("stockminimum", unArticle.stockminimum))
                    .Add(New OdbcParameter("id", unArticle.id))
                End With
                 ' يتم تنفيذها
                Try
                     'يتم تنفيذ طلب الإدراج
                    Return executeUpdate(updatecommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
                End Try
            End SyncLock
        End Function

تعليقات:

  • السطر 1 - تتلقى الطريقة المقالة المراد تعديلها من مصدر البيانات ODBC. وتُرجع عدد الأسطر المتأثرة بهذه العملية، c.a.d. 1 أو 0
  • يمكن إدراج تعليقات الدالة [ajouteArticle] هنا

2.3.1.7. الطريقة supprimeArticle

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب الحذف
                With deleteSomeCommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("id", idArticle))
                End With
                 'يتم تنفيذه
                Try
                     'يتم تنفيذ طلب الحذف
                    Return executeUpdate(deleteSomeCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

تعليقات:

  • السطر 1 - تتلقى الطريقة رقم العنصر المراد حذفه من مصدر البيانات ODBC. وتُرجع عدد الأسطر المتأثرة بهذه العملية، c.a.d. 1 أو 0
  • يمكن إدراج تعليقات الدالة [ajouteArticle] هنا

2.3.1.8. الدالة getAllArticles

            Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             ' قسم حصري
            SyncLock Me
                Try
                     'يتم تنفيذ استعلام الاختيار
                    Dim articles As IList = executeQuery(selectAllCommand)
                     'يتم إرجاع القائمة
                    Return articles
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
                End Try
            End SyncLock
        End Function

تعليقات:

  • السطر 1 - لا تتلقى الطريقة أي معلمات. وتُرجع قائمة بجميع العناصر الموجودة في مصدر البيانات ODBC
  • يتم توجيه الاستعلام [Select] الذي يطلب جميع العناصر إلى الطريقة [executeQuery] - السطر 6
  • يتم إرجاع القائمة التي تم الحصول عليها في السطر 8
  • في الأسطر 9-12، يتم التعامل مع أي استثناء محتمل

2.3.1.9. الطريقة getArticleById

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد استعلام select
                With selectSomeCommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("id", idArticle))
                End With
                 'يتم تنفيذها
                Try
                     'يتم تنفيذ الاستعلام
                    Dim articles As IList = executeQuery(selectSomeCommand)
                     'يتم التحقق مما إذا تم العثور على المقالة
                    If articles.Count = 0 Then Return Nothing
                     'يتم إرجاع المقالة
                    Return CType(articles.Item(0), Article)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

تعليقات:

  • السطر 1 - تتلقى الطريقة رقم المادة المطلوبة كمعلمة. وتُرجع هذا الرقم إذا تم العثور عليه في المصدر ODBC، وإلا فإنها تُرجع المرجع [nothing].
  • يتم تهيئة الاستعلام [Select] الذي يطلب المادة في الأسطر 5-8
  • ويتم تنفيذها في السطر 12 - ونحصل على قائمة بالمواد
  • إذا كانت هذه القائمة فارغة، يتم إرجاع المرجع [nothing] في السطر 14
  • وإلا يتم عرض العنصر الوحيد في القائمة في السطر 16
  • في الأسطر 17-20، تتم معالجة أي استثناء محتمل

2.3.1.10. الطريقة clearAllArticles

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' قسم حصري
            SyncLock Me
                Try
                     'يتم تنفيذ طلب الحذف
                    executeUpdate(deleteAllCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
                End Try
            End SyncLock
        End Sub

تعليقات:

  • السطر 1 - لا تتلقى الطريقة أي معلمات ولا تُرجع أي قيمة
  • السطر 6 - يتم تنفيذ طلب حذف جميع المقالات
  • الأسطر 7-10: يتم التعامل مع أي استثناء محتمل

2.3.1.11. الدالة changerStockArticle

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب تحديث المخزون
                With updateStockCommand.Parameters
                    .Clear()
                    .Add(New OdbcParameter("mvt1", mouvement))
                    .Add(New OdbcParameter("id", idArticle))
                    .Add(New OdbcParameter("mvt2", mouvement))
                End With
                 'يتم تنفيذها
                Try
                    Return executeUpdate(updateStockCommand)
                Catch ex As Exception
                     'خطأ في الطلب
                    Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
                End Try
            End SyncLock
        End Function

تعليقات:

  • السطر 1 - تتلقى الطريقة كمعلمات رقم المادة التي يجب تعديل مخزونها بالإضافة إلى مقدار الزيادة أو النقصان في المخزون (موجبًا أو سالبًا). وتُرجع الطريقة عدد الأسطر التي تم تعديلها بواسطة العملية c.a.d. 0 أو 1.
  • الأسطر 5-10، يتم تهيئة الاستعلام [updateStockCommand]. نذكر نص الاستعلام SQL:
            updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)

يُلاحظ أن المخزون لا يتم تعديله إلا إذا ظل >=0 بعد التعديل.

  • يتم تنفيذ طلب تحديث مخزون المادة في السطر 13 ويتم عرض النتيجة
  • في الأسطر 14-18، يتم التعامل مع أي استثناء محتمل

2.3.2. إنشاء ملف التجميع الخاص بطبقة [dao]

يتميز مشروع Visual Studio الخاص بهذا الإصدار الجديد من الطبقة [dao] بالهيكل التالي:

Image

تم تكوين المشروع لإنشاء ملف DLL يُسمى [webarticles-dao.dll]:

2.3.3. اختبارات Nunit للطبقة [dao]

2.3.3.1. إنشاء مصدر بيانات باستخدام ODBC-Firebird

لاختبار الطبقة الجديدة [dao]، نحتاج إلى مصدر بيانات ODBC، وبالتالي إلى قاعدة بيانات. نستخدم قاعدة بيانات Firebird SGBD (الفقرة 3.5). باستخدام IBExpert (الفقرة 3.6)، نقوم بإنشاء قاعدة البيانات التالية:

سيكون المسؤول عن هذه القاعدة هو المستخدم [SYSDBA] بكلمة المرور [masterkey]. نقوم بإنشاء بعض المقالات:

Image

نقوم الآن بإنشاء مصدر Firebird التالي ODBC (انظر الفقرة 3.7):

يتميز الملف المصدر ODBC الذي تم إنشاؤه بالخصائص التالية:

  • الاسم DSN: odbc-firebird-articles
  • معرف الاتصال: SYSDBA
  • كلمة المرور المرتبطة: masterkey

2.3.3.2. فئة الاختبار NUnit

لقد كتبنا بالفعل فئة اختبار لطبقة [dao] التي تم إنشاؤها في البداية. إذا كان القارئ يتذكر، فإن هذه الفئة لم تكن تختبر فئة محددة بل واجهة [IArticlesDao]:

Imports System
Imports System.Collections
Imports NUnit.Framework
Imports istia.st.articles.dao
Imports System.Threading
Imports Spring.Objects.Factory.Xml
Imports System.IO

Namespace istia.st.articles.tests

    <TestFixture()> _
    Public Class NunitTestArticlesArrayList
         ' الكائن المراد اختباره
        Private articlesDao As IArticlesDao

        <SetUp()> _
        Public Sub init()
             ' يتم استرداد مثيل من مُنشئ الكائنات Spring
            Dim factory As XmlObjectFactory = New XmlObjectFactory(New FileStream("spring-config.xml", FileMode.Open))
             ' طلب إنشاء مثيل للكائن articlesdao
            articlesDao = CType(factory.GetObject("articlesdao"), IArticlesDao)
        End Sub

....

نلاحظ أنه في طريقة السمة <Setup()>، يُطلب من Spring مرجعًا إلى العنصر الفردي المسمى [articlesdao] من النوع [IArticlesDao]، أي من نوع الواجهة. تم تعريف الكائن الفردي [articlesdao] بواسطة ملف التكوين [spring-config.xml] التالي:

<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoArrayList, webarticles-dao"/>
</objects>

دعونا نثبت أن فئة الاختبار الأولية تسمح لنا باختبار الطبقة الجديدة [dao] دون الحاجة إلى إجراء أي تعديلات أو إعادة تجميع.

  • لنقم بإنشاء المجلد [tests] (على اليمين أدناه) عن طريق نسخ المجلد [bin] من مشروع اختبار الطبقة الأولية [dao] (على اليسار أدناه). وإذا لزم الأمر، يُرجى من القارئ مراجعة مشروع الاختبار الخاص بالإصدار الأول للطبقة [dao] في الجزء الأول من المقالة.
  • في المجلد [tests]، نستبدل DLL و [webarticles-dao.dll] المستمدتين منالطبقة القديمة [dao] بـ DLL و [webarticles-dao.dll] المستمدة من الطبقة الجديدة [dao]
  • نقوم بتعديل ملف التكوين [spring-config.xml] من أجل إنشاء مثيل للفئة الجديدة [ArticlesDaoPlainODBC]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">

<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, webarticles-dao">
        <constructor-arg index="0">
            <value>odbc-firebird-articles</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>SYSDBA</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>masterkey</value>
        </constructor-arg>
    </object>
</objects>

تعليقات:

  • في السطر 6، أصبح الكائن [articlesdao] مرتبطًا الآن بمثيل للفئة [ ArticlesDaoPlainODBC]
  • تحتوي هذه الفئة على منشئ بثلاثة معلمات:
  • اسم المصدر DSN - السطر 8
  • الهوية التي سيتم من خلالها الوصول إلى قاعدة البيانات - السطر 11
  • كلمة المرور المرتبطة بهذه الهوية - السطر 14

نستخدم هنا المعلومات المأخوذة من مصدر ODBC-Firebird الذي أنشأناه سابقًا.

2.3.3.3. الاختبارات

نحن الآن جاهزون لإجراء الاختبارات. باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل ملف DLL و [test-webarticles-dao.dll] من المجلد [tests] المذكور أعلاه ونقوم بتنفيذ الاختبار [testGetAllArticles]:

Image

بالنظر إلى لقطة الشاشة أعلاه، قد نأسف على الاسم [NUnitTestArticlesDaoArrayList] الذي أُطلق في البداية على فئة الاختبار. فهذا يسبب الالتباس. فالفئة التي يتم اختبارها هنا هي في الواقع [ArticlesDaoPlainODBC]. تُظهر لقطة الشاشة أننا استرجعنا بشكل صحيح السجلات التي كنا قد وضعناها في الجدول [ARTICLES]. والآن، دعونا نجري جميع الاختبارات:

Image

في النافذة اليسرى، تظهر قائمة بالطرق التي تم اختبارها. يشير لون النقطة الموجودة قبل اسم كل طريقة إلى نجاح (أخضر) أو فشل (أحمر) تلك الطريقة. وسيلاحظ القارئ الذي يقرأ هذا المستند على الشاشة أن جميع الاختبارات قد نجحت.

2.3.3.4. الخلاصة

لقد أوضحنا للتو أن:

  • لأن فئة الاختبار NUnit لم تكن تشير إلى فئة بل إلى واجهة؛
  • لأن الاسم الدقيق لفئة إنشاء مثيل الواجهة تم توفيره في ملف التكوين وليس في الكود؛
  • لأن Spring تولى إنشاء مثيل للفئة وتزويد كود الاختبار بمرجع لها؛

لذلك ظل كود الاختبار المكتوب للطبقة الأولية [dao] صالحًا لتنفيذ جديد لنفس هذه الطبقة. لم نكن بحاجة إلى الوصول إلى كود فئة الاختبار. ولم نستخدم سوى نسختها المُجمَّعة، تلك التي تم إنشاؤها أثناء اختبار الطبقة الأولية [dao]. وسنتوصل إلى استنتاجات مماثلة عندما يحين وقت دمج الطبقة الجديدة [dao] في التطبيق [webarticles].

2.3.4. دمج الطبقة الجديدة [dao] في التطبيق [webarticles]

2.3.4.1. اختبارات التكامل

تجدر الإشارة إلى أن الإصدار الأولي للتطبيق [webarticles] قد تم نشره في المجلد [runtime] التالي:

 

يُرجى من القارئ الرجوع، إذا لزم الأمر، إلى الفقرة 2.2.4 التي توضح تفاصيل إجراءات نشر التطبيق [webarticles]. نقوم بإجراء التعديلات التالية على محتوى المجلد [runtime]:

  • في المجلد [bin]، تم استبدال ملف DLL التابع للطبقة القديمة [dao] بملف DLL التابع للطبقة الجديدة [dao]
  • في [runtime]، يتم استبدال ملف التكوين [web.config] بملف يأخذ في الاعتبار فئة التنفيذ الجديدة للطبقة [dao]:

ملف التكوين الجديد [web.config] هو كما يلي:

<?xml version="1.0" encoding="iso-8859-1" ?>
<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 type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoPlainODBC, webarticles-dao">
                <constructor-arg index="0">
                    <value>odbc-firebird-articles</value>
                </constructor-arg>
                <constructor-arg index="1">
                    <value>SYSDBA</value>
                </constructor-arg>
                <constructor-arg index="2">
                    <value>masterkey</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

تعليقات:

  • تربط الأسطر 14-24 الكائن الفردي [articlesDao] بمثيل من الفئة الجديدة [ArticlesDaoPlainODBC]. هذا هو التعديل الوحيد. وقد صادفناه بالفعل أثناء اختبار الطبقة الجديدة [dao].

نحن جاهزون لإجراء الاختبارات. نقوم بتكوين خادم الويب [Cassini] بنفس الطريقة الموضحة في الفقرة 2.2.4. نقوم بتهيئة جدول المقالات [Firebird] بالقيم التالية:

Image

تأكد من تشغيل خادم الويب Cassini بالإضافة إلى SGBD و [Firebird]. باستخدام متصفح، نطلب عنوان URL [http://localhost/webarticles/main.aspx]:

Image

الآن دعونا نتحقق من محتوى الجدول [ARTICLES] في قاعدة البيانات [Firebird]:

Image

تم شراء الصنفين [parapluie] و [bottes]، وتم خصم الكمية المشتراة من مخزونهما. لم يتم شراء الصنف [chapeau] لأن الكمية المطلوبة تجاوزت الكمية المتوفرة في المخزون. ندعو القارئ إلى إجراء اختبارات إضافية.

2.3.4.2. الخلاصة

ماذا فعلنا؟

  • استخدمنا إصدار النشر الخاص بالإصدار القديم؛
  • وقمنا باستبدال المنتج DLL من الطبقة [dao] بإصدار جديد. بقيت ملفات DLL الخاصة بطبقتي [web] و [domain] دون تغيير؛
  • وقمنا بتعديل ملف التكوين [web.config] بحيث يأخذ في الاعتبار فئة التنفيذ الجديدة للطبقة [dao]

كل هذا منظم ويوفر سهولة كبيرة في تطوير تطبيق الويب. وتوفر لنا هذان الخياران المعماريان هذه الميزات المهمة:

  • الوصول إلى الطبقات عبر واجهات
  • تكامل الطبقات وتكوينها بواسطة Spring.

ونقترح الآن تنفيذًا جديدًا للطبقة [dao].

2.4. فئة التنفيذ [ArticlesDaoSqlServer]

يفترض التنفيذ الثاني للطبقة [dao] أن البيانات موجودة في قاعدة بيانات SQL Server. توفر Microsoft خادمًا يُسمى MSDE، وهو نسخة محدودة من خادم SQL. يمكن الاطلاع على كيفية الحصول عليه وتثبيته في الملحق، الفقرة 3.12.

2.4.1. الكود

تشبه الفئة [ArticlesDaoSqlServer] إلى حد كبير الفئة [ArticlesDaoPlainODBC] التي تمت دراستها سابقًا. ولذلك سنكتفي بالإشارة إلى التغييرات التي أُجريت على الإصدار السابق:

  • توجد الفئات الضرورية في مساحة الأسماء [System.Data.SqlClient] بدلاً من مساحة الأسماء [System.Data.Odbc]
  • أصبح اتصال النوع [OdbcConnection] الآن من النوع [SqlConnection]
  • أصبحت كائنات [OdbcCommand] الآن من النوع [SqlCommand]
  • تتغير صيغة الاستعلامات المُحدَّدة المعلمات من النوع SQL. وبذلك يصبح استعلام الإدراج كما يلي:
insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)

بينما كانت سابقًا:

insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
  • تصبح الطريقة [ajouteArticle] كما يلي:
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد استعلام الإدراج
                With insertCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@id", unArticle.id))
                    .Add(New SqlParameter("@nom", unArticle.nom))
                    .Add(New SqlParameter("@prix", unArticle.prix))
                    .Add(New SqlParameter("@sa", unArticle.stockactuel))
                    .Add(New SqlParameter("@sm", unArticle.stockminimum))
                End With
                Try
                     'يتم تنفيذه
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function
  • تم تعديل المصنع أيضًا:
        Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
             ' الخادم: اسم مثيل الخادم SQL المطلوب الوصول إليه
             ' databaseName: اسم قاعدة البيانات المطلوب الوصول إليها
             ' uid: هوية المستخدم
             ' password: كلمة المرور الخاصة به

             'يتم استرداد اسم قاعدة البيانات التي تم تمريرها كمعلمة
            Me.databaseName = databaseName
             'يتم إنشاء مثيل للاتصال
            Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
            connexion = New SqlConnection(connectString)
             ' يتم إعداد الاستعلامات SQL
            insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
...
        End Sub

يستقبل المُنشئ الآن أربعة معلمات:

             ' الخادم: اسم مثيل SQL الذي يجب الوصول إليه
             ' databaseName: اسم قاعدة البيانات المطلوب الوصول إليها
             ' uid: هوية المستخدم
             ' password: كلمة المرور الخاصة به

الرمز الكامل للفئة [ArticlesDaoSqlServer] هو كما يلي:

Imports System
Imports System.Collections
Imports System.Data.SqlClient

Namespace istia.st.articles.dao

    Public Class ArticlesDaoSqlServer
        Implements istia.st.articles.dao.IArticlesDao

         ' الحقول الخاصة
        Private connexion As SqlConnection = Nothing
        Private databaseName As String
        Private insertCommand As SqlCommand
        Private updatecommand As SqlCommand
        Private deleteSomeCommand As SqlCommand
        Private selectSomeCommand As SqlCommand
        Private updateStockCommand As SqlCommand
        Private deleteAllCommand As SqlCommand
        Private selectAllCommand As SqlCommand

         ' المُنشئ
        Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
             ' الخادم: اسم مثيل الخادم SQL المطلوب الوصول إليه
             ' databaseName: اسم قاعدة البيانات المطلوب الوصول إليها
             ' uid: هوية المستخدم
             ' password: كلمة المرور الخاصة به

             'يتم استرداد اسم قاعدة البيانات التي تم تمريرها كمعلمة
            Me.databaseName = databaseName
             'يتم إنشاء اتصال
            Dim connectString As String = String.Format("Data Source={0};Initial Catalog={1};UID={2};PASSWORD={3}", serveur, databaseName, uid, password)
            connexion = New SqlConnection(connectString)
             ' يتم إعداد الاستعلامات SQL
            insertCommand = New SqlCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
            updatecommand = New SqlCommand("update ARTICLES set nom=@nom, prix=@prix, stockactuel=@sa, stockminimum=@sm where id=@id", connexion)
            deleteSomeCommand = New SqlCommand("delete from ARTICLES where id=@id", connexion)
            selectSomeCommand = New SqlCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=@id", connexion)
            updateStockCommand = New SqlCommand("update ARTICLES set stockactuel=stockactuel+@mvt where id=@id and (stockactuel+@mvt)>=0", connexion)
            selectAllCommand = New SqlCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New SqlCommand("delete from ARTICLES", connexion)
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد استعلام الإدراج
                With insertCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@id", unArticle.id))
                    .Add(New SqlParameter("@nom", unArticle.nom))
                    .Add(New SqlParameter("@prix", unArticle.prix))
                    .Add(New SqlParameter("@sa", unArticle.stockactuel))
                    .Add(New SqlParameter("@sm", unArticle.stockminimum))
                End With
                Try
                     'يتم تنفيذها
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب تحديث المخزون
                With updateStockCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@mvt", mouvement))
                    .Add(New SqlParameter("@id", idArticle))
                End With
                 'يتم تنفيذه
                Try
                    Return executeUpdate(updateStockCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' قسم حصري
            SyncLock Me
                Try
                     'يتم تنفيذ طلب الإدراج
                    executeUpdate(deleteAllCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
                End Try
            End SyncLock
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             ' قسم حصري
            SyncLock Me
                Try
                     'يتم تنفيذ استعلام الاختيار
                    Dim articles As IList = executeQuery(selectAllCommand)
                     'يتم إرجاع القائمة
                    Return articles
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد استعلام select
                With selectSomeCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@id", idArticle))
                End With
                 'يتم تنفيذها
                Try
                     'يتم تنفيذ الاستعلام
                    Dim articles As IList = executeQuery(selectSomeCommand)
                     'يتم التحقق مما إذا تم العثور على المقالة
                    If articles.Count = 0 Then Return Nothing
                     'يتم إرجاع المقالة
                    Return CType(articles.Item(0), Article)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب التحديث
                With updatecommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@nom", unArticle.nom))
                    .Add(New SqlParameter("@prix", unArticle.prix))
                    .Add(New SqlParameter("@sa", unArticle.stockactuel))
                    .Add(New SqlParameter("@sm", unArticle.stockminimum))
                    .Add(New SqlParameter("@id", unArticle.id))
                End With
                 ' يتم تنفيذه
                Try
                     'يتم تنفيذ طلب الإدراج
                    Return executeUpdate(updatecommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
                End Try
            End SyncLock
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب الحذف
                With deleteSomeCommand.Parameters
                    .Clear()
                    .Add(New SqlParameter("@id", idArticle))
                End With
                 'يتم تنفيذه
                Try
                     'يتم تنفيذ طلب الحذف
                    Return executeUpdate(deleteSomeCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Private Function executeQuery(ByVal query As SqlCommand) As IList
             ' تنفيذ استعلام SELECT 
             ' إعلان الكائن الذي يسمح بالوصول إلى جميع صفوف جدول النتائج
            Dim myReader As SqlDataReader = Nothing
            Try
                 'إنشاء اتصال بـ BDD
                connexion.Open()
                 'يتم تنفيذ الاستعلام
                myReader = query.ExecuteReader()
                 'يتم تعريف قائمة العناصر لإرجاعها لاحقًا
                Dim articles As IList = New ArrayList
                Dim unArticle As Article
                While myReader.Read()
                     'يتم إعداد عنصر بقيم القارئ
                    unArticle = New Article
                    unArticle.id = myReader.GetInt32(0)
                    unArticle.nom = myReader.GetString(1)
                    unArticle.prix = myReader.GetDouble(2)
                    unArticle.stockactuel = myReader.GetInt32(3)
                    unArticle.stockminimum = myReader.GetInt32(4)
                     ': تتم إضافة العنصر إلى القائمة
                    articles.Add(unArticle)
                End While
                 'يتم إرجاع النتيجة
                Return articles
            Finally
                 ' تحرير الموارد
                If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function

        Private Function executeUpdate(ByVal updateCommand As SqlCommand) As Integer
             ' تنفيذ طلب التحديث
            Try
                 'إنشاء اتصال بـ BDD
                connexion.Open()
                 'تنفيذ الطلب
                Return updateCommand.ExecuteNonQuery()
            Finally
                 ' تحرير الموارد
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function
    End Class

End Namespace

يُرجى من القارئ قراءة هذا الرمز في ضوء التعليقات التي سبق ذكرها بشأن الفئة [ArticlesDaoPlainODBC].

2.4.2. إنشاء تجميع طبقة [dao]

يتميز مشروع Visual Studio الجديد بالهيكل التالي:

Image

تم تكوين المشروع لإنشاء ملف DLL يُسمى [webarticles-dao.dll]:

2.4.3. اختبارات Nunit للطبقة [dao]

2.4.3.1. إنشاء خادم بيانات مصدر SQL

لاختبار الطبقة الجديدة [dao]، نحتاج إلى مصدر بيانات SQL Server، وبالتالي إلى SGBD SQL Server. سنستخدم في الواقع محرك البيانات SGBD MSDE (محرك البيانات MicroSoft) (الفقرة 3.12)، وهو إصدار من خادم SQL مقيّد فقط بعدد المستخدمين المتزامنين المسموح بهم. باستخدام [EMS MS SQL Manager] (الفقرة 3.14)، نقوم بإنشاء قاعدة المقالات التالية في مثيل MSDE يُسمى [portable1_tahe\msde140405]:

Image

القاعدة مملوكة للمستخدم [mdparticles] بكلمة المرور [admarticles]. الأمر Transact-SQL لإنشاء الجدول [ARTICLES] هو كما يلي:

CREATE TABLE [ARTICLES] (
  [id] int NOT NULL,
  [nom] varchar(20) COLLATE French_CI_AS NOT NULL,
  [prix] float(53) NOT NULL,
  [stockactuel] int NOT NULL,
  [stockminimum] int NOT NULL,
  CONSTRAINT [ARTICLES_uq] UNIQUE ([nom]),
  PRIMARY KEY ([id]),
  CONSTRAINT [ARTICLES_ck_id] CHECK ([id] > 0),
  CONSTRAINT [ARTICLES_ck_nom] CHECK ([nom] <> ''),
  CONSTRAINT [ARTICLES_ck_prix] CHECK ([prix] >= 0),
  CONSTRAINT [ARTICLES_ck_stockactuel] CHECK ([stockactuel] >= 0),
  CONSTRAINT [ARTICLES_ck_stockminimum] CHECK ([stockminimum] >= 0)
)
ON [PRIMARY]
GO

نقوم بإنشاء بعض المقالات:

Image

2.4.3.2. فئة الاختبار NUnit

فئة اختبار Nunit الخاصة بفئة التنفيذ [ArticlesDaoSqlServer] هي نفسها الخاصة بفئة [ArticlesDaoPlainODBC] (انظر الفقرة 2.3.3.2). نتبع نهجًا مشابهًا لإعداد اختبار Nunit الخاص بالفئة:

  • نقوم بإنشاء المجلد [tests] (على اليمين) في مجلد Visual Studio الخاص بالمشروع [dao-sqlserver] عن طريق نسخ المجلد [tests] من المشروع [dao-odbc] (على اليسار):
  • في المجلد [tests] التابع للمشروع [dao-sqlserver]، نستبدل DLL و [webarticles-dao.dll] بـ DLL و [webarticles-dao.dll] الناتجين عن إنشاء المشروع [dao-sqlserver]
  • نقوم بتعديل ملف التكوين [spring-config.xml] من أجل إنشاء مثيل للفئة الجديدة [ArticlesDaoSqlServer]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!--
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
-->
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoSqlServer, webarticles-dao">
        <constructor-arg index="0">
            <value>portable1_tahe\msde140405</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>dbarticles</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>admarticles</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>mdparticles</value>
        </constructor-arg>
    </object>
</objects>

تعليقات:

  • في السطر 7، أصبح الكائن [articlesdao] مرتبطًا الآن بمثيل للفئة [ ArticlesDaoSqlServeur]
  • تحتوي هذه الفئة على منشئ بأربعة معلمات:
  • اسم مثيل MSDE المستخدم - السطر 9
  • اسم قاعدة البيانات - السطر 12
  • الهوية التي سيتم من خلالها الوصول إلى قاعدة البيانات - السطر 15
  • كلمة المرور المرتبطة بهذه الهوية - السطر 18

نستخدم هنا المعلومات المأخوذة من المصدر MSDE الذي أنشأناه سابقًا.

2.4.3.3. الاختبارات

نحن جاهزون لإجراء الاختبارات. باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل ملف DLL و [test-webarticles-dao.dll] من المجلد [tests] المذكور أعلاه ونقوم بتنفيذ الاختبار [testGetAllArticles]:

Image

على الرغم من الاسم [NUnitTestArticlesDaoArrayList] الذي أُطلق في البداية على فئة الاختبار والذي تم الاحتفاظ به نظرًا لأننا نستخدم DLL و [tests-webarticles-dao.dll] المستمدتين من هذه الفئة، إلا أن الفئة [ArticlesDaoSqlserver] هي التي يتم اختبارها هنا. تُظهر لقطة الشاشة أننا استرجعنا بشكل صحيح السجلات التي كنا قد وضعناها في الجدول [ARTICLES]. والآن، دعونا نجري جميع الاختبارات:

Image

في النافذة اليسرى، نرى قائمة بالطرق التي تم اختبارها. يشير لون النقطة التي تسبق اسم كل طريقة إلى نجاح (أخضر) أو فشل (أحمر) الطريقة. سيتمكن القارئ الذي يشاهد هذا المستند على الشاشة من رؤية أن جميع الاختبارات قد نجحت.

2.4.4. دمج الطبقة الجديدة [dao] في التطبيق [webarticles]

نتبع الإجراء الموضح في الفقرة 2.3.4. نقوم بإجراء التعديلات التالية على محتوى المجلد [runtime]:

  • في المجلد [bin]، يتم استبدال DLL منالطبقة القديمة [dao] بـ DLL من الطبقة الجديدة [dao] التي تنفذها الفئة [ArticlesDaoSqlServer]
  • في [runtime]، يتم استبدال ملف التكوين [web.config] بملف يأخذ في الاعتبار فئة التنفيذ الجديدة:
<?xml version="1.0" encoding="iso-8859-1" ?>
<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 type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoSqlServer, webarticles-dao">
        <constructor-arg index="0">
            <value>portable1_tahe\msde140405</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>dbarticles</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>admarticles</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>mdparticles</value>
        </constructor-arg>
    </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

تعليقات:

  • تربط الأسطر 15-33 الكائن الفردي [articlesDao] بمثيل للفئة الجديدة [ArticlesDaoSqlServer]. هذا هو التعديل الوحيد. وقد واجهنا هذا التعديل بالفعل أثناء اختبار الطبقة الجديدة [dao]

نحن جاهزون لإجراء الاختبارات. سنحتفظ بنفس تكوين خادم الويب [Cassini] كما كان من قبل. سنقوم بتهيئة جدول المقالات [MSDE] بالقيم التالية:

Image

تأكد من تشغيل خادم الويب Cassini بالإضافة إلى SGBD وMSDE (وهنا المثيل portable1_tahe\msde140405). باستخدام متصفح، نطلب الوصول إلى URL و [http://localhost/webarticles/main.aspx]:

Image

الآن دعونا نتحقق من محتوى الجدول [ARTICLES] في قاعدة البيانات [MSDE]:

Image

تم شراء الصنفين [ballon foot] و [raquette tennis]، وتم خصم الكمية المشتراة من مخزونهما. لم يتم شراء الصنف [rollers] لأن الكمية المطلوبة تجاوزت الكمية المتوفرة في المخزون. ندعو القارئ إلى إجراء اختبارات إضافية.

2.4.5. فئة التنفيذ [ArticlesDaoOleDb]

2.4.5.1. ات مصادر البيانات OleDb

يفترض التنفيذ الثالث للطبقة [dao] أن البيانات موجودة في قاعدة بيانات يمكن الوصول إليها عبر برنامج تشغيل OleDb. مبدأ مصادر OleDb مشابه لمبدأ مصادر ODBC. يقوم البرنامج الذي يستخدم مصدر OleDb بذلك عبر واجهة قياسية مشتركة بين جميع مصادر OleDb. يقتصر تغيير مصدر OleDb على تغيير برنامج التشغيل OleDb. ولا يتم تعديل الكود نفسه.

يمكن معرفة برامج التشغيل OleDb المتوفرة على جهازك باستخدام Visual Studio:

  • اعرض مستكشف الخوادم باستخدام [Affichage/Explorateur de serveurs]:

Image

  • لإضافة اتصال جديد، انقر بزر الماوس الأيمن على [Connexion de données] واختر الخيار [Ajouter une connexion]. سيظهر عندئذٍ معالج يمكنك من خلاله تحديد خصائص الاتصال:

Image

  • تُظهر لوحة [Fournisseur] قائمة ببرامج التشغيل OLEDB المتاحة. سنستخدم للطبقة الجديدة [dao] برنامج تشغيل [Microsoft Jet 4.0 OLE DB Provider] الذي يتيح الوصول إلى قواعد البيانات ACCESS.
  • لنغادر Visual Studio مؤقتًا لإنشاء قاعدة البيانات ACCESS [articles.mdb] التي تحتوي على الجدول الوحيد التالي:

Image

  • هيكل الجدول هو كما يلي:
id
رقمي - عدد صحيح - مفتاح أساسي
nom
نص - 20 حرفًا -
prix
رقمي - عدد حقيقي مزدوج
stockactuel
رقمي - عدد صحيح
stockminimum
رقمي - عدد صحيح
  • لنعد إلى Visual Studio وننشئ اتصالاً جديداً كما تم شرحه سابقاً:

Image

  • نختار برنامج التشغيل [Microsoft Jet 4.0] وننتقل إلى اللوحة [Connexion]:

Image

  • باستخدام الزر [1]، حدد قاعدة البيانات ACCESS التي تم إنشاؤها للتو، ثم أكمل تعريف الاتصال باستخدام الزر [Terminer]. يظهر الاتصال الذي تم إنشاؤه الآن في قائمة الاتصالات المتاحة:

Image

  • بالنقر المزدوج على الجدول [ARTICLES] يمكننا الوصول إلى محتواه:

Image

  • يمكننا بعد ذلك إضافة أو تعديل أو حذف الصفوف في الجدول.
  • حدد الاتصال الجديد في مستكشف الخوادم للوصول إلى صفحة خصائصه:

Image

  • من المفيد معرفة سلسلة الاتصال. فهي ستساعدنا في الاتصال بقاعدة البيانات:
Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;Data Source=D:\data\serge\databases\access\articles\articles.mdb;Mode=Share Deny None;Extended Properties="";Jet OLEDB:System database="";Jet OLEDB:Registry Path="";Jet OLEDB:Engine Type=5;Jet OLEDB:Database Locking Mode=1;Jet OLEDB:Global Partial Bulk Ops=2;Jet OLEDB:Global Bulk Transactions=1;Jet OLEDB:Create System Database=False;Jet OLEDB:Encrypt Database=False;Jet OLEDB:Don't Copy Locale on Compact=False;Jet OLEDB:Compact Without Replica Repair=False;Jet OLEDB:SFP=False
  • من هذه السلسلة، سنحتفظ فقط بالعناصر التالية:
Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;

2.4.5.2. رمز الفئة [ArticlesDaoOleDb]

تشبه الفئة [ArticlesDaoOleDb] إلى حد كبير الفئة [ArticlesDaoPlainODBC] التي درسناها سابقًا. ولذلك سنكتفي بالإشارة إلى التغييرات التي أُجريت على الإصدار السابق:

  • توجد الفئات الضرورية في مساحة الأسماء [System.Data.OleDb] بدلاً من مساحة الأسماء [System.Data.Odbc]
  • أصبح اتصال النوع [OdbcConnection] الآن من النوع [OleDbConnection]
  • أصبحت كائنات [OdbcCommand] الآن من النوع [OleDbCommand]

يقبل منشئ الفئة كمعلمة وحيدة سلسلة اتصال قاعدة البيانات:

         ' منشئ
        Public Sub New(ByVal connectString As String)
             ' connectString: سلسلة اتصال بالمصدر OleDb
             'يتم إنشاء مثيل للاتصال
            connexion = New OleDbConnection(connectString)
             ' يتم إعداد الطلبات SQL
...
        End Sub

الرمز الكامل للفئة [ArticlesDaoOleDb] هو كما يلي:

Imports System
Imports System.Collections
Imports System.Data.OleDb

Namespace istia.st.articles.dao

    Public Class ArticlesDaoOleDb
        Implements istia.st.articles.dao.IArticlesDao

         ' الحقول الخاصة
        Private connexion As OleDbConnection = Nothing
        Private insertCommand As OleDbCommand
        Private updatecommand As OleDbCommand
        Private deleteSomeCommand As OleDbCommand
        Private selectSomeCommand As OleDbCommand
        Private updateStockCommand As OleDbCommand
        Private deleteAllCommand As OleDbCommand
        Private selectAllCommand As OleDbCommand

         ' منشئ
        Public Sub New(ByVal connectString As String)
             ' connectString: سلسلة اتصال بالمصدر OleDb
             'يتم إنشاء مثيل للاتصال
            connexion = New OleDbConnection(connectString)
             ' يتم إعداد الطلبات SQL
            insertCommand = New OleDbCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)
            updatecommand = New OleDbCommand("update ARTICLES set nom=?, prix=?, stockactuel=?, stockminimum=? where id=?", connexion)
            deleteSomeCommand = New OleDbCommand("delete from ARTICLES where id=?", connexion)
            selectSomeCommand = New OleDbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=?", connexion)
            updateStockCommand = New OleDbCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)
            selectAllCommand = New OleDbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New OleDbCommand("delete from ARTICLES", connexion)
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب الإدراج
                With insertCommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("id", unArticle.id))
                    .Add(New OleDbParameter("nom", unArticle.nom))
                    .Add(New OleDbParameter("prix", unArticle.prix))
                    .Add(New OleDbParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OleDbParameter("stockminimum", unArticle.stockminimum))
                End With
                Try
                     'يتم تنفيذها
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب تحديث المخزون
                With updateStockCommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("mvt1", mouvement))
                    .Add(New OleDbParameter("id", idArticle))
                    .Add(New OleDbParameter("mvt2", mouvement))
                End With
                 'يتم تنفيذه
                Try
                    Return executeUpdate(updateStockCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' قسم حصري
            SyncLock Me
                Try
                     'يتم تنفيذ طلب الإدراج
                    executeUpdate(deleteAllCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
                End Try
            End SyncLock
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             ' قسم حصري
            SyncLock Me
                Try
                     'يتم تنفيذ استعلام الاختيار
                    Dim articles As IList = executeQuery(selectAllCommand)
                     'يتم إرجاع القائمة
                    Return articles
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد استعلام select
                With selectSomeCommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("id", idArticle))
                End With
                 'يتم تنفيذها
                Try
                     'يتم تنفيذ الاستعلام
                    Dim articles As IList = executeQuery(selectSomeCommand)
                     'يتم التحقق مما إذا تم العثور على المقالة
                    If articles.Count = 0 Then Return Nothing
                     'يتم إرجاع المقالة
                    Return CType(articles.Item(0), Article)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب التحديث
                With updatecommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("nom", unArticle.nom))
                    .Add(New OleDbParameter("prix", unArticle.prix))
                    .Add(New OleDbParameter("stockactuel", unArticle.stockactuel))
                    .Add(New OleDbParameter("stockminimum", unArticle.stockactuel))
                    .Add(New OleDbParameter("id", unArticle.id))
                End With
                 ' يتم تنفيذه
                Try
                     'يتم تنفيذ طلب الإدراج
                    Return executeUpdate(updatecommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
                End Try
            End SyncLock
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد استعلام الحذف
                With deleteSomeCommand.Parameters
                    .Clear()
                    .Add(New OleDbParameter("id", idArticle))
                End With
                 'يتم تنفيذه
                Try
                     'يتم تنفيذ طلب الحذف
                    Return executeUpdate(deleteSomeCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Private Function executeQuery(ByVal query As OleDbCommand) As IList
             ' تنفيذ استعلام SELECT 
             ' إعلان الكائن الذي يسمح بالوصول إلى جميع صفوف جدول النتائج
            Dim myReader As OleDbDataReader = Nothing
            Try
                 'إنشاء اتصال بـ BDD
                connexion.Open()
                 'يتم تنفيذ الاستعلام
                myReader = query.ExecuteReader()
                 'يتم تعريف قائمة العناصر لإرجاعها لاحقًا
                Dim articles As IList = New ArrayList
                Dim unArticle As Article
                While myReader.Read()
                     'يتم إعداد عنصر باستخدام قيم القارئ
                    unArticle = New Article
                    unArticle.id = myReader.GetInt32(0)
                    unArticle.nom = myReader.GetString(1)
                    unArticle.prix = myReader.GetDouble(2)
                    unArticle.stockactuel = myReader.GetInt32(3)
                    unArticle.stockminimum = myReader.GetInt32(4)
                     ': تتم إضافة العنصر إلى القائمة
                    articles.Add(unArticle)
                End While
                 'يتم إرجاع النتيجة
                Return articles
            Finally
                 ' تحرير الموارد
                If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function

        Private Function executeUpdate(ByVal sqlCommand As OleDbCommand) As Integer
             ' تنفيذ طلب التحديث
            Try
                 'إنشاء اتصال بـ BDD
                connexion.Open()
                 'يتم تنفيذ الطلب
                Return sqlCommand.ExecuteNonQuery()
            Finally
                 ' تحرير الموارد
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function
    End Class

End Namespace

يُرجى من القارئ قراءة هذا الرمز في ضوء التعليقات التي سبق ذكرها بشأن الفئة [ArticlesDaoPlainODBC].

2.4.5.3. إنشاء تجميع طبقة [dao]

يتميز مشروع Visual Studio الجديد بالهيكل التالي:

Image

تم تكوين المشروع لإنشاء ملف DLL يُسمى [webarticles-dao.dll]:

2.4.5.4. اختبارات Nunit للطبقة [dao]

2.4.5.4.1. فئة الاختبار NUnit

فئة اختبار Nunit لفئة التنفيذ [ArticlesDaoOleDb] هي نفسها فئة [ArticlesDaoPlainODBC] (انظر الفقرة 2.3.3.2). نتبع نهجًا مشابهًا لإعداد اختبار Nunit للفئة:

  • نقوم بإنشاء المجلد [tests] (على اليمين) في مجلد Visual Studio الخاص بالمشروع [dao-oledb] عن طريق نسخ المجلد [tests] من المشروع [dao-odbc] (على اليسار):
  • في المجلد [tests] التابع للمشروع [dao-oledb]، نستبدل المجلدين DLL و[webarticles-dao.dll] بالمجلدين DLL و[webarticles-dao.dll] الناتج عن إنشاء المشروع [dao-oledb]
  • نقوم بتعديل ملف التكوين [spring-config.xml] من أجل إنشاء مثيل للفئة الجديدة [ArticlesDaoOleDb]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!--
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
-->
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoOleDb, webarticles-dao">
        <constructor-arg index="0">
            <value>Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;</value>
        </constructor-arg>
    </object>
</objects>

تعليقات:

  • السطر 7، أصبح الكائن [articlesdao] مرتبطًا الآن بمثيل للفئة [ ArticlesDaoOleDb]
  • تحتوي هذه الفئة على منشئ ذي معلمة واحدة: سلسلة اتصال قاعدة البيانات OleDb ACCESS - السطر 9
2.4.5.4.2. Tests

نحن جاهزون لإجراء الاختبارات. باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل الملفات DLL و [test-webarticles-dao.dll] من المجلد [tests] المذكور أعلاه ونقوم بإجراء الاختبار [testGetAllArticles]:

Image

على الرغم من الاسم [NUnitTestArticlesDaoArrayList] الذي أُطلق في البداية على فئة الاختبار، فإن الفئة التي يتم اختبارها هنا هي بالفعل [ArticlesDaoOleDb]. تُظهر لقطة الشاشة أننا استرجعنا بشكل صحيح السجلات التي وضعناها في الجدول [ARTICLES]. والآن، دعونا نجري جميع الاختبارات:

Image

سيتمكن القارئ الذي يشاهد هذا المستند على الشاشة من رؤية أن جميع الاختبارات قد نجحت (اللون الأخضر).

2.4.5.5. دمج الطبقة الجديدة [dao] في التطبيق [webarticles]

نتبع الخطوات الموضحة في الفقرة 2.3.4. نقوم بإجراء التعديلات التالية على محتوى المجلد [runtime]:

  • في الملف [bin]، تم استبدال الملف DLL منالطبقة القديمة [dao] يتم استبدالها بـ DLL من الطبقة الجديدة [dao] التي تم تنفيذها بواسطة الفئة [ArticlesDaoOleDb]
  • في [runtime]، يتم استبدال ملف التكوين [web.config] بملف يأخذ في الاعتبار فئة التنفيذ الجديدة:
<?xml version="1.0" encoding="iso-8859-1" ?>
<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 type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoOleDb, webarticles-dao">
                <constructor-arg index="0">
                    <value>Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

تعليقات:

  • تربط الأسطر 14-18 الكائن الفردي [articlesDao] بمثيل للفئة الجديدة [ArticlesDaoOleDb]. هذا هو التعديل الوحيد.

نحتفظ بنفس تكوين خادم الويب [Cassini] كما كان من قبل. نقوم بتهيئة جدول المقالات بالقيم التالية:

Image

تأكد من أن قاعدة بيانات المقالات غير مستخدمة من قبل برنامج مثل Visual Studio أو ACCESS. باستخدام متصفح، نطلب URL [http://localhost/webarticles/main.aspx]:

Image

الآن دعونا نتحقق من محتوى الجدول [ARTICLES] باستخدام ACCESS:

Image

تم شراء الصنفين [pantalon] و [jupe]، وتم خصم الكمية المشتراة من مخزونهما. لم يتم شراء الصنف [manteau] لأن الكمية المطلوبة تجاوزت الكمية المتوفرة في المخزون. ندعو القارئ إلى إجراء اختبارات إضافية.

2.5. فئة التنفيذ [ArticlesDaoFirebirdProvider]

2.5.1. مزود الوصول Firebird-net-provider

لقد استخدمنا سابقًا مصدر بيانات [Firebird] عبر برنامج تشغيل ODBC. على الرغم من أن برامج التشغيل ODBC توفر قابلية إعادة استخدام كبيرة للكود الذي يستخدمها، إلا أنها أقل أداءً من برامج التشغيل المكتوبة خصيصًا لـ SGBD المستهدف. يمكن استخدام SGBD و[Firebird] عبر مكتبة من الفئات المحددة التي يمكن تنزيلها من موقع Firebird [http://firebird.sourceforge.net/]. توفر صفحة التنزيلات الروابط التالية (أبريل 2005):

Image

الرابط [firebird-net-provider] هو الرابط الذي يجب استخدامه لتنزيل فئات .NET للوصول إلى SGBD Firebird. يؤدي تثبيت الحزمة إلى إنشاء مجلد مشابه لما يلي:

Image

هناك عنصران يهماننا:

  • [FirebirdSql.Data.Firebird.dll]: التجميع الذي يحتوي على فئات .NET للوصول إلى Firebird SGBD
  • [FirebirdNETProviderSDK.chm]: الوثائق الخاصة بهذه الفئات

بعد ذلك، لكي يتمكن مشروع Visual Studio من استخدام هذه الفئات، سنقوم بأمرين:

  • نضع التجميع [FirebirdSql.Data.Firebird.dll] في مجلد [bin] الخاص بالمشروع
  • نضيف نفس التجميع إلى مراجع المشروع

2.5.2. كود الفئة [ArticlesDaoFirebirdProvider]

تشبه الفئة [ArticlesDaoFirebirdProvider] إلى حد كبير الفئة [ArticlesDaoSqlServer] التي تمت دراستها سابقًا. ولذلك سنكتفي بالإشارة إلى التغييرات التي أُجريت مقارنةً بتلك النسخة:

  • توجد الفئات الضرورية في مساحة الأسماء [FirebirdSql.Data.Firebird] بدلاً من مساحة الأسماء [System.Data.SqlClient]
  • أصبح اتصال النوع [SqlConnection] الآن من النوع [FbConnection]
  • أصبحت الكائنات [SqlCommand] الآن من النوع [FbCommand]
  • أصبحت الكائنات [SqlParameter] من النوع [FbParameter]

يقبل منشئ الفئة أربعة معلمات، يستخدمها لإنشاء سلسلة الاتصال بالقاعدة:

         ' المنشئ
        Public Sub New(ByVal serveur As String, ByVal databaseName As String, ByVal uid As String, ByVal password As String)
             ' الخادم: اسم الجهاز المضيف لـ SGBD
             ' databaseName: مسار الوصول إلى قاعدة البيانات
             ' uid: معرّف المستخدم الذي يقوم بتسجيل الدخول
             ' password: كلمة المرور الخاصة به
...
        End Sub

الرمز الكامل للفئة [ArticlesDaoFirebirdProvider] هو كما يلي:

Imports System
Imports System.Collections
Imports FirebirdSql.Data.Firebird

Namespace istia.st.articles.dao

    Public Class ArticlesDaoFirebirdProvider
        Implements istia.st.articles.dao.IArticlesDao

         ' الحقول الخاصة
        Private connexion As FbConnection = Nothing
        Private databasePath As String
        Private insertCommand As FbCommand
        Private updatecommand As FbCommand
        Private deleteSomeCommand As FbCommand
        Private selectSomeCommand As FbCommand
        Private updateStockCommand As FbCommand
        Private deleteAllCommand As FbCommand
        Private selectAllCommand As FbCommand

         ' المُنشئ
        Public Sub New(ByVal serveur As String, ByVal databasePath As String, ByVal uid As String, ByVal password As String)
             ' الخادم: اسم الجهاز المضيف لـ SGBD Firebird
             ' databaseName: مسار الوصول إلى قاعدة البيانات المراد استغلالها
             ' uid: معرّف المستخدم الذي يتصل بقاعدة البيانات
             ' password: كلمة مروره

             'يتم استرداد اسم قاعدة البيانات الذي تم تمريره كمعلمة
            Me.databasePath = databasePath
             'يتم إنشاء اتصال
            Dim connectString As String = String.Format("DataSource={0};Database={1};User={2};Password={3}", serveur, databasePath, uid, password)
            connexion = New FbConnection(connectString)
             ' يتم إعداد الاستعلامات SQL
            insertCommand = New FbCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (@id,@nom,@prix,@sa,@sm)", connexion)
            updatecommand = New FbCommand("update ARTICLES set nom=@nom, prix=@prix, stockactuel=@sa, stockminimum=@sm where id=@id", connexion)
            deleteSomeCommand = New FbCommand("delete from ARTICLES where id=@id", connexion)
            selectSomeCommand = New FbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES where id=@id", connexion)
            updateStockCommand = New FbCommand("update ARTICLES set stockactuel=stockactuel+@mvt where id=@id and (stockactuel+@mvt)>=0", connexion)
            selectAllCommand = New FbCommand("select id, nom, prix, stockactuel, stockminimum from ARTICLES", connexion)
            deleteAllCommand = New FbCommand("delete from ARTICLES", connexion)
        End Sub

        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد استعلام الإدراج
                With insertCommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@id", unArticle.id))
                    .Add(New FbParameter("@nom", unArticle.nom))
                    .Add(New FbParameter("@prix", unArticle.prix))
                    .Add(New FbParameter("@sa", unArticle.stockactuel))
                    .Add(New FbParameter("@sm", unArticle.stockminimum))
                End With
                Try
                     'يتم تنفيذها
                    Return executeUpdate(insertCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur à l'ajout de l'article [{0}] : {1}", unArticle.ToString, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب تحديث المخزون
                With updateStockCommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@mvt", mouvement))
                    .Add(New FbParameter("@id", idArticle))
                End With
                 'يتم تنفيذه
                Try
                    Return executeUpdate(updateStockCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors du changement de stock [idArticle={0}, mouvement={1}] : [{2}]", idArticle, mouvement, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
             ' قسم حصري
            SyncLock Me
                Try
                     'يتم تنفيذ طلب الإدراج
                    executeUpdate(deleteAllCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la suppression des articles : {0}", ex.Message))
                End Try
            End SyncLock
        End Sub

        Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             ' قسم حصري
            SyncLock Me
                Try
                     'يتم تنفيذ استعلام الاختيار
                    Dim articles As IList = executeQuery(selectAllCommand)
                     'يتم إرجاع القائمة
                    Return articles
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de l'obtention des articles [select id,nom,prix,stockactuel,stockminimum from articles]: {0}", ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد استعلام select
                With selectSomeCommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@id", idArticle))
                End With
                 'يتم تنفيذها
                Try
                     'يتم تنفيذ الاستعلام
                    Dim articles As IList = executeQuery(selectSomeCommand)
                     'يتم التحقق مما إذا تم العثور على المقالة
                    If articles.Count = 0 Then Return Nothing
                     'يتم إرجاع المقالة
                    Return CType(articles.Item(0), Article)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la recherche de l'article [{0} : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب التحديث
                With updatecommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@nom", unArticle.nom))
                    .Add(New FbParameter("@prix", unArticle.prix))
                    .Add(New FbParameter("@sa", unArticle.stockactuel))
                    .Add(New FbParameter("@sm", unArticle.stockminimum))
                    .Add(New FbParameter("@id", unArticle.id))
                End With
                 ' يتم تنفيذه
                Try
                     'يتم تنفيذ طلب الإدراج
                    Return executeUpdate(updatecommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception("Erreur lors de la modification de l'article [" + unArticle.ToString + "]", ex)
                End Try
            End SyncLock
        End Function

        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
             ' قسم حصري
            SyncLock Me
                 ' يتم إعداد طلب الحذف
                With deleteSomeCommand.Parameters
                    .Clear()
                    .Add(New FbParameter("@id", idArticle))
                End With
                 'يتم تنفيذه
                Try
                     'يتم تنفيذ طلب الحذف
                    Return executeUpdate(deleteSomeCommand)
                Catch ex As Exception
                     'خطأ في الاستعلام
                    Throw New Exception(String.Format("Erreur lors de la suppression de l'article [id={0}] : {1}", idArticle, ex.Message))
                End Try
            End SyncLock
        End Function

        Private Function executeQuery(ByVal query As FbCommand) As IList
             ' تنفيذ استعلام SELECT 
             ' إعلان الكائن الذي يسمح بالوصول إلى جميع صفوف جدول النتائج
            Dim myReader As FbDataReader = Nothing
            Try
                 'إنشاء اتصال بـ BDD
                connexion.Open()
                 'يتم تنفيذ الاستعلام
                myReader = query.ExecuteReader()
                 'يتم تعريف قائمة العناصر لإرجاعها لاحقًا
                Dim articles As IList = New ArrayList
                Dim unArticle As Article
                While myReader.Read()
                     ': يجري إعداد مقال يتضمن قيم جهاز القراءة
                    unArticle = New Article
                    unArticle.id = myReader.GetInt32(0)
                    unArticle.nom = myReader.GetString(1)
                    unArticle.prix = myReader.GetDouble(2)
                    unArticle.stockactuel = myReader.GetInt32(3)
                    unArticle.stockminimum = myReader.GetInt32(4)
                     'يتم إضافة المقالة إلى القائمة
                    articles.Add(unArticle)
                End While
                 'يتم إرجاع النتيجة
                Return articles
            Finally
                 ' تحرير الموارد
                If Not myReader Is Nothing And Not myReader.IsClosed Then myReader.Close()
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function

        Private Function executeUpdate(ByVal updateCommand As FbCommand) As Integer
             ' تنفيذ طلب التحديث
            Try
                 'إنشاء اتصال بـ BDD
                connexion.Open()
                 'تنفيذ الطلب
                Return updateCommand.ExecuteNonQuery()
            Finally
                 ' تحرير الموارد
                If Not connexion Is Nothing Then connexion.Close()
            End Try
        End Function
    End Class

End Namespace

يُرجى من القارئ قراءة هذا الرمز في ضوء التعليقات التي سبق ذكرها بشأن الفئة [ArticlesDaoSqlServer].

2.5.3. إنشاء تجميع طبقة [dao]

يتميز مشروع Visual Studio الجديد بالهيكل التالي:

Image

تجدر الإشارة إلى وجود التجميع [FirebirdSql.Data.Firebird.dll] ضمن مراجع المشروع. وقد تم وضع هذا التجميع DLL في مجلد [bin] التابع للمشروع. تم تكوين المشروع لإنشاء ملف DLL يُسمى [webarticles-dao.dll]:

2.5.4. اختبارات Nunit للطبقة [dao]

2.5.4.1. فئة الاختبار NUnit

فئة الاختبار Nunit الخاصة بفئة التنفيذ [ArticlesDaoFirebirdProvider] هي نفسها الخاصة بفئة [ArticlesDaoPlainODBC] (انظر الفقرة 2.3.3.2). نتبع نهجًا مشابهًا لإعداد اختبار Nunit لفئة [ArticlesDaoFirebirdProvider]:

  • نقوم بإنشاء المجلد [tests] (على اليمين) في مجلد Visual Studio الخاص بمشروع [dao-firebird-provider] عن طريق نسخ المجلد [bin] من مشروع الاختبارات الخاص بالطبقة [dao-odbc] (على اليسار):
  • في المجلد [tests]، نستبدل DLL و [webarticles-dao.dll] بـ DLL و [webarticles-dao.dll] الناتجين عن إنشاء مشروع [dao-firebird-provider]
  • نقوم بتعديل ملف التكوين [spring-config.xml] من أجل إنشاء مثيل للفئة الجديدة [ArticlesDaoFirebirdProvider]:
<?xml version="1.0" encoding="iso-8859-1" ?>
<!--
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
-->
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoFirebirdProvider, webarticles-dao">
        <constructor-arg index="0">
            <value>localhost</value>
        </constructor-arg>
        <constructor-arg index="1">
            <value>D:\data\serge\databases\firebird\dbarticles2.gdb</value>
        </constructor-arg>
        <constructor-arg index="2">
            <value>sysdba</value>
        </constructor-arg>
        <constructor-arg index="3">
            <value>masterkey</value>
        </constructor-arg>
    </object>
</objects>

تعليقات:

  • في السطر 7، أصبح الكائن [articlesdao] مرتبطًا الآن بمثيل للفئة [ArticlesDaoFirebirdProvider]
  • تحتوي هذه الفئة على منشئ بأربعة معلمات
  • الجهاز المضيف لـ SGBD - السطر 9
  • مسار الوصول إلى قاعدة بيانات Firebird - السطر 12
  • اسم تسجيل الدخول للمستخدم الذي يقوم بتسجيل الدخول - السطر 15
  • كلمة مروره - السطر 18

2.5.4.2. الاختبارات

يتم ملء الجدول [ARTICLES] في مصدر البيانات بالمقالات التالية (استخدم IBExpert):

Image

نحن جاهزون لإجراء الاختبارات. باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل DLL و [test-webarticles-dao.dll] من المجلد [tests] المذكور أعلاه وننفذ الاختبار [testGetAllArticles]:

Image

على الرغم من الاسم [NUnitTestArticlesDaoArrayList] الذي أُطلق في البداية على فئة الاختبار، فإن الفئة التي يتم اختبارها هنا هي بالفعل [ArticlesDaoFirebirdProvider]. تُظهر لقطة الشاشة أننا استرجعنا بشكل صحيح العناصر التي وضعناها في الجدول [ARTICLES]. والآن، دعونا نجري جميع الاختبارات:

Image

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

2.5.5. دمج الطبقة الجديدة [dao] في التطبيق [webarticles]

نتبع النهج الذي تم شرحه مرتين من قبل، ولا سيما في الفقرة 2.3.4. نقوم بإجراء التعديلات التالية على محتوى المجلد [runtime]:

  • في المجلد [bin]، يتم استبدال DLL منالطبقة القديمة [dao] بـ DLL من الطبقة الجديدة [dao] التي تم تنفيذها بواسطة الفئة [ArticlesDaoFirebirdProvider]. كما نضع فيها أيضًا الطبقة DLL اللازمة لـ Firebird [FirebirdSql.Data.Firebird.dll]:

Image

  • في [runtime]، يتم استبدال ملف التكوين [web.config] بملف يأخذ في الاعتبار فئة التنفيذ الجديدة:
<?xml version="1.0" encoding="iso-8859-1" ?>
<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 type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoFirebirdProvider, webarticles-dao">
                <constructor-arg index="0">
                    <value>localhost</value>
                </constructor-arg>
                <constructor-arg index="1">
                    <value>D:\data\serge\databases\firebird\dbarticles2.gdb</value>
                </constructor-arg>
                <constructor-arg index="2">
                    <value>sysdba</value>
                </constructor-arg>
                <constructor-arg index="3">
                    <value>masterkey</value>
                </constructor-arg>
            </object>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

تعليقات:

  • تربط الأسطر 14-27 الكائن الفردي [articlesDao] بمثيل للفئة الجديدة [ArticlesDaoFirebirdProvider]. هذا هو التعديل الوحيد.

نحن جاهزون لإجراء الاختبارات. نقوم بتكوين خادم الويب [Cassini] كما في الاختبارات السابقة. نقوم بتهيئة جدول المقالات بالقيم التالية:

Image

باستخدام متصفح، نطلب URL [http://localhost/webarticles/main.aspx]:

Image

الآن دعونا نتحقق من محتوى الجدول [ARTICLES]:

Image

تم شراء الصنفين [crayon bille] و [ramette 50 feuilles]، وتم خصم الكمية المشتراة من مخزونهما. لم يتم شراء الصنف [stylo plume] لأن الكمية المطلوبة تجاوزت الكمية المتوفرة في المخزون. ندعو القارئ إلى إجراء اختبارات إضافية.

2.5.6. فئة التنفيذ [ArticlesDaoSqlMap]

2.5.6.1. منتج Ibatis SqlMap

لقد قمنا بكتابة أربعة تطبيقات مختلفة للطبقة [dao] في تطبيقنا [webarticles]. وفي كل مرة، تمكنا من دمج الطبقة الجديدة [dao] في التطبيق [webarticles] دون الحاجة إلى إعادة ترجمة الطبقتين الأخريين [web] و [domain]. وقد تحقق ذلك، كما نذكر، بفضل خيارين في البنية:

  • الوصول إلى الطبقات عبر واجهات
  • دمج الطبقات بواسطة Spring

نرغب في الذهاب أبعد من ذلك قليلاً. على الرغم من اختلافها، فإن تطبيقاتنا الأربعة للطبقة [dao] تظهر أوجه تشابه لافتة للنظر. بمجرد كتابة التطبيق الأول، تم الحصول على التطبيقات الثلاثة الأخرى تقريبًا عن طريق النسخ واللصق واستبدال بعض الكلمات المفتاحية بكلمات مفتاحية أخرى. أما المنطق، فلم يتم تغييره. قد نتساءل عما إذا كان من الممكن الحصول على تطبيق واحد يحررنا من طرق الوصول المختلفة إلى البيانات. لقد استخدمنا أربعة طرق:

  • الوصول عبر برنامج تشغيل ODBC إلى مصدر بيانات ODBC
  • الوصول المباشر إلى قاعدة بيانات SQL Server
  • الوصول عبر برنامج تشغيل Ole Db إلى مصدر بيانات Ole Db
  • الوصول المباشر إلى قاعدة بيانات Firebird

تتيح أداة Ibatis SqlMap [[http://www.ibatis.com/] تطوير طبقات للوصول إلى البيانات تكون مستقلة عن الطبيعة الفعلية لمصدر البيانات. ويتم ضمان الوصول إلى البيانات باستخدام:

  • ملفات التكوين التي تحتوي على المعلومات التي تحدد مصدر البيانات والعمليات التي نريد إجراؤها عليه
  • مكتبة من الفئات التي تعتمد على هذه المعلومات للوصول إلى البيانات

تم تطوير أداة Ibatis SqlMap في البداية لمنصة Java. وقد تم نقلها مؤخرًا إلى منصة .NET، ويبدو أنها تحتوي على بعض الأخطاء (رأي شخصي يتطلب تحققًا دقيقًا). ومع ذلك، وبما أن الأداة أثبتت كفاءتها على منصة Java، يبدو من المفيد عرض نسختها .NET.

2.5.6.2. أين يمكن العثور على IBATIS SqlMap؟

الموقع الرئيسي لـ Firebird هو [http://www.ibatis.com/]. توفر صفحة التنزيلات الروابط التالية:

Image

سنختار الرابط [Stable Binaries] الذي ينقلنا إلى [SourceForge.net]. اتبع عملية التنزيل حتى النهاية. سنحصل على ملف مضغوط يحتوي على الملفات التالية:

Image

في مشروع Visual Studio يستخدم Ibatis SqlMap، يجب القيام بأمرين:

  • وضع الملفات المذكورة أعلاه في مجلد [bin] التابع للمشروع
  • إضافة مرجع إلى كل ملف من هذه الملفات في المشروع

2.5.6.3. ملفات تكوين Ibatis SqlMap

سيتم تعريف مصدر بيانات [SqlMap] باستخدام ملفات التكوين التالية:

  1. providers.config: يحدد مكتبات الفئات التي سيتم استخدامها للوصول إلى البيانات
  2. sqlmap.config: يحدد خصائص الاتصال المطلوب إنشاؤه
  3. ملفات التعيين: تحدد العمليات التي يجب إجراؤها على البيانات

منطق هذه الملفات هو كما يلي:

  • للوصول إلى البيانات، سنحتاج إلى اتصال. لتمثيل هذا الاتصال، سبق أن تعرفنا على عدة فئات: OdbcConnection، SqlConnection، OleDbConnection، FbConnection. كما سنحتاج إلى كائن [Command] لإرسال استعلامات SQL: OdbcCommand، SqlCommand، OleDbCommand، FbCommand. إلخ. في الملف [providers.config]، نحدد مجموعة الفئات التي نحتاجها.
  • ويحدد الملف [sqlmap.config] بشكل أساسي سلسلة الاتصال بقاعدة البيانات التي تحتوي على البيانات. سيتم فتح الاتصال بقاعدة البيانات عن طريق إنشاء مثيل للفئة [Connection] المُعرَّفة في [providers.config]، حيث سيتم تمرير سلسلة الاتصال المُعرَّفة في [sqlmap.config] إلى مُنشئها.
  • تحدد ملفات التعيين:
    • الارتباطات بين صفوف جداول البيانات والفئة .NET التي ستحتوي مثيلاتها على هذه الصفوف
    • عمليات SQL المطلوب تنفيذها. ويتم تحديد هذه العمليات بواسطة اسم. يقوم الكود .NET بتنفيذ هذه العمليات عبر أسمائها، مما يؤدي إلى إزالة أي كود SQL من الكود .NET.

2.5.6.4. ملفات التكوين الخاصة بمشروع [dao-sqlmap]

لنستعرض، من خلال مثال، الطبيعة الدقيقة لملفات التكوين الخاصة بـ SqlMap. سنفترض أن مصدر البيانات هو مصدر Firebird ODBC المذكور في الفقرة 2.3.3.1.

2.5.6.4.1. providers.config

ملف [providers.config] الخاص بمصدر ODBC هو هذا:

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

<providers>
    <clear/>
    <provider 
        name="Odbc1.1" 
        enabled="true" 
        assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
        connectionClass="System.Data.Odbc.OdbcConnection" 
        commandClass="System.Data.Odbc.OdbcCommand" 
        parameterClass="System.Data.Odbc.OdbcParameter" 
        parameterDbTypeClass="System.Data.Odbc.OdbcType" 
        parameterDbTypeProperty="OdbcType" 
        dataAdapterClass="System.Data.Odbc.OdbcDataAdapter" 
        commandBuilderClass="System.Data.Odbc.OdbcCommandBuilder" 
        usePositionalParameters = "true"
        useParameterPrefixInSql = "false"
        useParameterPrefixInParameter = "false"
        parameterPrefix = "@"
    />
</providers>

تعليقات:

  • يتم توزيع ملف [providers.config] مع حزمة [SqlMap]. وهو يوفر العديد من مزودي الخدمة (provider) القياسيين. الكود أعلاه مأخوذ مباشرة من هذا الملف.
  • يحتوي كل <provider> على اسم - السطر 6 - يمكن أن يكون أي اسم
  • يمكن تفعيل <provider> [enabled=true] أو عدم تفعيله [enabled=false]. إذا تم تفعيله، فيجب أن يكون <provider> المشار إليه في السطر 8 DLL متاحًا. يمكن أن يحتوي ملف [providers.config] على عدة علامات <provider>.
  • السطر 8 - اسم التجميع الذي يحتوي على الفئات المحددة في الأسطر 9-15
  • السطر 9 - الفئة التي سيتم استخدامها لإنشاء اتصال
  • السطر 10 - الفئة التي يجب استخدامها لإنشاء كائن [Command] لإرسال أوامر SQL
  • السطر 11 - الفئة المستخدمة لإدارة معلمات أمر SQL الذي تم تعيين معلماته
  • السطر 12 - فئة تعداد أنواع البيانات الممكنة لحقول الجدول
  • السطر 13 - اسم خاصية كائن [Parameter] التي تحتوي على نوع قيمة هذا المعامل
  • السطر 14 - اسم الفئة [Adapter] التي تتيح إنشاء كائنات [DataSet] من مصدر البيانات
  • السطر 15 - اسم الفئة [CommandBuilder] التي، عند ربطها بكائن [Adapter]، تتيح إنشاء خصائص [InsertCommand, DeleteCommand, UpdateCommand] الخاصة به تلقائيًا انطلاقًا من خاصيته [SelectCommand]
  • الأسطر 16 - 19 - يتم تحديد كيفية إدارة الأوامر SQL التي تم تعيين معلماتها. حسب الحالة، يجب كتابة ما يلي على سبيل المثال:
insert into ARTICLES(id,nom,prix,stockactuel,stockminimum) values (?,?,?,?,?)

أو

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

في الحالة الأولى، نتحدث عن معلمات موضعية رسمية. يجب توفير القيم الفعلية لهذه المعلمات بترتيب المعلمات الرسمية. في الحالة الثانية، نتعامل مع معلمات مسماة. يتم توفير قيمة لمثل هذه المعلمة من خلال تحديد اسمها. لم يعد للترتيب أي أهمية.

  • السطر 16 - يُشار إلى أن المصادر ODBC تستخدم معلمات موضعية
  • الأسطر 17-19 - تتعلق بالمعلمات المسماة. ولا توجد أي منها هنا.

تسمح هذه المعلومات لـ SqlMap بمعرفة، على سبيل المثال، الفئة التي يجب عليه إنشاء مثيل لها لإنشاء اتصال. وهنا ستكون الفئة هي [OdbcConnection] (السطر 9).

2.5.6.4.2. sqlmap.config

يحدد الملف [providers.config] الفئات التي يجب استخدامها للوصول إلى مصدر ODBC. ولا يشير إلى أي مصدر ODBC. الملف [sqlmap.config] هو الذي يقوم بذلك:

<?xml version="1.0" encoding="utf-8" ?>
<sqlMapConfig xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="Schemas\SqlMapConfig.xsd">
    <properties resource="properties.xml"/>
    <settings>
        <setting useStatementNamespaces="false" />
        <setting cacheModelsEnabled="false" />
    </settings>
     <!-- ==== مصدر البيانات ========= --    >
    <database>
        <provider name="${provider}"/>
        <dataSource name="sqlmaparticles"  connectionString="${connectionString}"/>
        <transactionManager type="ADO/SWC" />
    </database>
    <sqlMaps>
        <sqlMap resource="articles.xml" />
    </sqlMaps>
</sqlMapConfig>

تعليقات:

  • السطر 3 - يتم تعريف ملف خصائص [properties.xml]. ويحدد هذا الملف أزواجًا (مفتاح، قيمة). يمكن أن تكون المفاتيح أي شيء. يتم الحصول على القيمة المرتبطة بالمفتاح C من خلال الترميز ${C} في ملف [sqlmap.config]. فيما يلي ملف [properties.xml] الذي سيتم ربطه بالملف السابق [sqlmap.config]:
1
2
3
4
5
<?xml version="1.0" encoding="utf-8" ?>
<settings>
    <add key="provider" value="Odbc1.1" />
    <add key="connectionString" value="DSN=odbc-firebird-articles;UID=SYSDBA;PASSWORD=masterkey" />
</settings>

السطر 3 - تم تعريف المفتاح [provider]. قيمته هي اسم العلامة <provider> التي سيتم استخدامها في [providers.config]

السطر 4 - تم تعريف المفتاح [connectionString]. قيمته هي سلسلة الاتصال التي سيتم استخدامها لفتح اتصال بمصدر البيانات ODBC Firebird.

  • الأسطر 4-7 - معلمات التكوين:
    • السطر 5 - سيتم تعريف استعلامات SQL باسم يمكن أن يكون جزءًا من مساحة أسماء. يشير [useStatementNamespaces="false"] إلى أنه لن يتم استخدام مساحات الأسماء.
    • السطر 6 - SqlMap تمتلك استراتيجيات تخزين مؤقت مختلفة لتقليل الوصول إلى مصدر البيانات. تشير [cacheModelsEnabled="false"] إلى أنه لن يتم استخدام أي منها.
  • الأسطر 9-13 - يتم تحديد خصائص مصدر البيانات:
    • السطر 10 - اسم <provider> الخاص بـ [providers.config] المراد استخدامه
    • السطر 11 - سلسلة الاتصال بمصدر البيانات
    • السطر 12 - مدير المعاملات. لم نستخدمه هنا، لكننا أبقينا على السطر لأنه كان موجودًا في ملف التوزيع القياسي.
  • الأسطر 14-16 - قائمة بالملفات التي تحدد عمليات SQL المطلوب تنفيذها على مصدر البيانات.
  • السطر 15 - يحدد ملف التعيين [articles.xml]
2.5.6.4.3. articles.xml

يؤدي هذا الملف وظيفتين:

  • تحديد تعيين كائني لجداول مصدر البيانات. في أبسط الحالات، يعادل ذلك ربط فئة بصف في جدول.
  • تحديد عمليات SQL المُعَرَّفة مسبقًا وتسميتها.

سنستخدم الملف التالي: [articles.xml]:

<?xml version="1.0" encoding="iso-8859-1" ?>
<sqlMap namespace="Articles" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="SqlMap.xsd">
     <!-- resultMap -->
    <resultMaps>
        <resultMap id="article" class="istia.st.articles.dao.Article">
            <result property="id" column="ID" />
            <result property="nom" column="NOM" />
            <result property="prix" column="PRIX" />
            <result property="stockactuel" column="STOCKACTUEL" />
            <result property="stockminimum" column="STOCKMINIMUM" />
        </resultMap>
    </resultMaps>
     <!-- الاستعلامات SQL -->
    <statements>
         <!-- الحصول على جميع المقالات -->
        <select id="getAllArticles" resultMap="article">
                select ID,NOM,PRIX,STOCKACTUEL,STOCKMINIMUM FROM ARTICLES
            </select> 
         <!-- حذف جميع المقالات-->
        <delete id="clearAllArticles">
                delete from ARTICLES
            </delete> 
         <!-- إضافة منتج -->
        <insert id="insertArticle" parameterClass="istia.st.articles.dao.Article">
                insert into ARTICLES (id, nom, prix,stockactuel, stockminimum) values
                ( #(id# ، #الاسم# ، #السعر# ، #المخزون_الحالي# ، #الحد_الأدنى_للمخزون#)
            </insert>
         <!-- حذف منتج -->
        <delete id="deleteArticle" parameterClass="int">
                delete FROM ARTICLES where ID= #القيمة#
            </delete>
         <!-- تعديل منتج -->
        <update id="modifyArticle" parameterClass="istia.st.articles.dao.Article">
                update ARTICLES set NOM= #الاسم# ،PRIX= #السعر# ،STOCKACTUEL= #المخزون_الحالي# ,STOCKMINIMUM= #الحد الأدنى للمخزون# حيث ID= #id#
            </update>
         <!-- البحث عن منتج محدد -->
        <select id="getArticleById" resultMap="article" parameterClass="int">
                select ID, NOM, PRIX,    STOCKACTUEL, STOCKMINIMUM FROM ARTICLES where ID= #القيمة#
            </select>
         <!-- تغيير مخزون منتج -->
        <update id="changerStockArticle" parameterClass="Hashtable">
                update ARTICLES set STOCKACTUEL=(STOCKACTUEL + #حركة#) حيث ID=#id# و ((STOCKACTUEL + #حركة#) >=0)
      </update>
    </statements>
</sqlMap>

تعليقات:

  • الأسطر 4-11 - يتم تعريف تخطيط بين سطر من الجدول [ARTICLES] في مصدر البيانات والفئة [istia.st.articles.dao.Article]. يتم ربط كل عمود (column) في الجدول بخاصية (property) من الفئة [Article]. يسمح هذا التعيين لـ [SqlMap] بإنشاء نتيجة عملية SQL SELECT. سيتم وضع كل سطر من نتائج عملية SELECT في كائن [Article] وفقًا لقواعد التعيين.
  • السطر 5 - يتم تعيين التعيين بعلامة <resultMap> ويتم تسميته بالسمة [id="article"]. ويتم تحديد الفئة المرتبطة بالسمة [class="istia.st.articles.dao.Article"].
  • الأسطر 14-44 - يتم تعريف العمليات SQL المطلوبة
  • الأسطر 16-18 - يتم تعريف عملية SELECT تُسمى [getAllArticles]
    • السطر 16 - تُسمى العملية SELECT باسم [name= "getAllArticles "]، ويتم تحديد التعيين المطلوب استخدامه بواسطة السمة [resultMap="article"]. وبالتالي، يتم هنا الإشارة إلى التعيين الوارد في الأسطر 5-11
    • السطر 17 - نص الأمر SQL المطلوب تنفيذه
  • الأسطر 20-22 - يتم تعريف الأمر SQL-Delete [clearAllArticles] المخصص لإفراغ جدول المنتجات.
  • الأسطر 24-27 - يتم تعريف الأمر SQL-Insert [insertArticle] المخصص لإضافة منتج جديد إلى جدول المنتجات. وهو استعلام يتم تعيين معلماته بواسطة العناصر (#id#، #nom#، #prix#، #stockactuel#، #stockminimum#). وستأتي قيم هذه العناصر الخمسة من كائن [Article] الذي يتم تمريره كمعلمة: [parameterClass="istia.st.articles.dao.Article"]. يجب أن يحتوي كائن المعلمة على الخصائص (id، nom، prix، stockactuel، stockminimum) المشار إليها في الأمر SQL المُعَرَّف.
  • الأسطر 29-31 - يتم تعريف الأمر SQL Delete [deleteArticle] المخصص لحذف عنصر معروف برقمه #value#. سيتم تمرير هذا الرقم كمعلمة: [parameterClass="int"]. هذه قاعدة عامة. عندما يكون المعامل فريدًا، يُشار إليه بالكلمة الرئيسية #value# في نص الأمر SQL.
  • الأسطر 33-35 - يتم تعريف الأمر SQL-Update [modifyArticle] المخصص لتعديل عنصر معروف رقمه. وكما هو الحال مع الأمر [insertArticle]، ستأتي المعلومات الخمسة اللازمة من خصائص كائن [istia.st.articles.dao.Article].
  • الأسطر 37-39 - يتم تعريف الأمر SQL-Select [getArticleById] الذي يتيح الحصول على السجل الخاص بسلعة معروفة رقمها.
  • الأسطر 41-43 - يتم تعريف أمر SQL-Update [changerStockArticle] الذي يقوم بتعديل الحقل [stockactuel] لأحد الأصناف الذي نعرف رقمه. سيتم العثور على المعلومات الضرورية، وهي رقم #id# للمنتج وزيادة المخزون #mouvement#، في قاموس: [parameterClass="Hashtable"]. يجب أن يحتوي هذا القاموس على مفتاحين: id و mouvement. وستُستخدم القيم المرتبطة بهذين المفتاحين في الأمر SQL.
2.5.6.4.4. موقع ملفات التكوين

سنستعرض حالتين مختلفتين:

  • في حالة اختبار Nunit، سيتم وضع ملفات التكوين الخاصة بـ [SqlMap] في نفس المجلد الذي توجد فيه الملفات الثنائية قيد الاختبار.
  • في حالة تطبيق الويب، سيتم وضعها في الجذر الخاص بالتطبيق.

2.5.6.5. API من SqlMap

توجد فئات SqlMap ضمن ملف DLL الذي يتم وضعه عادةً في مجلد [bin] الخاص بالتطبيق:

Image

يجب على التطبيقات التي تستخدم فئات SqlMap استيراد مساحة الأسماء [IBatisNet.DataMapper]:

Imports IBatisNet.DataMapper

تُجرى جميع عمليات SQL من خلال عنصر فريد من نوع [Mapper]، وهي فئة تابعة لمساحة الأسماء [IBatisNet.DataMapper ]. يتم الحصول على العنصر الفريد بالطريقة التالية:

        Dim mappeur As SqlMapper = Mapper.Instance

لتنفيذ الأمر SqlMap [getAllArticles]، نكتب:

                    dim articles as IList=mappeur.QueryForList("getAllArticles", Nothing)
  • تسمح الطريقة [QueryForList] بالحصول على نتيجة الأمر SELECT في قائمة
  • المعلمة الأولى هي اسم الأمر SQL المراد تنفيذه (انظر articles.xml)
  • المعلمة الثانية هي المعلمة المراد إرسالها إلى الاستعلام SQL. يجب أن تتطابق مع السمة [parameterClass] للأمر SqlMap. في [articles.xml]، لدينا [parameterClass=Nothing]. ولذلك يتم هنا تمرير مؤشر صفر.
  • النتيجة من النوع IList. يتم الإشارة إلى الكائنات في هذه القائمة بواسطة السمة [resultMap] للأمر SQL-select: [resultMap="article"]. «article» هو اسم التعيين:
<resultMap id="article" class="istia.st.articles.dao.Article">

الفئة المرتبطة بهذا التعيين هي [istia.st.articles.dao.Article]. وفي النهاية، فإن المتغير [articles] المُعرَّف أعلاه هو قائمة من الكائنات [ istia.st.articles.dao.Article]. وبذلك حصلنا على الجدول [ARTICLES] بالكامل في تعليمة واحدة. إذا كان الجدول [ARTICLES] فارغًا، فسنحصل على كائن [IList] يحتوي على 0 عنصر.

لتنفيذ الأمر SqlMap [getArticleById]، نكتب:

dim unArticle as Article=CType(mappeur.QueryForObject("getArticleById", idArticle), Article)
  • تسمح الطريقة [QueryForObject] بالحصول على نتيجة الأمر SELECT الذي لا يُرجع سوى سطر واحد
  • المعلمة الأولى هي اسم الأمر SqlMap المراد تنفيذه
  • المعلمة الثانية هي المعلمة التي يجب إرسالها إلى الاستعلام SQL. يجب أن تتطابق مع السمة [parameterClass] للأمر SqlMap. في [articles.xml]، لدينا [parameterClass="int"]. لذا، نمرر هنا عددًا صحيحًا يمثل رقم المادة المطلوبة.
  • النتيجة من نوع Object. إذا لم يُرجع الأمر SELECT أي صف، فسيكون المؤشر «صفر» (nothing) هو النتيجة.

لتنفيذ الأمر SqlMap [insertArticle]، نكتب:

                    mappeur.Insert("insertArticle", unArticle)
  • تسمح الطريقة [Insert] بتنفيذ الأوامر SQL و INSERT
  • المعلمة الأولى هي اسم الأمر SqlMap المراد تنفيذه
  • المعلمة الثانية هي المعلمة التي يجب تمريرها إلى هذا الأمر. يجب أن تتطابق مع السمة [parameterClass] للأمر SqlMap. في [articles.xml]، لدينا [parameterClass="istia.st.articles.dao.Article"]. ولذلك، يتم هنا تمرير كائن من النوع [istia.st.articles.dao.Article].

لتنفيذ الأمر SqlMap [deleteArticle]، نكتب:

                    dim nbArticles as Integer=mappeur.Delete("deleteArticle", idArticle)
  • تسمح الطريقة [Delete] بتنفيذ الأوامر SQL و DELETE
  • المعلمة الأولى هي اسم الأمر SQL المراد تنفيذه
  • المعلمة الثانية هي المعلمة التي يجب إرسالها إلى هذا الأمر. يجب أن تتطابق مع السمة [parameterClass] للأمر SqlMap. في الأمر [articles.xml]، لدينا الأمر [parameterClass="int"]. ولذلك، يتم هنا تمرير رقم المادة المراد حذفها.
  • نتيجة الأسلوب [Delete] هي عدد الأسطر التي تم حذفها

وبالمثل، لتنفيذ الأمر SqlMap [clearAllArticles]، نكتب:

                    dim nbArticles as Integer=mappeur.Delete("clearAllArticles", nothing)

لتنفيذ الأمر SqlMap [modifyArticle]، نكتب:

                    dim nbArticles as Integer=mappeur.Update("modifyArticle", unArticle)
  • تسمح الطريقة [Update] بتنفيذ الأوامر SQL و UPDATE
  • المعلمة الأولى هي اسم الأمر SqlMap المراد تنفيذه
  • المعلمة الثانية هي المعلمة التي يجب تمريرها إلى هذا الأمر. يجب أن تتطابق مع السمة [parameterClass] للأمر SqlMap. في [articles.xml]، لدينا [parameterClass="istia.st.articles.dao.Article"]. ولذلك يتم هنا تمرير كائن من النوع [istia.st.articles.dao.Article].
  • نتيجة طريقة [Update] هي عدد الأسطر التي تم تعديلها.

وبالمثل، لتنفيذ الأمر SqlMap [changerStockArticle]، نكتب:

                    Dim paramètres As New Hashtable(2)
                    paramètres("id") = idArticle
                    paramètres("mouvement") = mouvement
                     ' تحديث
                    dim nbLignes as Integer= mappeur.Update("changerStockArticle", paramètres)
  • المعلمة الثانية تتوافق مع السمة [parameterClass] للأمر SqlMap. في [articles.xml]، يوجد [parameterClass="Hashtable"]. الأمر SQL الذي تم تعيين معلماته إلى [changerStockArticle] يستخدم المعلمات [id, mouvement]. ولذلك يتم هنا تمرير قاموس يحتوي على هذين المفتاحين.

2.5.6.6. كود فئة [ArticlesDaoSqlMap]

بعد الشروحات السابقة، أصبح بإمكاننا الآن كتابة فئة التنفيذ الجديدة [ArticlesDaoSqlMap] التالية:

Option Explicit On 
Option Strict On

Imports System
Imports IBatisNet.DataMapper
Imports System.Collections

Namespace istia.st.articles.dao

    Public Class ArticlesDaoSqlMap
        Implements IArticlesDao

         ' الحقول الخاصة
        Dim mappeur As SqlMapper = Mapper.Instance

         ' قائمة بجميع المقالات
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
            SyncLock Me
                Try
                    Return mappeur.QueryForList("getAllArticles", Nothing)
                Catch ex As Exception
                    Throw New Exception("Echec de l'obtention de tous les articles : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' إضافة مقال
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
            SyncLock Me
                Try
                     ' unArticle: مقال مراد إضافته
                     ' إدراج
                    mappeur.Insert("insertArticle", unArticle)
                    Return 1
                Catch ex As Exception
                    Throw New Exception("Echec de l'ajout de l'article [" + unArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' حذف عنصر
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
            SyncLock Me
                Try
                     ' id: معرّف المادة المراد حذفها
                     ' حذف
                    Return mappeur.Delete("deleteArticle", idArticle)
                Catch ex As Exception
                    Throw New Exception("Erreur lors de la suppression de l'article d'id [" + idArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' تعديل مقال
        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
            SyncLock Me
                Try
                     ' تحديث
                    Return mappeur.Update("modifyArticle", unArticle)
                Catch ex As Exception
                    Throw New Exception("Erreur lors de la mise à jour de l'article [" + unArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' البحث عن مقال
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
            SyncLock Me
                Try
                     ' id: معرّف المقالة المطلوبة
                    Return CType(mappeur.QueryForObject("getArticleById", idArticle), Article)
                Catch ex As Exception
                    Throw New Exception("Erreur lors de la recherche de l'article d'id [" + idArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' حذف جميع المنتجات
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
            SyncLock Me
                Try
                    mappeur.Delete("clearAllArticles", Nothing)
                Catch ex As Exception
                    Throw New Exception("Erreur lors de l'effacement de la table des articles : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Sub

         ' تغيير مخزون أحد المنتجات
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
            SyncLock Me
                Try
                     ' المعرف: معرف المنتج الذي يتم تغيير مخزونه
                     ' التحرك: حركة المخزون
                    Dim paramètres As New Hashtable(2)
                    paramètres("id") = idArticle
                    paramètres("mouvement") = mouvement
                     ' تحديث
                    Return mappeur.Update("changerStockArticle", paramètres)
                Catch ex As Exception
                    Throw New Exception(String.Format("Erreur lors du changement de stock [{0},{1}] : {2}", idArticle, mouvement, ex.ToString))
                End Try
            End SyncLock
        End Function
    End Class
End Namespace

يُرجى من القارئ قراءة هذا الرمز في ضوء التفسيرات المقدمة لـ API من SqlMap. ومن الجدير بالذكر أن استخدام [SqlMap] قد قلل بشكل كبير من كمية الرموز المطلوب كتابتها.

2.5.6.7. إنشاء تجميع طبقة [dao]

يتميز مشروع Visual Studio الجديد بالهيكل التالي:

Image

تجدر الإشارة إلى وجود «التجميعات» اللازمة لـ SqlMap في مراجع المشروع. وقد تم وضع هذه التجميعات في مجلد [bin] التابع للمشروع. تم تكوين المشروع لإنشاء ملف DLL يُسمى [webarticles-dao.dll]:

2.5.6.8. اختبارات Nunit للطبقة [dao]

2.5.6.8.1. فئة الاختبار NUnit

فئة اختبار Nunit لفئة التنفيذ [ArticlesDaoSqlMap] هي نفسها فئة [ArticlesDaoPlainODBC] (انظر الفقرة 2.3.3.2). نتبع نهجًا مشابهًا لإعداد اختبار Nunit لفئة [ArticlesDaoSqlMap]:

  • نقوم بإنشاء المجلد [test1] (على اليمين) في مجلد Visual Studio الخاص بالمشروع [dao-sqlmap] عن طريق نسخ المجلد [tests] من المشروع [dao-odbc] (على اليسار):
  • في المجلد [tests]، نستبدل الملفين DLL و[webarticles-dao.dll] بالملفين DLL و[webarticles-dao.dll] الناتجين عن إنشاء المشروع [dao-sqlmap].
  • نضيف ملفات DLL اللازمة لـ SqlMap بالإضافة إلى ملفات التكوين التي تمت دراستها [providers.config, sqlmap.config, properties.xml, articles.xml].
  • نقوم بتعديل ملف التكوين [spring-config.xml] من أجل إنشاء مثيل للفئة الجديدة [ArticlesDaoSqlMap]:
1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="iso-8859-1" ?>
<!--
<!DOCTYPE objects PUBLIC "-//SPRING//DTD OBJECT//EN"
"http://www.springframework.net/dtd/spring-objects.dtd">
-->
<objects>
    <object id="articlesdao" type="istia.st.articles.dao.ArticlesDaoSqlMap, webarticles-dao"/>
</objects>

تعليقات:

  • في السطر 7، أصبح الكائن [articlesdao] مرتبطًا الآن بمثيل للفئة [ArticlesDaoSqlMap]
  • هذه الفئة لا تحتوي على منشئ. سيتم استخدام المنشئ الافتراضي.
2.5.6.8.2. Tests

تم ملء الجدول [ARTICLES] في مصدر البيانات Firebird بالمواد التالية:

Image

نحن جاهزون لإجراء الاختبارات. باستخدام التطبيق [Nunit-Gui]، نقوم بتحميل DLL و [test-webarticles-dao.dll] من المجلد [test1] المذكور أعلاه وننفذ الاختبار [testGetAllArticles]:

Image

على الرغم من الاسم [NUnitTestArticlesDaoArrayList] الذي أُطلق في البداية على فئة الاختبار، فإن الفئة التي يتم اختبارها هنا هي بالفعل [ArticlesDaoSqlMap]. تُظهر لقطة الشاشة أننا استرجعنا بشكل صحيح العناصر التي وضعناها في الجدول [ARTICLES]. والآن، دعونا نجري جميع الاختبارات:

Image

سيلاحظ القارئ الذي يشاهد هذا المستند على الشاشة أن بعض الاختبارات قد نجحت (باللون الأخضر) بينما فشلت اختبارات أخرى (باللون الأحمر). الاختبارات التي فشلت هي الاختبارات [testArticleAbsent] و [testChangerStockArticle]. بعد بحث طويل، يبدو أن أسباب هذا الفشل هي التالية:

  • في الاختبار [testArticleAbsent]، يُطلب تعديل مقال غير موجود. يتم استخدام الطريقة [modifieArticle] لهذا الغرض، والتي تجعل عدد الأسطر التي تم تعديلها 0 أو 1. هنا، من المفترض أن يكون العدد 0. بدلاً من ذلك، تظهر استثناء من النوع [IBatisNet.Common.Exceptions.ConcurrentException].
  • في [changerStockArticle]، لدينا عملية أخرى من النوع [update]. وهي تتمثل في خصم كمية من المخزون أكبر من المخزون نفسه. ولذلك، نستخدم الطريقة [changerStockArticle] التي تجعل عدد الأسطر التي تم تعديلها 0 أو 1. تمت كتابة الأمر SQL لتجنب إجراء تحديث (انظر الأمر SQL "changerStockArticle" في articles.xml) من شأنه أن يجعل المخزون سالبًا. من المتوقع هنا الحصول على 0 كنتيجة لطريقة [changerStockArticle]. ومرة أخرى، لدينا استثناء من النوع [IBatisNet.Common.Exceptions.ConcurrentException].

هناك العديد من الأسباب المحتملة للخطأ:

  1. كود الفئة [ArticlesDaoSqlMap] خاطئ. هذا احتمال وارد. ومع ذلك، فهو ناتج عن تحويل فئة Java كانت تعمل بشكل صحيح مع الإصدار Java من SqlMap.
  2. الإصدار .NET من SqlMap به أخطاء
  3. برنامج التشغيل ODBC الخاص بـ Firebird به أخطاء
  4. ...

في ظل عدم وجود يقين، سنقوم بتجاوز هذه العقبة عن طريق اعتراض الاستثناء الشهير [IBatisNet.Common.Exceptions.ConcurrentException]. يصبح الكود الجديد لفئة [ArticlesDaoSqlMap] كما يلي:

....
Namespace istia.st.articles.dao

    Public Class ArticlesDaoSqlMap
        Implements IArticlesDao

         ' الحقول الخاصة
        Dim mappeur As SqlMapper = Mapper.Instance

         ' قائمة بجميع الأصناف
        Public Function getAllArticles() As IList Implements IArticlesDao.getAllArticles
...
        End Function

         ' إضافة منتج
        Public Function ajouteArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.ajouteArticle
...
        End Function

         ' حذف عنصر
        Public Function supprimeArticle(ByVal idArticle As Integer) As Integer Implements IArticlesDao.supprimeArticle
            SyncLock Me
                Try
                     ' id: معرّف المقالة المراد حذفها
                     ' حذف
                    Return mappeur.Delete("deleteArticle", idArticle)
                Catch ex As Exception
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    Throw New Exception("Erreur lors de la suppression de l'article d'id [" + idArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' تعديل مقال
        Public Function modifieArticle(ByVal unArticle As Article) As Integer Implements IArticlesDao.modifieArticle
            SyncLock Me
                Try
                     ' تحديث
                    Return mappeur.Update("modifyArticle", unArticle)
                Catch ex As Exception
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    Throw New Exception("Erreur lors de la mise à jour de l'article [" + unArticle.ToString + "] : [" + ex.ToString + "]")
                End Try
            End SyncLock
        End Function

         ' البحث عن مقال
        Public Function getArticleById(ByVal idArticle As Integer) As Article Implements IArticlesDao.getArticleById
...
        End Function

         ' حذف جميع المنتجات
        Public Sub clearAllArticles() Implements IArticlesDao.clearAllArticles
....
        End Sub

         ' تغيير مخزون منتج
        Public Function changerStockArticle(ByVal idArticle As Integer, ByVal mouvement As Integer) As Integer Implements IArticlesDao.changerStockArticle
            SyncLock Me
                Try
                     ' id: معرّف المنتج الذي يتم تغيير مخزونه
                     ' الحركة: حركة المخزون
                    Dim paramètres As New Hashtable(2)
                    paramètres("id") = idArticle
                    paramètres("mouvement") = mouvement
                     ' تحديث
                    Return mappeur.Update("changerStockArticle", paramètres)
                Catch ex As Exception
                    If ex.GetType.Equals(GetType(IBatisNet.Common.Exceptions.ConcurrentException)) Then Return 0
                    Throw New Exception(String.Format("Erreur lors du changement de stock [{0},{1}] : {2}", idArticle, mouvement, ex.ToString))
                End Try
            End SyncLock
        End Function
    End Class
End Namespace

التعديلات موجودة في الأسطر: 28، 41، 69. بالنسبة لعمليات SQL من النوع [UPDATE, DELETE]، إذا حدث استثناء من النوع [IBatisNet.Common.Exceptions.ConcurrentException]، فإننا نُرجع 0 كنتيجة، مما يشير إلى أنه لم يتم تعديل أو حذف أي سطر. وبعد ذلك، يتم إعادة إنشاء ملف DLL الخاص بالمشروع، ووضعه في المجلد [test1]، وإعادة تشغيل الاختبارات NUnit:

Image

هذه المرة نجح الأمر. سنعمل من الآن فصاعدًا باستخدام DLL.

2.5.6.9. دمج الطبقة الجديدة [dao] في التطبيق [webarticles]

2.5.6.9.1. مصدر البيانات ODBC

نقوم هنا باختبار مصدر البيانات ODBC الذي تمت دراسته في الفقرة 2.3.3.1. ويتم استخدامه هنا من خلال SqlMap.

نتبع الخطوات الموضحة في الفقرة 2.3.4. نقوم بإجراء التعديلات التالية على محتوى المجلد [runtime]:

  • في المجلد [bin]، يتم استبدال المجلد DLL منالطبقة القديمة [dao] بـ DLL من الطبقة الجديدة [dao] التي تم تنفيذها بواسطة الفئة [ArticlesDaoSqlMap]. ونضيف إليها الطبقات DLL اللازمة لـ Firebird و SqlMap:

Image

  • في [runtime]، نضع ملفات التكوين الخاصة بـ SqlMap و [providers.config, sqlmap.config, properties.xml, articles.xml]:

Image

  • في [runtime]، يتم استبدال ملف التكوين [web.config] بملف يأخذ في الاعتبار فئة التنفيذ الجديدة:
<?xml version="1.0" encoding="iso-8859-1" ?>
<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 type="Spring.Context.Support.XmlApplicationContext, Spring.Core">
            <resource uri="config://spring/objects" />
        </context>
        <objects>
            <object id="articlesDao" type="istia.st.articles.dao.ArticlesDaoSqlMap, webarticles-dao"/>
            <object id="articlesDomain" type="istia.st.articles.domain.AchatsArticles, webarticles-domain">
                <constructor-arg index="0">
                    <ref object="articlesDao" />
                </constructor-arg>
            </object>
        </objects>
    </spring>
    <appSettings>
        <add key="urlMain" value="/webarticles/main.aspx" />
        <add key="urlInfos" value="vues/infos.aspx" />
        <add key="urlErreurs" value="vues/erreurs.aspx" />
        <add key="urlListe" value="vues/liste.aspx" />
        <add key="urlPanier" value="vues/panier.aspx" />
        <add key="urlPanierVide" value="vues/paniervide.aspx" />
    </appSettings>
</configuration>

تعليقات:

  • تربط السطر 14 الكائن الفردي [articlesDao] بمثيل للفئة الجديدة [ArticlesDaoSqlMap]. هذا هو التعديل الوحيد.

نحن جاهزون لإجراء الاختبارات. نقوم بتكوين خادم الويب [Cassini] كما في الاختبارات السابقة. نقوم بتهيئة جدول المقالات بالقيم التالية:

Image

باستخدام متصفح، نطلب URL [http://localhost/webarticles/main.aspx]:

Image

الآن دعونا نتحقق من محتوى الجدول [ARTICLES]:

Image

تم شراء الصنفين [couteau] و [cuiller]، وتم خصم الكمية المشتراة من مخزونهما. لم يتم شراء الصنف [fourchette] لأن الكمية المطلوبة تجاوزت الكمية المتوفرة في المخزون. ندعو القارئ إلى إجراء اختبارات إضافية.

2.5.6.9.2. مصدر البيانات MSDE

نقوم هنا باختبار مصدر البيانات MSDE الذي تمت دراسته في الفقرة 2.4.3.1. ويُستخدم هنا من خلال SqlMap. ونتبع نفس الإجراء الذي اتبعناه سابقًا. ونقوم بإجراء التعديلات التالية على محتوى الملف [runtime]:

  • لم يتغير محتوى المجلد [bin]
  • في [runtime]، تتغير ملفات التكوين الخاصة بـ SqlMap و [providers.config, properties.xml]. لا تتغير ملفات التكوين الخاصة بـ [sqlmap.config, articles.xml].
  • يُهيئ الملف [providers.config] <provider> جديدًا:
<?xml version="1.0" encoding="utf-8" ?> 

<providers>
    <clear/>
    <provider
        name="sqlServer1.1"
        assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
        connectionClass="System.Data.SqlClient.SqlConnection"
        commandClass="System.Data.SqlClient.SqlCommand"
        parameterClass="System.Data.SqlClient.SqlParameter"
        parameterDbTypeClass="System.Data.SqlDbType"
        parameterDbTypeProperty="SqlDbType"
        dataAdapterClass="System.Data.SqlClient.SqlDataAdapter"
        commandBuilderClass="System.Data.SqlClient.SqlCommandBuilder"    
        usePositionalParameters = "false"    
        useParameterPrefixInSql = "true"
        useParameterPrefixInParameter = "true"                
        parameterPrefix="@"
    />        
</providers>

يستخدم هذا <provider> فئات .NET للوصول إلى مصادر البيانات SQL Server. وهو مدمج بشكل قياسي في ملف القالب [providers.config] الموزع مع SqlMap.

  • يحدد الملف [properties.xml] <provider> لمصدر MSDE بالإضافة إلى سلسلة الاتصال الخاصة به:
<?xml version="1.0" encoding="utf-8" ?> 
<settings>
    <add key="provider" value="sqlServer1.1" />
    <add 
        key="connectionString" 
        value="Data Source=portable1_tahe\msde140405;Initial Catalog=dbarticles;UID=admarticles;PASSWORD=mdparticles;"/>
</settings>
  • في [runtime]، لا يتغير ملف التكوين [web.config].

نحن جاهزون لإجراء الاختبارات. يحتفظ خادم الويب [Cassini] بتكوينه المعتاد. نقوم بتهيئة جدول المقالات في المصدر MSDE باستخدام [EMS MS SQL Manager]:

Image

باستخدام متصفح، نطلب URL [http://localhost/webarticles/main.aspx]:

Image

الآن دعونا نتحقق من محتوى الجدول [ARTICLES] مع [EMS MS SQL Manager]:

Image

تم شراء الصنفين [ballon foot] و [raquette tennis]، وتم خصم الكمية المشتراة من مخزونهما. لم يتم شراء الصنف [rollers] لأن الكمية المطلوبة تجاوزت الكمية المتوفرة في المخزون. ندعو القارئ إلى إجراء اختبارات إضافية.

2.5.6.9.3. مصدر البيانات OleDb

نقوم هنا باختبار مصدر البيانات ACCESS المقدم في الفقرة 2.4.5.1. ويُستخدم هنا من خلال SqlMap. ونتبع نفس الإجراء الذي اتبعناه سابقًا. ونقوم بإجراء التعديلات التالية على محتوى الملف [runtime]:

  • لم يتغير محتوى المجلد [bin]
  • في ملف [runtime]، تتغير ملفات التكوين الخاصة بـ SqlMap و [providers.config, properties.xml]. أما ملفات التكوين الخاصة بـ [sqlmap.config, articles.xml] فلا تتغير.
  • يقوم الملف [providers.config] بتكوين <provider> جديد:
<?xml version="1.0" encoding="utf-8" ?> 

<providers>
    <clear/>
    <provider 
        name="OleDb1.1" 
        enabled="true" 
        assemblyName="System.Data, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" 
        connectionClass="System.Data.OleDb.OleDbConnection" 
        commandClass="System.Data.OleDb.OleDbCommand" 
        parameterClass="System.Data.OleDb.OleDbParameter" 
        parameterDbTypeClass="System.Data.OleDb.OleDbType" 
        parameterDbTypeProperty="OleDbType" 
        dataAdapterClass="System.Data.OleDb.OleDbDataAdapter" 
        commandBuilderClass="System.Data.OleDb.OleDbCommandBuilder" 
        usePositionalParameters = "true"
        useParameterPrefixInSql = "false"
        useParameterPrefixInParameter = "false"
        parameterPrefix = ""
    />
</providers>

يستخدم هذا <provider> فئات .NET للوصول إلى مصادر البيانات OleDb. وهو مدمج بشكل قياسي في الملف النموذجي [providers.config] الموزع مع SqlMap.

  • يحدد الملف [properties.xml] <provider> لمصدر البيانات OleDb بالإضافة إلى سلسلة الاتصال الخاصة به:
<?xml version="1.0" encoding="utf-8" ?> 
<settings>
    <add key="provider" value="OleDb1.1" />
    <add 
        key="connectionString" 
        value="Provider=Microsoft.Jet.OLEDB.4.0;Data Source=D:\data\serge\databases\access\articles\articles.mdb;"/>
</settings>
  • في [runtime]، لا يتغير ملف التكوين [web.config].

نحن جاهزون لإجراء الاختبارات. يحتفظ خادم الويب [Cassini] بتكوينه المعتاد. نقوم بتهيئة جدول المقالات من المصدر ACCESS بالطريقة التالية:

Image

باستخدام متصفح، نطلب URL [http://localhost/webarticles/main.aspx]:

Image

الآن دعونا نتحقق من محتوى الجدول [ARTICLES] باستخدام:

Image

تم شراء الصنفين [pantalon] و [jupe]، وتم خصم الكمية المشتراة من مخزونهما. لم يتم شراء الصنف [manteau] لأن الكمية المطلوبة تجاوزت الكمية المتوفرة في المخزون. ندعو القارئ إلى إجراء اختبارات إضافية.

2.5.7. الخلاصة

نختتم هنا هذا المقال التعليمي الطويل. ماذا قمنا به؟

  • لقد قمنا بتنفيذ الطبقة [dao] لتطبيق ويب ثلاثي الطبقات بأربع طرق مختلفة:
    1. باستخدام فئات الوصول .NET إلى المصادر ODBC
    2. باستخدام فئات الوصول .NET إلى مصادر SQL Server
    3. باستخدام فئات الوصول .NET إلى المصادر OleDb
    4. باستخدام فئات الوصول الخاصة بطرف ثالث للوصول إلى قاعدة بيانات Firebird
  • في كل مرة، قمنا بدمج الطبقة الجديدة [dao] في التطبيق [webarticles] المكون من ثلاث طبقات [web, domain, dao] دون إعادة تجميع أي من الطبقات [web, domain]
  • وأخيرًا، قدمنا الأداة [SqlMap] التي مكنتنا من إنشاء طبقة [dao] قادرة على التكيف مع مصادر البيانات المختلفة بطريقة شفافة بالنسبة للكود. وهكذا، وبفضل هذه الطبقة الجديدة، تمكنا من استخدام مصادر البيانات الخاصة بالتطبيقات السابقة من 1 إلى 3 تباعًا. وقد تم ذلك بشكل شفاف باستخدام ملفات التكوين.
  • وقد أظهرنا المرونة الكبيرة التي توفرها أدوات Spring وSqlMap لتطبيقات الويب ثلاثية الطبقات.