Skip to content

2. بخش ۲

2.1. Introduction

ما با مرور آنچه در بخش اول پوشش داده شد، به ویژه معماری سه‌لایه [web, domain, dao] مورد استفاده، شروع خواهیم کرد. در راه‌حل پیشنهادی، لایه [dao] یک لایه آزمایشی بود: منبع داده توسط یک شیء [ArrayList] پیاده‌سازی شده بود. در این مقاله، ما بر لایه [dao] تمرکز می‌کنیم و پیاده‌سازی‌های مختلف ممکن آن را زمانی که داده‌ها در یک SGBD ذخیره می‌شوند، ارائه می‌دهیم.

ابزارهای مورد استفاده:

  • Firebird SGBD – به ضمیمه، بند ۳.۵ مراجعه کنید.
  • SGBD MSDE (موتور داده مایکروسافت) – رجوع شود به ضمیمه، بند ۳.۱۲.
  • IBExpert، نسخهٔ شخصی برای مدیریت گرافیکی SGBD Firebird – رجوع شود به ضمیمه، بند 3.6.
  • EMS MS SQL مدیر برای مدیریت گرافیکی SGBD MSDE - ضمیمه، بند ۳.۱۴ را ببینید.
  • 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 ]
  • استفاده از جنبه IoC از Spring: [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. برنامهٔ وب‌مقالات – خلاصه

در اینجا اجزای وب‌اپلیکیشن تجارت الکترونیک ساده‌شده‌ای را که در بخش اول مورد بحث قرار گرفت، ارائه می‌دهیم. این اپلیکیشن به کاربران وب اجازه می‌دهد:

  • مشاهده فهرست اقلام از یک پایگاه داده
  • تا برخی از آنها را به سبد خرید آنلاین اضافه کنند
  • تا انتخاب خود را تأیید کنند. تنها اثر این تأیید، به‌روزرسانی موجودی اقلام خریداری‌شده در پایگاه داده است.

2.2.1. نماهای برنامه

نماهای مختلفی که به کاربر ارائه می‌شوند به شرح زیر است:

  • نمای [ERREURS]، که هرگونه خطای برنامه را گزارش می‌کند

Image

2.2.2. معماری کلی برنامه

برنامه‌ای که در بخش اول ساخته شد، دارای معماری سه‌لایه است:

  • سه لایه از طریق استفاده از رابط‌ها مستقل شده‌اند
  • یکپارچه‌سازی لایه‌های مختلف با استفاده از Spring انجام شد
  • هر لایه فضای نام خود را دارد: وب (لایه UIدامنه (لایه کسب‌وکار) و DAO (لایه دسترسی به داده).

این برنامه از معماری MVC (مدل–نما–کنترل‌کننده) پیروی می‌کند. اگر به نمودار لایه‌ای بالا مراجعه کنیم، معماری MVC به شرح زیر در آن جای می‌گیرد:

پردازش یک درخواست مشتری مراحل زیر را دنبال می‌کند:

  1. کلاینت درخواستی را به کنترلر ارسال می‌کند. در این مورد، کنترلر یک صفحه .aspx است که نقش مشخصی ایفا می‌کند. این صفحه تمام درخواست‌های کلاینت را مدیریت می‌کند. این نقطه ورود به برنامه است. این «C» در MVC است.
  2. کنترل‌کننده این درخواست را پردازش می‌کند. برای این کار، ممکن است به کمک لایه کسب‌وکار، که در معماری MVC به عنوان مدل «M» شناخته می‌شود، نیاز داشته باشد.
  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» مخصوص به خود قرار دارد:

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\work\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] را پیشنهاد خواهیم داد. هر یک از این‌ها می‌تواند بدون هیچ تغییری در لایه‌های [domain] و [web]، جایگزین لایه فعلی [dao] شود. این انعطاف‌پذیری به این دلیل حاصل می‌شود که:

  • لایه [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

         ' fields خصوصی
        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 قرار دارند. معلوم است که در ویندوز، تقریباً همه محصولات SGBD موجود در بازار دارای درایور ODBC هستند. مزیت این راه‌حل این است که SGBD را می‌توان به‌طور شفاف برای برنامه تغییر داد. نقطه ضعف این است که یک درایور ODBC که تنها از ویژگی‌های مشترک تمام دستگاه‌های SGBD استفاده می‌کند، عموماً کارایی کمتری نسبت به درایوری دارد که به طور خاص برای بهره‌برداری از تمام پتانسیل یک دستگاه SGBD نوشته شده است. برای مشاهده مثالی از نحوه ایجاد یک منبع ODBC به بخش 3.7 مراجعه کنید.

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: شناسه کاربر
             ' رمز عبور: رمز عبور آنها
...
        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

توضیحات:

  • خط ۳: فضای نام حاوی کلاس‌های .NET برای دسترسی به منابع ODBC وارد می‌شود
  • خط ۱۱ – اتصال به منبع ODBC را ذخیره می‌کند
  • خط ۱۲ – نام منبع داده DSN را ذخیره می‌کند
  • خطوط ۱۳–۱۹ – متغیرهای خصوصی از نوع [OdbcCommand] که پرس‌وجوهای SQL را تعریف می‌کنند و توسط متدهای مختلف کلاس استفاده می‌شوند
  • خطوط ۲۲–۲۷ – سازنده (کنستراکتور). این متد، عناصری را دریافت می‌کند که به آن امکان ساخت شیء [OdbcConnection] را می‌دهد، که کد را به منبع داده ODBC پیوند خواهد داد.
  • خطوط ۲۹–۳۱ – متد افزودن یک آیتم
  • خطوط ۳۳–۳۵ – متد تغییر موجودی یک آیتم
  • خطوط ۳۷–۳۹ – متدی که تمام اقلام را از منبع داده ODBC حذف می‌کند
  • خطوط ۴۱–۴۳ – متدی که فهرست تمام اقلام را از منبع داده ODBC بازیابی می‌کند
  • خطوط ۴۵–۴۷ – متد بازیابی یک آیتم مشخص
  • خطوط ۴۹–۵۱ – متدی که به شما امکان می‌دهد فیلدهای خاصی از یک آیتم را که شماره آن را در اختیار دارید، ویرایش کنید
  • خطوط ۵۳–۵۵ – متد حذف یک آیتم که شماره‌اش مشخص است
  • خطوط ۵۷–۶۰ – متد کمکی برای اجرای [SELECT] بر روی منبع داده و بازگرداندن نتیجه
  • خطوط ۶۲–۶۴ – یک متد ابزاربانی برای اجرای [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: شناسه کاربر
             ' رمز عبور: رمز عبور آنها

             'نام پایگاه داده را که به‌عنوان آرگومان ارسال شده است بازیابی می‌کند
            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

توضیحات:

  • خط ۲ – سازنده سه اطلاعات مورد نیاز برای اتصال به یک منبع ODBC را دریافت می‌کند: نام منبع DSN، نام کاربری برای اتصال و رمز عبور مربوطه.
  • خط ۸ – نام منبع DSN ذخیره می‌شود تا بتوان آن را در پیام‌های خطا درج کرد.
  • خط ۹ – شیء [OdbcConnection] نمونه برداری می‌شود. یک اتصال نمونه برداری شده، یک اتصال باز نیست. متد [open] برای باز کردن اتصال استفاده می‌شود.
  • خطوط ۱۲–۱۹ – ما پرس‌وجوهای 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] بازمی‌گرداند
  • خط ۱ – تنها پارامتر این متد، شیء [OdbcCommand] است که حاوی پرس‌وجوی [Select] برای اجرا می‌باشد.
  • خط ۷ – اتصال باز می‌شود. این اتصال در خط ۲۹ بسته خواهد شد، صرف‌نظر از اینکه خطایی رخ داده باشد یا خیر.
  • خط ۹ – شیء [OdbcDataReader] که برای پردازش نتیجه [Select] مورد نیاز است، نمونه سازی می‌شود.
  • خطوط ۱۳–۲۳ – هر سطر نتیجه از [Select] در یک شیء [Article] قرار می‌گیرد که سپس به سایر موارد در یک شیء [ArrayList] اضافه می‌شود
  • فهرست آیتم‌ها در خط ۲۵ بازگردانده می‌شود
  • هیچ استثنایی مدیریت نمی‌شود. این موارد باید توسط کدی که این متد را فراخوانی می‌کند، مدیریت شوند.

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] است.
  • اتصال در خط ۵ باز می‌شود. این اتصال در خط ۱۰ بسته خواهد شد، صرف‌نظر از اینکه خطایی رخ داده باشد یا خیر.
  • پرس‌وجوی به‌روزرسانی در خط ۷ اجرا می‌شود. نتیجه بلافاصله بازگردانده می‌شود؛ این تعداد سطرهای جدول 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
  • خطوط ۳ و ۲۰ – متد همگام‌سازی شده است. این وضعیت برای تمام متدهای دسترسی به داده صادق است. این بدان معناست که در هر لحظه تنها یک نخ (thread) می‌تواند روی منبع داده کار کند. این احتمالاً بیش از حد محافظه‌کارانه است. جایگزین‌های بهتری وجود دارد، به‌ویژه قرار دادن این عملیات‌ها در داخل تراکنش‌ها. در این مورد، این SGBD است که دسترسی همزمان را مدیریت می‌کند. ما در این مرحله نمی‌خواستیم مفهوم تراکنش‌ها را معرفی کنیم. Spring این امکان را به ما می‌دهد که آن‌ها را در لایه [domain] معرفی کنیم. ممکن است در مقاله‌ای دیگر فرصت بازگشت به این موضوع را داشته باشیم.
  • در خطوط ۵ تا ۱۲، مقادیر به پارامترهای رسمی درخواست برای شی [insertCommand] که توسط سازنده (constructor) inicialize شده است، اختصاص داده می‌شوند. بیایید این را به یاد بیاوریم:
insertCommand = New OdbcCommand("insert into ARTICLES(id, nom, prix, stockactuel, stockminimum) values (?,?,?,?,?)", connexion)

پنج مقدار مورد نیاز برای پرس‌وجو توسط خطوط ۷ تا ۱۱ فراهم می‌شوند.

  • خطوط ۱۳–۱۹: پرس‌وجو اجرا می‌شود. اگر موفق باشد، نتیجه بازگردانده می‌شود. در غیر این صورت، یک استثنای عمومی با پیام خطای صریح پرتاب می‌شود

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
                 'آماده‌سازی پرس‌وجوی DELETE
                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

توضیحات:

  • خط ۱ – این متد شماره آیتم قابل حذف را از منبع داده ODBC دریافت می‌کند. این متد تعداد ردیف‌های تحت تأثیر این عملیات را بازمی‌گرداند، c.a.d. ۱ یا ۰
  • توضیحات روش [ajouteArticle] را می‌توان در اینجا درج کرد

2.3.1.8. روش getAllArticles

            Public Function getAllArticles() As System.Collections.IList Implements IArticlesDao.getAllArticles
             'بخش انحصاری
            SyncLock Me
                Try
                     'در حال اجرای پرس‌وجوی SELECT
                    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

توضیحات:

  • خط ۱ – این متد هیچ پارامتری ندارد. این متد فهرستی از همه آیتم‌ها را از منبع داده ODBC بازمی‌گرداند
  • پرس‌وجوی [Select] که همه آیتم‌ها را بازیابی می‌کند، به متد [executeQuery] ارسال می‌شود – خط ۶
  • فهرست به‌دست‌آمده در خط ۸ بازگردانده می‌شود
  • خطوط ۹–۱۲ هرگونه استثنا را مدیریت می‌کنند

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

توضیحات:

  • خط ۱ – متد شماره آیتم مورد نظر را به‌عنوان پارامتر می‌پذیرد. اگر آیتم در منبع ODBC یافت شود، این شماره را بازمی‌گرداند؛ در غیر این صورت، مرجع [nothing] را بازمی‌گرداند.
  • پرس‌وجوی [Select] که درخواست آیتم را انجام می‌دهد، در خطوط ۵–۸ اولیه می‌شود
  • این در خط ۱۲ اجرا می‌شود – یک لیست از آیتم‌ها بازگردانده می‌شود
  • اگر این فهرست خالی باشد، مرجع [nothing] در خط 14 بازگردانده می‌شود
  • در غیر این صورت، تنها آیتم موجود در لیست در خط ۱۶ بازگردانده می‌شود
  • خطوط 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

توضیحات:

  • خط ۱ – این متد هیچ پارامتری نمی‌گیرد و چیزی بازنمی‌گرداند
  • خط ۶ – پرس‌وجوی حذف همه آیتم‌ها اجرا می‌شود
  • خطوط ۷–۱۰: اگر استثنا رخ دهد، رسیدگی می‌شود

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

توضیحات:

  • خط ۱ – این متد به عنوان پارامتر، شماره آیتم مورد نظر برای اصلاح موجودی و همچنین مقدار افزایش موجودی (مثبت یا منفی) را دریافت می‌کند. این متد تعداد خطوط اصلاح‌شده توسط عملیات c.a.d را برمی‌گرداند: ۰ یا ۱.
  • خطوط ۵–۱۰: پرس‌وجوی [updateStockCommand] مقداردهی اولیه می‌شود. متن پرس‌وجوی SQL را به یاد بیاورید:
            updateStockCommand = New OdbcCommand("update ARTICLES set stockactuel=stockactuel+? where id=? and (stockactuel+?)>=0", connexion)

توجه کنید که موجودی تنها در صورتی تغییر می‌کند که پس از تغییر، همچنان ≥۰ باشد.

  • درخواست به‌روزرسانی موجودی کالا در خط ۱۳ اجرا می‌شود و نتیجه بازگردانده می‌شود
  • خطوط ۱۴–۱۸: رسیدگی به یک استثنای احتمالی

2.3.2. تولید اسمبلی برای لایه [dao]

پروژهٔ Visual Studio برای این نسخهٔ جدید لایهٔ [dao] ساختار زیر را دارد:

Image

پروژه طوری پیکربندی شده است که یک DLL به نام [webarticles-dao.dll] تولید کند:

2.3.3. تست‌های NUnit برای لایه [dao]

2.3.3.1. ایجاد یک source داده ODBC-Firebird

برای آزمایش لایه جدید [dao]، به یک منبع داده ODBC و در نتیجه یک پایگاه داده نیاز داریم. ما از پایگاه داده Firebird (بخش 3.5) استفاده می‌کنیم. با استفاده از IBExpert (بخش 3.6)، پایگاه داده محصول زیر را ایجاد می‌کنیم:

مدیر این پایگاه داده کاربر [SYSDBA] با رمز عبور [masterkey] خواهد بود. ما چند مقاله ایجاد می‌کنیم:

Image

اکنون سورس Firebird زیر را ایجاد می‌کنیم: ODBC (به بخش 3.7 مراجعه کنید):

فایل منبع ایجادشده ODBC دارای ویژگی‌های زیر است:

  • نام DSN: مقالات-odbc-firebird
  • شناسه اتصال: SYSDBA
  • رمز عبور مرتبط: masterkey

2.3.3.2. NUnit test class

ما قبلاً یک کلاس تست برای لایه [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 درخواست می‌شود تا مرجعی از singleton با نام [articlesdao] از نوع [IArticlesDao]، یعنی از نوع interface، ارائه دهد. این singleton [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] را بدون هیچ‌گونه تغییر یا بازکامپایل کردن آزمایش کنیم.

  • در پوشه Visual Studio مربوط به لایه جدیدمان [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>

توضیحات:

  • خط ۶: شیء [articlesdao] اکنون با یک نمونه از کلاس [ ArticlesDaoPlainODBC] مرتبط است
  • این کلاس دارای یک سازنده با سه آرگومان است:
  • نام منبع DSN – خط ۸
  • هویتی که برای دسترسی به پایگاه داده استفاده می‌شود – خط ۱۱
  • رمز عبور مرتبط با این شناسه – خط 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 به جای یک کلاس، به یک رابط ارجاع داده بود؛
  • زیرا نام دقیق کلاسِ پیاده‌سازِ رابط در یک فایل پیکربندی ارائه شده بود، نه در کد؛
  • زیرا اسپرینگ مسئول ایجاد نمونه از کلاس و ارائه مرجع آن در کد تست بود؛

بنابراین، کد تست نوشته شده برای لایه اولیه [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>

توضیحات:

  • خطوط ۱۴ تا ۲۴، کلاس تک‌نسخه‌ای [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 ذخیره شده‌اند. مایکروسافت یک SGBD به نام MSDE ارائه می‌دهد که نسخه‌ای محدود از SQL Server است. دستورالعمل‌های نحوه دریافت و نصب آن را می‌توانید در ضمیمه، بخش 3.12 بیابید.

2.4.1. کد

کلاس [ArticlesDaoSqlServer] بسیار شبیه به کلاس [ArticlesDaoPlainODBC] است که قبلاً مورد بحث قرار گرفت. بنابراین، ما فقط تغییرات اعمال‌شده در نسخه قبلی را برجسته خواهیم کرد:

  • کلاس‌های مورد نیاز به جای فضای نام [System.Data.Odbc]، در فضای نام [System.Data.SqlClient] قرار دارند
  • نوع اتصال [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: شناسه کاربر
             ' رمز عبور: رمز عبور کاربر

             ': نام پایگاه داده که به‌عنوان آرگومان ارسال شده است، بازیابی می‌شود
            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: شناسه کاربر
             ' رمز عبور: رمز عبور آنها

کد کامل کلاس [ArticlesDaoSqlServer] به شرح زیر است:

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

Namespace istia.st.articles.dao

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

         ' fields خصوصی
        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: شناسه کاربر
             ' رمز عبور: رمز عبور کاربر

             ': نام پایگاه داده که به‌عنوان آرگومان ارسال شده است، بازیابی می‌شود
            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
                     'در حال اجرای پرس‌وجوی SELECT
                    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 و در نتیجه سرور SGBD SQL نیاز داریم. در واقع ما از SGBD MSDE استفاده خواهیم کرد (موتور داده MicroSoft) (بخش ۳.۱۲)، که نسخه‌ای از سرور 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>

توضیحات:

  • خط ۷: شیء [articlesdao] اکنون با یک نمونه از کلاس [ ArticlesDaoSqlServeur] مرتبط است
  • این کلاس دارای یک سازنده با چهار آرگومان است:
  • نام نمونهٔ MSDE مورد استفاده – خط ۹
  • نام پایگاه داده – خط ۱۲
  • هویتی که برای دسترسی به پایگاه داده استفاده می‌شود – خط ۱۵
  • رمز عبور مرتبط با این شناسه – خط ۱۸

در اینجا ما از جزئیات منبع 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>

توضیحات:

  • خطوط ۱۵–۳۳ یک نمونه از کلاس جدید [ArticlesDaoSqlServer] را با کلاس تک‌نمونه [articlesDao] مرتبط می‌کنند. این تنها تغییر است. ما قبلاً در حین آزمایش لایه جدید [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 im

پیاده‌سازی سوم لایه [dao] فرض می‌کند که داده‌ها در یک پایگاه داده قرار دارند که از طریق یک درایور OleDb قابل دسترسی است. اصل پشت منابع OleDb مشابه اصل منابع ODBC است. یک برنامه که از منبع OleDb استفاده می‌کند، این کار را از طریق یک رابط استاندارد مشترک برای همه منابع OleDb انجام می‌دهد. تغییر منبع OleDb صرفاً مستلزم تغییر درایور OleDb است. خود کد بدون تغییر باقی می‌ماند.

می‌توانید با استفاده از Visual Studio بفهمید کدام درایورهای OleDb روی سیستم شما موجود هستند:

  • با استفاده از [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
متن – ۲۰ کاراکتر –
prix
عددی - اعشاری
stockactuel
عددی - صحیح
stockminimum
عددی – صحیح
  • بیایید به ویژوال استودیو بازگردیم و همان‌طور که قبلاً توضیح داده شد، یک اتصال جدید ایجاد کنیم:

Image

  • درایور [Microsoft Jet 4.0] را انتخاب کرده و به پنل [Connexion] می‌رویم:

Image

  • با استفاده از دکمه [1]، پایگاه داده ACCESS را که به تازگی ایجاد شده است انتخاب کنید، سپس با استفاده از دکمه [Terminer] تعریف اتصال را تکمیل کنید. اتصالی که ایجاد کرده‌اید اکنون در لیست اتصالات موجود ظاهر می‌شود:

Image

  • دو بار کلیک کردن روی جدول [ARTICLES] دسترسی ما را به محتویات آن فراهم می‌کند:

Image

  • سپس می‌توانید سطرها را در جدول اضافه، ویرایش یا حذف کنید.
  • اتصال جدید را در Server Explorer انتخاب کنید تا صفحهٔ ویژگی‌های آن باز شود:

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.Odbc] در فضای نام [System.Data.OleDb] قرار دارند
  • اتصال از نوع [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

         ' fields خصوصی
        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
                     'در حال اجرای پرس‌وجوی SELECT
                    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
                 ' آماده‌سازی پرس‌وجوی DELETE
                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] از پروژه [dao-odbc] (سمت چپ)، پوشه [tests] را (در سمت راست) در داخل پوشه Visual Studio برای پروژه [dao-oledb] ایجاد می‌کنیم:
  • در پوشه [tests] پروژه [dao-oledb]، ما DLL و [webarticles-dao.dll] را با DLL و QZXW2HTMLP0 جایگزین می‌کنیم.01049ZQX، تولیدشده از پروژه [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>

توضیحات:

  • خط ۷: شیء [articlesdao] اکنون با یک نمونه از کلاس [ ArticlesDaoOleDb] مرتبط است
  • این کلاس یک سازنده با یک آرگومان دارد: رشته اتصال به پایگاه داده OleDb ACCESS – خط ۹
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>

توضیحات:

  • خطوط ۱۴–۱۸ یک نمونه از کلاس جدید [ArticlesDaoOleDb] را با کلاس تک‌نمونه [articlesDao] مرتبط می‌کنند. این تنها تغییر است.

ما پیکربندی یکسان را برای سرور وب [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/] قابل دانلود است، استفاده کرد. صفحه دانلود پیوندهای زیر را ارائه می‌دهد (آوریل ۲۰۰۵):

Image

لینک [firebird-net-provider] لینکی است که برای دانلود کلاس‌های .NET جهت دسترسی به SGBD Firebird باید استفاده شود. نصب این بسته، پوشه‌ای مشابه با موارد زیر ایجاد می‌کند:

Image

دو مورد قابل توجه وجود دارد:

  • [FirebirdSql.Data.Firebird.dll]: اسمبلی حاوی کلاس‌های .NET برای دسترسی به پایگاه داده Firebird SGBD
  • [FirebirdNETProviderSDK.chm]: مستندات این کلاس‌ها

سپس، برای اینکه یک پروژه ویژوال استودیو بتواند از این کلاس‌ها استفاده کند، دو کار انجام می‌دهیم:

  • مجموعه [FirebirdSql.Data.Firebird.dll] را در پوشه [bin] پروژه قرار دهید
  • این اسمبلی را به مراجع پروژه اضافه کنید

2.5.2. کد کلاس [ArticlesDaoFirebirdProvider]

کلاس [ArticlesDaoFirebirdProvider] بسیار شبیه به کلاس [ArticlesDaoSqlServer] است که قبلاً مورد بحث قرار گرفت. بنابراین، ما فقط تغییرات ایجاد شده در مقایسه با آن نسخه را برجسته خواهیم کرد:

  • کلاس‌های مورد نیاز به جای فضای نام [System.Data.SqlClient] در فضای نام [FirebirdSql.Data.Firebird] قرار دارند
  • نوع اتصال [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: شناسه کاربری که در حال ورود است
             ' رمز عبور: رمز عبور آنها
...
        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

         ' fields خصوصی
        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: نام کاربری کاربری که به پایگاه داده وارد می‌شود
             ' رمز عبور: رمز عبور آنها

             ': نام پایگاه داده که به‌عنوان آرگومان ارسال شده است، بازیابی می‌شود
            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
                     'در حال اجرای پرس‌وجوی SELECT
                    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
                 'آماده‌سازی پرس‌وجوی DELETE
                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] که از QZXW تولید شده‌اند، جایگزین می‌کنیم.2HTMLP001137ZQX
  • ما در حال اصلاح فایل پیکربندی [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>

توضیحات:

  • خط ۷، شیء [articlesdao] اکنون با یک نمونه از کلاس [ArticlesDaoFirebirdProvider] مرتبط است
  • این کلاس یک سازنده با چهار آرگومان دارد
  • ماشین میزبان برای SGBD – خط ۹
  • مسیر پایگاه داده Firebird – خط ۱۲
  • نام کاربری کاربر متصل‌شونده – خط ۱۵
  • رمز عبور آنها – خط ۱۸

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>

توضیحات:

  • خطوط ۱۴–۲۷، کلاس تک‌نسخه (singleton) [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] را بدون نیاز به کامپایل مجدد دو لایه دیگر، [web] و [domain]، در برنامه [webarticles] ادغام کنیم. لازم به ذکر است که این امر از طریق دو انتخاب معماری محقق شد:

  • دسترسی به لایه‌ها از طریق رابط‌ها
  • یکپارچه‌سازی لایه‌ها با استفاده از Spring

ما می‌خواهیم این موضوع را یک قدم فراتر ببریم. اگرچه متفاوت هستند، چهار پیاده‌سازی ما از لایه [dao] شباهت‌های قابل توجهی دارند. پس از نوشتن پیاده‌سازی اول، سه پیاده‌سازی دیگر تقریباً به طور کامل با کپی-پیست و جایگزینی برخی کلمات کلیدی با یکدیگر تولید شدند. خودِ منطق بدون تغییر باقی ماند. ممکن است این سؤال پیش بیاید که آیا می‌توان یک پیاده‌سازی واحد داشت که ما را از سر و کار داشتن با روش‌های مختلف دسترسی به داده‌ها رها کند. ما از چهار روش استفاده کرده‌ایم:

  • دسترسی از طریق یک درایور ODBC به یک منبع داده ODBC
  • دسترسی مستقیم به پایگاه داده سرور SQL
  • دسترسی از طریق درایور Ole Db به یک منبع داده Ole Db
  • دسترسی مستقیم به پایگاه داده Firebird

ابزار Ibatis SqlMap [[http://www.ibatis.com/] توسعه لایه‌های دسترسی به داده را که مستقل از ماهیت واقعی منبع داده هستند، امکان‌پذیر می‌سازد. دسترسی به داده از طریق:

  • فایل‌های پیکربندی حاوی اطلاعاتی که منبع داده و عملیات‌های قابل انجام بر روی آن را تعریف می‌کنند
  • کتابخانه‌ای از کلاس‌ها که از این اطلاعات برای دسترسی به داده‌ها استفاده می‌کنند

ابزار Ibatis SqlMap در ابتدا برای پلتفرم جاوا توسعه داده شد. نسخهٔ آن برای پلتفرم .NET جدید است و به نظر می‌رسد تا حدی دارای باگ باشد (یک نظر شخصی که نیازمند بررسی دقیق است). با این حال، از آنجا که این ابزار ارزش خود را در پلتفرم جاوا اثبات کرده است، ارائه نسخه .NET نیز ارزشمند به نظر می‌رسد.

2.5.6.2. از کجا می‌توانم IBATIS SqlMap را پیدا کنم؟

وب‌سایت اصلی Firebird [http://www.ibatis.com/] است. صفحه دانلود پیوندهای زیر را ارائه می‌دهد:

Image

لینک [Stable Binaries] را انتخاب کنید که شما را به [SourceForge.net] هدایت می‌کند. فرآیند دانلود را تا انتها دنبال کنید. یک فایل zip دریافت خواهید کرد که شامل فایل‌های زیر است:

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> ممکن است فعال ([enabled=true]) یا غیرفعال ([enabled=false]) باشد. اگر فعال باشد، DLL که در خط ۸ به آن ارجاع شده است باید قابل دسترسی باشد. یک فایل [providers.config] ممکن است حاوی چندین تگ <provider> باشد.
  • خط ۸ – نام اسمبلی حاوی کلاس‌های تعریف‌شده در خطوط ۹ تا ۱۵
  • خط ۹ – کلاسی که برای ایجاد یک اتصال استفاده می‌شود
  • خط ۱۰ – کلاسی که برای ایجاد یک شیء [Command] جهت صدور دستورات SQL استفاده می‌شود
  • خط ۱۱ – کلاسی که برای مدیریت پارامترهای یک دستور SQL پیکربندی‌شده استفاده می‌شود
  • خط ۱۲ – کلاسی که انواع داده‌های ممکن برای فیلدهای یک جدول را فهرست می‌کند
  • خط ۱۳ – نام ویژگی یک شیء [Parameter] که نوع مقدار این پارامتر را در خود دارد
  • خط ۱۴ – نام کلاس [Adapter] که برای ایجاد اشیاء [DataSet] از منبع داده استفاده می‌شود
  • خط ۱۵ – نام کلاس [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)

در مورد اول، این‌ها به عنوان پارامترهای موقعیتی رسمی شناخته می‌شوند. مقادیر واقعی آن‌ها باید به ترتیب پارامترهای رسمی ارائه شوند. در مورد دوم، این‌ها پارامترهای نام‌گذاری شده هستند. برای چنین پارامترهایی، مقدار با مشخص کردن نام آن ارائه می‌شود. ترتیب دیگر مهم نیست.

  • خط ۱۶ – این نشان می‌دهد که منابع ODBC از پارامترهای موقعیتی استفاده می‌کنند
  • خطوط ۱۷–۱۹ – مربوط به پارامترهای نام‌گذاری شده هستند. در اینجا هیچ‌کدام وجود ندارد.

این اطلاعات به SqlMap امکان می‌دهد تا برای مثال، تعیین کند که برای ایجاد یک اتصال، کدام کلاس را باید نمونه‌سازی کند. در این مورد، این کلاس [OdbcConnection] (خط ۹) خواهد بود.

2.5.6.4.2. sqlmap.config

فایل [providers.config] کلاس‌هایی را که برای دسترسی به یک منبع 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>

توضیحات:

  • خط ۳ – یک فایل ویژگی‌ها، [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>

خط ۳ – کلید [provider] تعریف شده است. مقدار آن نام تگ <provider> است که باید در [providers.config] استفاده شود.

خط ۴ – کلید [connectionString] تعریف شده است. مقدار آن رشته اتصال است که برای برقراری ارتباط با منبع داده Firebird ODBC استفاده می‌شود.

  • خطوط ۴–۷ – پارامترهای پیکربندی:
    • خط ۵ – پرس‌وجوها برای SQL با نامی شناسایی می‌شوند که خود می‌تواند بخشی از یک فضای نام باشد. [useStatementNamespaces="false"] نشان می‌دهد که از فضاهای نام استفاده نخواهد شد.
    • خط ۶ – SqlMap دارای استراتژی‌های مختلف کشینگ برای به حداقل رساندن دسترسی به منبع داده است. [cacheModelsEnabled="false"] نشان می‌دهد که هیچ‌یک استفاده نخواهد شد.
  • خطوط ۹–۱۳ – ویژگی‌های منبع داده تعریف می‌شوند:
    • خط ۱۰ – نام <provider> از [providers.config] که باید استفاده شود
    • خط ۱۱ – رشته اتصال به منبع داده
    • خط ۱۲ – مدیر تراکنش. ما در اینجا از آن استفاده نکرده‌ایم، اما به هر حال این خط را باقی گذاشته‌ایم زیرا در فایل توزیع استاندارد گنجانده شده بود.
  • خطوط ۱۴–۱۶ – فهرست فایل‌هایی که عملیات SQL را که باید روی منبع داده انجام شود، تعریف می‌کنند.
  • خط ۱۵ – فایل نگاشت [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#, #name#, #price#, #currentstock#, #minimumstock#)
            </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= #name#, PRIX= #price#, STOCKACTUEL= #current_stock# ,STOCKMINIMUM= #حداقل موجودی# که 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 + #movement#) where ID=#id# and ((STOCKACTUEL + #movement#) >=0)
      </update>
    </statements>
</sqlMap>

توضیحات:

  • خطوط ۴–۱۱ – یک نگاشت بین یک سطر در جدول [ARTICLES] در منبع داده و کلاس [istia.st.articles.dao.Article] تعریف شده است. هر ستون در جدول با یک ویژگی از کلاس [Article] مرتبط است. این نگاشت به [SqlMap] امکان می‌دهد تا نتیجهٔ یک عملیات SQL SELECT را بسازد. هر سطر نتیجه از SELECT بر اساس قواعد نگاشت در یک شیء [Article] قرار داده می‌شود.
  • خط ۵ – نگاشت در داخل تگ <resultMap> قرار گرفته و با استفاده از ویژگی [id="article"] نام‌گذاری می‌شود. کلاس مربوطه با ویژگی [class="istia.st.articles.dao.Article"] مشخص می‌شود.
  • خطوط 14–44 – عملیات مورد نیاز SQL تعریف شده‌اند
  • خطوط 16–18 – یک عملیات SELECT تعریف شده و با نام [getAllArticles] نام‌گذاری می‌شود
    • خط 16 – عملیات SELECT با نام [name= "getAllArticles "] تعریف شده و نگاشت مورد استفاده توسط ویژگی [resultMap="article"] مشخص می‌شود. بنابراین این به نگاشت در خطوط 5–11 اشاره دارد
    • خط ۱۷ – متن دستور SQL برای اجرا
  • خطوط ۲۰–۲۲ – دستور DELETE در SQL با شناسه [clearAllArticles] برای پاک کردن جدول محصولات تعریف شده است.
  • خطوط 24–27 – دستور SQL-Insert با شناسه [insertArticle] برای افزودن یک آیتم جدید به جدول items تعریف شده است. این یک پرس‌وجو است که با عناصر (#id#, #name#, #price#, #currentstock#, #minimumstock#) پارامترگذاری شده است. مقادیر این پنج عنصر از یک شیء [Article] که به‌عنوان پارامتر ارسال می‌شود، تأمین خواهند شد: [parameterClass="istia.st.articles.dao.Article"]. ابژه پارامتر باید دارای خواص (id, name, price, current_stock, minimum_stock) باشد که در فرمان پارامتریک SQL به آنها ارجاع شده است.
  • خطوط ۲۹–۳۱ – ما فرمان SQL Delete [deleteArticle] را تعریف می‌کنیم که برای حذف موردی با شماره #value# استفاده می‌شود. این شماره به عنوان پارامتر ارسال خواهد شد: [parameterClass="int"]. این یک قاعده کلی است. وقتی تنها یک پارامتر وجود داشته باشد، با کلمه کلیدی #value# در متن دستور SQL به آن ارجاع داده می‌شود.
  • خطوط ۳۳–۳۵ – فرمان SQL-Update با شناسه [modifyArticle] برای اصلاح یک آیتم با شمارهٔ شناخته‌شده تعریف شده است. همانند فرمان [insertArticle]، پنج اطلاعات مورد نیاز از ویژگی‌های یک شیء با شناسه [istia.st.articles.dao.Article] گرفته می‌شود.
  • خطوط ۳۷–۳۹ – دستور SQL SELECT با شناسه [getArticleById] تعریف شده است که رکورد یک آیتم با شمارهٔ شناخته‌شده را بازیابی می‌کند.
  • خطوط ۴۱–۴۳ – دستور 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 از طریق یک نمونهٔ واحد (singleton) از نوع [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] را داریم. بنابراین، یک نشانگر null اینجا ارسال می‌شود.
  • نتیجه از نوع 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 هیچ ردیفی بازنگرداند، نتیجه یک اشاره‌گر null (هیچ چیز) است.

برای اجرای دستور 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

         ' fields خصوصی
        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
                     ' 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
                    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 در مراجع پروژه توجه کنید. این DLL در پوشه [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]، رویه‌ای مشابه را دنبال می‌کنیم:

  • ما با کپی کردن پوشه [tests] از پروژه [dao-odbc] (سمت چپ)، پوشه [test1] (سمت راست) را در داخل پوشه Visual Studio برای پروژه [dao-sqlmap] ایجاد می‌کنیم:
  • در پوشه [tests]، ما فایل‌های DLL و [webarticles-dao.dll] را با DLL و [webarticles-dao.dll] که از QZXW تولید شده‌اند، جایگزین می‌کنیم.2HTMLP001420ZQX.
  • ما فایل‌های لازم 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>

توضیحات:

  • خط ۷: شیء [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 مراجعه کنید) که منجر به سطح موجودی منفی می‌شد. در اینجا انتظار داریم نتیجه متد [changerStockArticle] برابر 0 باشد. بار دیگر، یک استثنا از نوع [IBatisNet.Common.Exceptions.ConcurrentException] داریم.

منابع متعددی برای خطا وجود دارد:

  1. کد کلاس [ArticlesDaoSqlMap] نادرست است. این امکان‌پذیر است. با این حال، این کلاس از یک کلاس جاوا منتقل شده که با نسخهٔ جاوا از SqlMap به‌درستی کار می‌کرد.
  2. نسخه .NET از SqlMap دارای باگ است
  3. درایور Firebird ODBC معیوب است
  4. ...

در غیاب اطمینان، ما با گرفتن استثناء مذکور [IBatisNet.Common.Exceptions.ConcurrentException] مشکل را دور می‌زنیم. کد جدید برای کلاس [ArticlesDaoSqlMap] به شرح زیر است:

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

    Public Class ArticlesDaoSqlMap
        Implements IArticlesDao

         ' fields خصوصی
        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>

توضیحات:

  • خط ۱۴، کلاس تک‌نسخه‌ای [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 استفاده می‌کند. این کلاس به طور استاندارد در فایل قالب [providers.config] که همراه با SqlMap توزیع می‌شود، گنجانده شده است.

  • فایل [properties.xml]، ارائه‌دهنده را برای منبع 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
    3. با استفاده از کلاس‌های دسترسی .NET برای منابع OleDb
    4. استفاده از کلاس‌های دسترسی شخص ثالث برای دسترسی به پایگاه داده Firebird
  • هر بار، لایه جدید [dao] را بدون نیاز به کامپایل مجدد هیچ‌یک از لایه‌های [web, domain, dao] و [web, domain]، در برنامه سه‌لایه [webarticles] یکپارچه کردیم
  • در نهایت ابزار [SqlMap] را معرفی کردیم که به ما امکان داد لایه‌ای به نام [dao] ایجاد کنیم که قادر بود به صورت شفاف برای کد، خود را با منابع داده مختلف تطبیق دهد. بنابراین، با این لایه جدید، ما توانستیم به ترتیب از منابع داده پیاده‌سازی‌های قبلی ۱ تا ۳ استفاده کنیم. این امر به‌صورت شفاف با استفاده از فایل‌های پیکربندی محقق شد.
  • ما انعطاف‌پذیری فوق‌العاده‌ای را که ابزارهای Spring و SqlMap برای برنامه‌های وب سه‌لایه به ارمغان می‌آورند، نشان داده‌ایم.