Skip to content

6. دراسة حالة مع PostgreSQL 9.2.1

6.1. تثبيت الأدوات

الأدوات المطلوب تثبيتها هي التالية:

  • SGBD: [http://www.enterprisedb.com/products-services-training/pgdownload#windows
  • أداة إدارة: EMS SQL Manager for PostgreSQL Freeware [http://www.sqlmanager.net/fr/products/postgresql/manager/download].

في الأمثلة التالية، كلمة مرور المستخدم postgres هي postgres.

لنقم بتشغيل PostgreSQL ثم أداة [SQL Manager Lite for PostgreSQL] التي سنستخدمها لإدارة SGBD.

  • في [1]، نقوم بتشغيل SGBD PostgreSQL من خدمات Windows؛
  • في [2]، يتم تشغيل الخدمة؛

نقوم الآن بتشغيل الأداة [SQL Manager Lite for MySQL] التي سنستخدمها لإدارة SGBD و [3].

  • في [4]، نقوم بإنشاء قاعدة بيانات جديدة؛
  • في [5]، نحدد اسم القاعدة؛
  • في [5]، نقوم بتسجيل الدخول باسم postgres / postgres؛
  • في [6]، نقدم بعض المعلومات؛
  • في [7]، يتم تأكيد الأمر SQL الذي سيتم تنفيذه؛
  • في [8]، تم إنشاء قاعدة البيانات. يجب الآن تسجيلها في [EMS Manager]. المعلومات صحيحة. نقوم بتنفيذ [OK
  • في [9]، نقوم بالاتصال بها؛
  • في [10]، يعرض [EMS Manager] قاعدة البيانات، وهي فارغة في الوقت الحالي. تجدر الإشارة إلى أن الجداول ستنتمي إلى مخطط يسمى public [11].

سنقوم الآن بربط مشروع VS 2012 بهذه القاعدة.

6.2. إنشاء قاعدة البيانات من الكيانات

نبدأ بنسخ مجلد مشروع [RdvMedecins-SqlServer-01] في [RdvMedecins-PostgreSQL-01] [1]:

  • إلى [2]، وفي VS 2012، نحذف المشروع [RdvMedecins-SqlServer-01] من الحل؛
  • في [3]، تم حذف المشروع؛
  • في [4]، نضيف مشروعًا آخر. يتم أخذ هذا المشروع من المجلد [RdvMedecins-PostgreSQL-01] الذي أنشأناه سابقًا؛
  • في [5]، يُسمى المشروع الذي تم تحميله [RdvMedecins-SqlServer-01
  • في [6]، نغير اسمه إلى [RdvMedecins-PostgreSQL-01
  • في [7]، نضيف مشروعًا آخر إلى الحل. يتم أخذ هذا المشروع من المجلد [RdvMedecins-SqlServer-01] الخاص بالمشروع الذي قمنا بحذفه من الحل سابقًا؛
  • في [8]، أعيد إدراج المشروع [RdvMedecins-SqlServer-01] في الحل.

المشروع [RdvMedecins-PostgreSQL-01] مطابق للمشروع [RdvMedecins-SqlServer-01]. علينا إجراء بعض التعديلات. في [App.config]، سنقوم بتعديل سلسلة الاتصال و [DbProviderFactory] الذي يجب تكييفه مع كل SGBD.


<!-- سلسلة الاتصال بالقاعدة -->
  <connectionStrings>
    <add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
  </connectionStrings>
  <!-- مزود المصنع -->
  <system.data>
    <DbProviderFactories>
      <add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql Server" type="Npgsql.NpgsqlFactory, Npgsql, Version=2.0.11.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7" />
    </DbProviderFactories>
  </system.data>
  • السطر 3: اسم المستخدم وكلمة المرور؛
  • الأسطر 7-9: DbProviderFactory. تشير السطر 8 إلى DLL [Npgsql] التي لا نملكها. يمكن الحصول عليها باستخدام NuGet [1]:
  • في [2]، في منطقة البحث نكتب الكلمة الرئيسية postgresql؛
  • في [3]، نختار الحزمة [Npgsql]. إنه موصل ADO.NET لـ PostgreSQL؛
  • في [4]، تمت إضافة مرجعين؛
  • في [5]، في [App.config]، يجب إدخال الإصدار الصحيح لـ DLL. يمكن العثور عليه في خصائصه.

في الملف [Entites.cs]، يجب تعديل مخطط الجداول التي سيتم إنشاؤها:


  [Table("MEDECINS", Schema = "public")]
  public class Medecin : Personne
  {...}

  [Table("CLIENTS", Schema = "public")]
  public class Client : Personne
  {...}

  [Table("CRENEAUX", Schema = "public")]
  public class Creneau
  {...}

  [Table("RVS", Schema = "public")]
  public class Rv
  {...}

لقد رأينا سابقًا عند إنشاء قاعدة بيانات PostgreSQL، أن الجداول تنتمي إلى مخطط يسمى public.

نقوم بتكوين تنفيذ المشروع:

  • في [1]، نمنح اسمًا آخر للتجميع الذي سيتم إنشاؤه؛
  • في [2]، بالإضافة إلى مساحة أسماء افتراضية أخرى؛
  • في [3]، نحدد البرنامج المراد تنفيذه.

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

Exception non gérée : System.Data.MetadataException: Le schéma spécifié n'est pas valide. Erreurs :
(11,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(23,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(33,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
(43,6) : erreur 0040: Le type rowversion n'est pas qualifié avec un espace de noms ou un alias. Seuls les types primitifs peuvent être utilisés sans qualification.
   à System.Data.Metadata.Edm.StoreItemCollection.Loader.ThrowOnNonWarningErrors
()
   ...
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\CreateDB_01.cs:ligne 15

نتذكر أننا واجهنا نفس الخطأ مع MySQL و Oracle. ويرتبط ذلك بنوع الحقل Timestamp في الكيانات. نقوم بإجراء نفس التعديل الذي أجريناه مع Oracle. في الكيانات، نستبدل الأسطر الثلاثة


    [Column("TIMESTAMP")]
    [Timestamp]
    public byte[] Timestamp { get; set; }

بالأسطر التالية:


    [ConcurrencyCheck]
    [Column("VERSIONING")]
    public int? Versioning { get; set; }

نقوم بتغيير نوع العمود من byte[] إلى int?. في SGBD، سنستخدم إجراءات مخزنة لزيادة هذا العدد الصحيح بمقدار وحدة واحدة في كل مرة يتم فيها إدراج أو تعديل سطر.

نقوم بإجراء التعديل السابق على الكيانات الأربعة ثم نعيد تشغيل التطبيق. عندها نحصل على الخطأ التالي:

1
2
3
4
5
6
Exception non gérée : System.Data.DataException: An exception occurred while initializing the database. See the InnerException for details. ---> System.Data.ProviderIncompatibleException: DeleteDatabase n'est pas pris en charge par le fournisseur.
   à System.Data.Common.DbProviderServices.DbDeleteDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
   ...
   à System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\CreateDB_01.cs:ligne 15

تشير السطر 1 إلى أن الموصل ADO.NET الخاص بـ PostgreSQL غير قادر على حذف قاعدة البيانات الموجودة. تمامًا كما هو الحال مع Oracle. لذلك، يتعين علينا إنشاء قاعدة البيانات [RDVMEDECINS-EF] يدويًا باستخدام الأداة [EMS Manager for PostgreSQL]. لن نصف جميع الخطوات، بل سنكتفي بذكر أهمها فقط.

ستكون قاعدة البيانات PostgreSQL كما يلي:

الجداول

  • في [1]، ID هو المفتاح الأساسي من النوع serial. هذا النوع PostgreSQL هو عدد صحيح يتم إنشاؤه تلقائيًا بواسطة SGBD.

تحتوي الجداول المختلفة على المفاتيح الأساسية والأجنبية التي كانت موجودة في هذه الجداول نفسها في الأمثلة السابقة. تحتوي المفاتيح الأجنبية على السمة ON DELETE CASCADE.

التسلسلات

كما هو الحال مع Oracle، قمنا هنا بإنشاء تسلسلات. وهي عبارة عن مولدات للأرقام المتتالية. يوجد منها 5 [1].

  • في [2]، نرى خصائص التسلسل [CLIENTS_ID_SEQ]. وهو يولد أرقامًا متتالية بزيادة 1، بدءًا من 1 وحتى قيمة كبيرة جدًا.

جميع التسلسلات مبنية على نفس النموذج.

  • سيتم استخدام [CLIENTS_ID_seq] لتوليد المفتاح الأساسي للجدول [CLIENTS
  • سيتم استخدام [MEDECINS_ID_seq] لتوليد المفتاح الأساسي للجدول [MEDECINS
  • سيتم استخدام [CRENEAUX_ID_seq] لتوليد المفتاح الأساسي للجدول [CRENEAUX
  • سيتم استخدام [RVS_ID_seq] لتوليد المفتاح الأساسي للجدول [RVS
  • سيتم استخدام [sequence_versions] لتوليد قيم أعمدة [VERSIONING] لجميع الجداول.

المشغلات

المشغل هو إجراء يتم تنفيذه بواسطة SGBD قبل أو بعد حدث (إدراج، تعديل، حذف) في جدول. لدينا 4 مشغلات [1]:

لنلقِ نظرة على كود DDL للمشغل [CLIENTS_tr] الذي يغذي العمود [VERSIONING] في الجدول [CLIENTS]:

1
2
3
4
CREATE TRIGGER "CLIENTS_tr"
  BEFORE INSERT OR UPDATE 
  ON public."CLIENTS" FOR EACH ROW 
EXECUTE PROCEDURE public.trigger_versions();
  • الأسطر 1-3: قبل كل عملية INSERT أو UPDATE على الجدول [CLIENTS
  • السطر 4: يتم تنفيذ الإجراء [public.trigger_versions()].

الإجراء [public.trigger_versions()] هو كما يلي:

1
2
3
4
BEGIN
NEW."VERSIONING":=nextval('sequence_versions');
return NEW;
END
  • السطر 2: يمثل "NEW" السطر الذي سيتم إدراجه أو تعديله. "NEW". و"VERSIONING" هو العمود "[VERSIONING]" لهذا السطر. يتم تعيين القيمة التالية من مولد الأرقام "sequence_versions" له. وبالتالي، يتغير العمود ["VERSIONING"] مع كل عملية INSERT / UPDATE يتم إجراؤها على الجدول [CLIENTS].

المشغلات [MEDECINS_tr, CRENEAUX_tr, RVS_tr] مماثلة. تستمد الأعمدة الأربعة ["VERSIONING"] قيمها من نفس التسلسل.

تم وضع البرنامج النصي لإنشاء جداول قاعدة البيانات PostgreSQL [RDVMEDECINS-EF] في المجلد [RdvMedecins / databases / postgreSQL]. يمكن للقارئ تحميله وتشغيله لإنشاء جداوله.

وبعد ذلك، يمكن تشغيل البرامج المختلفة للمشروع. وهي تعطي نفس النتائج التي تعطيها مع خادم SQL باستثناء البرنامج [ModifyDetachedEntities] الذي يتعطل لنفس السبب الذي تسبب في تعطله مع Oracle. يتم حل المشكلة بنفس الطريقة. يكفي نسخ البرنامج [ModifyDetachedEntities] من المشروع [RdvMedecins-Oracle-01] إلى المشروع [RdvMedecins-PostgreSQL-01].

يتعطل البرنامج [LazyEagerLoading] مع الاستثناء التالي:

1
2
3
4
Exception non gérée : System.Data.EntityCommandExecutionException: Une erreur s'est produite lors de l'exécution de la définition de la commande. Pour plus de détails, consultez l'exception interne. ---> Npgsql.NpgsqlException: ERREUR: 42601: erreur de syntaxe sur ou près de « LEFT »
   à Npgsql.NpgsqlState.<ProcessBackendResponses_Ver_3>d__a.MoveNext()
   ...
   à RdvMedecins_01.LazyEagerLoading.Main(String[] args) dans d:\data\istia-1213\c#\dvp\Entity Framework\RdvMedecins\RdvMedecins-PostgreSQL-01\LazyEagerLoading.cs:السطر 23

الرمز الخاطئ هو التالي:


      using (var context = new RdvMedecinsContext())
      {
        // الفتحة رقم 0
        creneau = context.Creneaux.Include("Medecin").Single<Creneau>(c => c.Id == idCreneau);
        Console.WriteLine(creneau.ShortIdentity());
}

السطر رقم 1 من الاستثناء، تشير الخطأ المبلغ عنه إلى وجود ربط لأن LEFT هي كلمة رئيسية للربط. نظرًا لأن السطر 4 من الرمز أعلاه يطلب التحميل الفوري للتبعية [Medecin] من كيان [Creneau]، قامت EF بإجراء ربط بين الجدولين [CRENEAUX] و [MEDECINS]. ولكن يبدو أن الموصل ADO.NET قد أنشأ أمرًا غير صحيح SQL. نعيد كتابة الكود على النحو التالي:


      using (var context = new RdvMedecinsContext())
      {
        // الفتحة رقم 0
        creneau = context.Creneaux.Find(idCreneau);
        Console.WriteLine(creneau.ShortIdentity());
        // يتم فرض تحميل الطبيب المرتبط
        // هذا ممكن لأننا ما زلنا في سياق مفتوح
        Medecin medecin = creneau.Medecin;
}
  • السطر 4: نبحث عن الفتحة بدون وصلة؛
  • السطر 8: نستعيد التبعية المفقودة.

هذا يعمل. مرة أخرى نلاحظ أن تغيير SGBD له تأثير على الكود. في الواقع، ليس SGBD هو السبب هنا، بل موصله ADO.NET.

6.3. بنية متعددة الطبقات تعتمد على EF 5

نعود إلى دراسة الحالة الموضحة في الفقرة 2.

سنبدأ ببناء طبقة [DAO] للوصول إلى البيانات. للقيام بذلك، نقوم بنسخ مشروع وحدة التحكم VS 2012 [RdvMedecins-SqlServer-02] في [RdvMedecins-PostgreSQL-02] [1]:

  • إلى [2]، ونحذف المشروع [RdvMedecins-SqlServer-02
  • في [3]، نضيف مشروعًا موجودًا إلى الحل. نأخذه من المجلد [RdvMedecins-PostgreSQL-02] الذي تم إنشاؤه للتو؛
  • في [4]، يحمل المشروع الجديد اسم المشروع الذي تم حذفه. سنقوم بتغيير اسمه؛
  • في [5]، قمنا بتغيير اسم المشروع؛
  • في [6]، نقوم بتعديل بعض خصائصه، مثل اسم التجميع هنا؛
  • إلى [7]، تم حذف المجلد [Models] ليتم استبداله بالمجلد [Models] من المشروع [RdvMedecins-PostgreSQL-01]. في الواقع، يشترك المشروعان في نفس القوالب.
  • في [8]، المراجع الحالية للمشروع؛
  • في [9]، تمت إضافة الموصل ADO.NET من PostgreSQL باستخدام الأداة NuGet.

في الملف [App.config]، يتم استبدال معلومات قاعدة البيانات SQL Server بمعلومات قاعدة البيانات PostgreSQL. يمكن العثور عليها في الملف [App.config] الخاص بالمشروع [RdvMedecins-PostgreSQL-01]:


  <!-- سلسلة الاتصال على القاعدة -->
  <connectionStrings>
    <add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
  </connectionStrings>
  <!-- مزود المصنع -->
  <system.data>
    <DbProviderFactories>
      <add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql Server" type="Npgsql.NpgsqlFactory, Npgsql, Version=2.0.11.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7" />
    </DbProviderFactories>
</system.data>

كما تتغير الكائنات التي يديرها Spring. حاليًا لدينا:


  <!-- تكوين Spring -->
  <spring>
    <context>
      <resource uri="config://spring/objects" />
    </context>
    <objects xmlns="http://www.springframework.net">
      <object id="rdvmedecinsDao" type="RdvMedecins.Dao.Dao,RdvMedecins-SqlServer-02" />
    </objects>
</spring>

تشير السطر 7 إلى تجميع مشروع [RdvMedecins-SqlServer-02]. أصبح التجميع الآن [RdvMedecins-PostgreSQL-02].

وبعد ذلك، نكون جاهزين لتنفيذ اختبار الطبقة [DAO]. يجب أولاً الحرص على ملء قاعدة البيانات (برنامج [Fill] من مشروع [RdvMedecins-PostgreSQL-01]). يتعطل برنامج الاختبار مع الاستثناء التالي:

2012/10/12 13:56:27:188 [INFO]  Spring.Context.Support.XmlApplicationContext - A
pplicationContext Refresh: Completed
Liste des clients :
Client [47,Mr,Jules,Martin,468]
Client [48,Mme,Christine,German,469]
Client [49,Mr,Jules,Jacquard,470]
Client [50,Melle,Brigitte,Bistrou,471]
Liste des médecins :
Medecin [42,Mme,Marie,Pelissier,472]
Medecin [43,Mr,Jacques,Bromard,497]
Medecin [44,Mr,Philippe,Jandot,510]
Medecin [45,Melle,Justine,Jacquemot,511]
L'erreur suivante s'est produite : RdvMedecinsException[3,GetCreneauxMedecin,Une erreur s'est produite lors de l'exécution de la définition de la commande. Pour plus de détails, consultez l'exception interne.]

السطر 13، تشير الرسالة إلى أن الخطأ قد حدث في الطريقة [GetCreneauxMedecin] للطبقة [DAO]. وهي كما يلي:


    // قائمة المواعيد المتاحة لطبيب معين
    public List<Creneau> GetCreneauxMedecin(int idMedecin)
    {
      // قائمة المواعيد
      try
      {
        // فتح سياق الاستمرارية
        using (var context = new RdvMedecinsContext())
        {
          // استرداد الطبيب مع مواعيده
          Medecin medecin = context.Medecins.Include("Creneaux").Single(m => m.Id == idMedecin);
          // إرجاع قائمة المواعيد للطبيب
          return medecin.Creneaux.ToList<Creneau>();
        }
      }
      catch (Exception ex)
      {
        throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
      }
}

السطر 11، يمكن التعرف على الكلمة الرئيسية Include التي تسببت بالفعل في تعطل برنامج سابق. يمكن استبدال الكود السابق بالكود التالي:


    // قائمة المواعيد المتاحة لطبيب معين
    public List<Creneau> GetCreneauxMedecin(int idMedecin)
    {
      // قائمة المواعيد
      try
      {
        // فتح سياق الاستمرارية
        using (var context = new RdvMedecinsContext())
        {
          // يتم إرجاع قائمة المواعيد المتاحة للطبيب
          return context.Creneaux.Where(c => c.MedecinId == idMedecin).ToList<Creneau>(); 
        }
      }
      catch (Exception ex)
      {
        throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
      }
}

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

نقوم بإنشاء DLL للمشروع كما تم مع مشروع [RdvMedecins-SqlServer-02] ونجمعجميع ملفات DLL الخاصة بالمشروع في مجلد [lib] تم إنشاؤه في [RdvMedecins-PostgreSQL-02]. وستكون هذه هي مراجع مشروع الويب [RdvMedecins-PostgreSQL-03] الذي سيتبع.

  

نحن الآن جاهزون لبناء الطبقة [ASP.NET] لتطبيقنا:

سنبدأ من المشروع [RdvMedecins-SqlServer-03]. نقوم بنسخ مجلد هذا المشروع في [RdvMedecins-PostgreSQL-03] [1]:

  • في [2]، باستخدام VS 2012 Express للويب، نفتح حل المجلد [RdvMedecins-PostgreSQL-03
  • في [3]، نقوم بتغيير اسم الحل واسم المشروع؛
  • في [4]، المراجع الحالية للمشروع؛
  • في [5]، نقوم بحذفها؛
  • إلى [6]، لاستبدالها بمراجع إلى DLL التي قمنا بتخزينها للتو في مجلد [lib] التابع للمشروع [RdvMedecins-PostgreSQL-02].

لم يتبق لنا سوى تعديل الملف [Web.config]. نستبدل محتواه الحالي بمحتوى الملف [App.config] من المشروع [RdvMedecins-PostgreSQL-02]. بعد ذلك، نقوم بتشغيل المشروع على الويب. إنه يعمل. ولا ننسى ملء قاعدة البيانات قبل تشغيل تطبيق الويب.