Skip to content

4. دراسة حالة مع MySQL 5.5.28

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

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

  • SGBD: [http://dev.mysql.com/downloads/
  • أداة إدارة: EMS SQL Manager for MySQL Freeware [http://www.sqlmanager.net/fr/products/mysql/manager/download].

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

لنقم بتشغيل MySQL5. هنا، نقوم بذلك من نافذة خدمات Windows [1]. في [2]، يتم تشغيل SGBD.

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

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

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

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

نقوم بإنشاء مشروع وحدة التحكم VS 2012 [RdvMedecins-MySQL-01] [1] أدناه:

  • في [2]، نضيف مراجع إلى المشروع عبر NuGet؛
  • في [3]، نضيف المرجع EF
  • في [4]، أصبحت الآن ضمن المراجع؛
  • في [5]، نكرر العملية لإضافة [MySQL.Data.Entities] هذه المرة، وهو موصل ADO.NET لـ Entity Framework. للعثور على الحزمة، يمكن الاستعانة بمربع البحث [6]؛
  • في [7]، تظهر مرجعان [MySQL.Data.Entities] و [MySQL.Data]، حيث يمثل الأخير تبعية للأول.

الآن، سنقوم بإنشاء المشروع [RdvMedecins-MySQL-01] انطلاقًا من المشروع [RdvMedecins-SqlServer-01].

  • في [1]، ننسخ العناصر المحددة؛
  • في [2]، نلصقها في المشروع [RdvMedecins-MySQL-01
  • في [3]، نظرًا لوجود عدة برامج باستخدام طريقة [Main]، يتعين علينا تحديد مشروع بدء التشغيل.

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


<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- لمزيد من المعلومات حول تكوين Entity Framework، تفضل بزيارة http://go.microsoft.com/fwlink/?LinkID=237468 -->
    <section name="entityFramework" type="System.Data.Entity.Internal.ConfigFile.EntityFrameworkSection, EntityFramework, Version=5.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" requirePermission="false" />
  </configSections>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
  </startup>
  <entityFramework>
    <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework" />
  </entityFramework>

  <!-- سلسلة الاتصال-->
  <connectionStrings>
    <add name="monContexte"
         connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
         providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
  <!-- مزود المصنع -->
  <system.data>
    <DbProviderFactories>
      <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
          type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
        />
    </DbProviderFactories>
  </system.data>

</configuration>
  • السطر 17: سلسلة الاتصال بقاعدة البيانات MySQL [rdvmedecins-ef] التي أنشأناها؛
  • السطر 24: يجب أن تتطابق الإصدارة مع تلك الموجودة في المرجع [MySql.Data] للمشروع [1]:

هناك بعض الإعدادات أيضًا في الملف [Entites.cs] حيث نحدد أسماء الجداول وكذلك المخطط الذي تنتمي إليه. قد يتغير هذا وفقًا لـ SGBD. هذا هو الحال هنا، حيث لن يكون هناك مخطط. يتطور الملف [Entites.cs] على النحو التالي:


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

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

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

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

لنقم بتشغيل البرنامج [CreateDB_01] [2]. نحصل على الاستثناء التالي:

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-MySQL-01\CreateDB_01.cs:ligne 15

يظهر نفس الخطأ أربع مرات (الأسطر 2-5). يشير النوع rowversion إلى الحقل الذي يحمل التعليق التوضيحي [Timestamp] في الكيانات:


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

قررنا استبدال هذه الأسطر الثلاثة بما يلي:


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

نقوم بتغيير نوع العمود من byte[] إلى DateTime?. نقوم بذلك لأن MySQL له نوع [TIMESTAMP] الذي يمثل تاريخًا/وقتًا، ولأن العمود الذي له هذا النوع يتم تحديثه تلقائيًا بواسطة MySQL في كل مرة يتم فيها تحديث السطر. سيسمح لنا ذلك بإدارة عمليات الوصول المتزامنة.

لا يمكن تطبيق التعليق التوضيحي [Timestamp] إلا على عمود من النوع byte[]. نستبدله بالتعليق التوضيحي [ConcurrencyCheck]. يدير هذان التعليقان التوضيحيان كلاهما التنافس في الوصول. نقوم بذلك بالنسبة للكيانات الأربعة ثم نعيد تشغيل التطبيق. ثم نحصل على الخطأ التالي:

1
2
3
4
5
6
7
8
Exception non gérée : MySql.Data.MySqlClient.MySqlException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'NOT NULL,        `ProductVersion` mediumtext NOT NULL);

ALTER TABLE `__MigrationH' at line 5
   à MySql.Data.MySqlClient.MySqlStream.ReadPacket()
   à MySql.Data.MySqlClient.NativeDriver.GetResult(Int32& affectedRow, Int32& insertedId)
   ...
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-MySQL-01\CreateDB_01.cs:ligne 15

تشير السطر 1 إلى خطأ في بناء الجملة في SQL الذي تم تنفيذه بواسطة MySQL. نظرًا لأن هذا الخطأ لم يتم إنشاؤه بواسطتنا بل بواسطة المزود ADO.NET التابع لـ MySQL، لا يمكننا تصحيح هذه النقطة. ومع ذلك، يمكن ملاحظة أنه تم إنشاء جداول [1] أدناه:

  • في [2]، يمكننا رؤية بنية الجدول [clients] [3].

هناك العديد من التعديلات التي يجب إجراؤها على القاعدة التي تم إنشاؤها:

  • نوع العمود [VERSIONING] غير مناسب. يجب تعيين النوع MySQL [TIMESTAMP] له؛
  • نتذكر أن الجدول [rvs] له قيد التفرد. لم يتم إنشاؤه بواسطة هذا التوليد؛
  • كان الموصل ADO.NET من SQL Server قد أنشأ مفاتيح خارجية باستخدام الشرط ON DELETE CASCADE. لم يقم الموصل ADO.NET الخاص بـ MySQL بذلك.

وكما فعلنا مع خادم SQL، يتعين علينا تعديل قاعدة البيانات التي تم إنشاؤها. لن نعرض كيفية إجراء التعديلات. سنقدم فقط البرنامج النصي لإنشاء قاعدة البيانات:


# SQL Manager Lite لـ MySQL 5.3.0.2
# ---------------------------------------
# المضيف     : localhost
# المنفذ     : 3306
# قاعدة البيانات : rdvmedecins-ef


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;

SET FOREIGN_KEY_CHECKS=0;

USE `rdvmedecins-ef`;

#
# هيكل جدول `clients`: 
#

CREATE TABLE `clients` (
  `ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRENOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `TITRE` VARCHAR(5) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY USING BTREE (`ID`) COMMENT ''
)ENGINE=InnoDB
AUTO_INCREMENT=96 AVG_ROW_LENGTH=4096 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT=''
;

#
# هيكل جدول `medecins`: 
#

CREATE TABLE `medecins` (
  `ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
  `NOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `PRENOM` VARCHAR(30) COLLATE utf8_general_ci NOT NULL,
  `TITRE` VARCHAR(5) COLLATE utf8_general_ci NOT NULL,
  `VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY USING BTREE (`ID`) COMMENT ''
)ENGINE=InnoDB
AUTO_INCREMENT=56 AVG_ROW_LENGTH=4096 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT=''
;

#
# هيكل جدول `creneaux`: 
#

CREATE TABLE `creneaux` (
  `ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
  `HDEBUT` INTEGER(11) NOT NULL,
  `MDEBUT` INTEGER(11) NOT NULL,
  `HFIN` INTEGER(11) NOT NULL,
  `MFIN` INTEGER(11) NOT NULL,
  `MEDECIN_ID` INTEGER(11) NOT NULL,
  `VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY USING BTREE (`ID`) COMMENT '',
   INDEX `MEDECIN_ID` USING BTREE (`MEDECIN_ID`) COMMENT '',
  CONSTRAINT `creneaux_ibfk_1` FOREIGN KEY (`MEDECIN_ID`) REFERENCES `medecins` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION
)ENGINE=InnoDB
AUTO_INCREMENT=472 AVG_ROW_LENGTH=455 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT=''
;

#
# هيكل جدول `rvs`: 
#

CREATE TABLE `rvs` (
  `ID` INTEGER(11) NOT NULL AUTO_INCREMENT,
  `JOUR` DATE NOT NULL,
  `CRENEAU_ID` INTEGER(11) NOT NULL,
  `CLIENT_ID` INTEGER(11) NOT NULL,
  `VERSIONING` TIMESTAMP NOT NULL ON UPDATE CURRENT_TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY USING BTREE (`ID`) COMMENT '',
  UNIQUE INDEX `CRENEAU_ID_JOUR` USING BTREE (`JOUR`, `CRENEAU_ID`) COMMENT '',
   INDEX `CRENEAU_ID` USING BTREE (`CRENEAU_ID`) COMMENT '',
   INDEX `CLIENT_ID` USING BTREE (`CLIENT_ID`) COMMENT '',
  CONSTRAINT `rvs_ibfk_2` FOREIGN KEY (`CLIENT_ID`) REFERENCES `clients` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION,
  CONSTRAINT `rvs_ibfk_1` FOREIGN KEY (`CRENEAU_ID`) REFERENCES `creneaux` (`ID`) ON DELETE CASCADE ON UPDATE NO ACTION
)ENGINE=InnoDB
AUTO_INCREMENT=28 AVG_ROW_LENGTH=16384 CHARACTER SET 'utf8' COLLATE 'utf8_general_ci'
COMMENT=''
;
  • الأسطر 22 و38 و54 و74: المفاتيح الأساسية ID للجداول هي من النوع AUTO_INCREMENT، وبالتالي تم إنشاؤها بواسطة MySQL؛
  • السطور 26 و42 و60 و 78: العمود VERSIONING هو من النوع TIMESTAMP ويتم تحديثه عند INSERT أو UPDATE؛
  • السطر 63: المفتاح الخارجي للجدول [creneaux] إلى الجدول [medecins] مع الشرط ON DELETE CASCADE؛
  • السطر 80: قيد التفرد للجدول [rvs]؛
  • السطر 83: المفتاح الأجنبي للجدول [rvs] إلى الجدول [creneaux] مع الشرط ON DELETE CASCADE؛
  • السطر 84: المفتاح الخارجي للجدول [rvs] إلى الجدول [clients] باستخدام الجملة ON DELETE CASCADE؛

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

وبعد ذلك، يمكن تشغيل البرامج المختلفة للمشروع. وهي تعطي نفس النتائج التي تعطيها مع خادم SQL باستثناء البرنامج [ModifyDetachedEntities] الذي يتعطل. لفهم السبب، يمكننا إلقاء نظرة على نتيجة البرنامج [ModifyAtttachedEntities]:

1
2
3
4
5
6
7
8
client1--avant
Client [,xx,xx,xx,]
client1--après
Client [86,xx,xx,xx,]
client2
Client [86,xx,xx,xx,11/10/2012 11:31:12]
client3
Client [86,xx,xx,yy,11/10/2012 11:31:12]
  • السطران 1-2: عميل قبل حفظ السياق؛
  • السطران 3-4: العميل بعد الحفظ. لديه مفتاح أساسي ولكن لا توجد قيمة لحقل [Versioning] الخاص به، في حين أن SQL Server كان يقوم بتحديث حقل [Timestamp] الخاص بالكيان.

الآن دعونا نلقي نظرة على كود البرنامج [ModifyDetachedEntities] الذي يتعطل:


using System;
...

namespace RdvMedecins_01
{
  class ModifyDetachedEntities
  {
    static void Main(string[] args)
    {
      Client client1;

      // إفراغ قاعدة البيانات الحالية
      Erase();
      // إضافة عميل
      using (var context = new RdvMedecinsContext())
      {
        // إنشاء عميل
        client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
        // إضافة العميل إلى السياق
        context.Clients.Add(client1);
        // حفظ السياق
        context.SaveChanges();
      }
      // عرض قاعدة البيانات
      Dump("1-----------------------------");
      // العميل 1 غير موجود في السياق - يتم تعديله
      client1.Nom = "y";
      // تعديل كيان خارج السياق
      using (var context = new RdvMedecinsContext())
      {
        // هنا، لدينا سياق جديد فارغ
        // نضع client1 في السياق في حالة معدلة
        context.Entry(client1).State = EntityState.Modified;
        // نقوم بحفظ السياق
        context.SaveChanges();
      }
      ...
    }

    static void Erase()
    {
      ...
    }

    static void Dump(string str)
    {
      ...
    }
  }
}
  • السطر 20: يتم حفظ عميل. يكون له عندئذ مفتاحه الأساسي ولكن من خلال إصداره؛
  • السطر 33: يتم إجراء تعديل على client1. يفشل التعديل لأنه لا يمتلك الإصدار الموجود في قاعدة البيانات.

نحل المشكلة بإدراج الكود التالي بين السطرين 25 و26:


      // نسترد client1 للحصول على نسخته
      using (var context = new RdvMedecinsContext())
      {
        // سيكون client2 في السياق
        Client client2 = context.Clients.Find(client1.Id);
        // يتم تعيين إصدار client1 إلى إصدار client2
        client1.Versioning = client2.Versioning;
}

من الآن فصاعدًا، أصبحت الكيان [client1] بنفس الإصدار الموجود في قاعدة البيانات، وبالتالي يمكن استخدامه لتحديث السطر في قاعدة البيانات.

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

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

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

  • في [2]، تتم إضافة المراجع [Common.Logging, EntityFramework, MySql.Data, MySql.Data.Entity, Spring.Core] مع NuGet؛
  • في [3]، يتم نسخ المجلد [Models] من المشروع [RdvMedecins-MySQL-01
  • في [4]، يتم نسخ المجلدين [Dao, Exception, Tests] والملف [App.config] من المشروع [RdvMedecins-SqlServer-02
  • في [5]، تم حذف الملف [Program.cs
  • في [6]، تم تكوين المشروع لتنفيذ برنامج اختبار الطبقة [DAO].

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


<!-- سلسلة الاتصال-->
  <connectionStrings>
    <add name="monContexte"
         connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
         providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
  <!-- مزود المصنع -->
  <system.data>
    <DbProviderFactories>
      <add name="MySQL Data Provider" invariant="MySql.Data.MySqlClient" description=".Net Framework Data Provider for MySQL"
          type="MySql.Data.MySqlClient.MySqlClientFactory, MySql.Data, Version=6.5.4.0, Culture=neutral, PublicKeyToken=C5687FC88969C44D"
        />
    </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-MySQL-02].

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

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

  

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

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

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

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

4.4. Conclusion

لنلخص ما تم القيام به للانتقال من خادم SGBD SQL إلى خادم SGBD MySQL:

  • تم تغيير الحقل الذي كان يستخدم لإدارة التنافس على الوصول إلى الكيانات. كانت نسخته في خادم SQL هي:

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

وأصبحت:


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

مع MySQL؛

  • تم تغيير التعليقات التوضيحية [Table] التي تربط كيانًا بجدول؛
  • تم تعديل سلسلة الاتصال بالقاعدة و [DbProviderFactory] في ملفات التكوين [App.config] و [Web.config
  • بعد الحفظ في قاعدة البيانات، كان للكيان SQL Server كل من مفتاحه الأساسي و Timestamp. مع MySQL، كان لديه مفتاحه الأساسي فقط. أدى ذلك إلى تعديل أحد الأكواد.

في النهاية، لم تكن التعديلات كثيرة، ولكن كان لا بد من مراجعة الكود. نكرر نفس الإجراء لثلاثة كيانات أخرى:

  • SGBD Oracle Database Express Edition 11g الإصدار 2؛
  • SGBD PostgreSQL 9.2.1؛
  • SGBD Firebird 2.1.