Skip to content

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

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

الأدوات التي سيتم تثبيتها هي كما يلي:

  • نظام إدارة قواعد البيانات (DBMS): [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]، نظام إدارة قواعد البيانات قيد التشغيل.

نقوم الآن بتشغيل أداة [SQL Manager Lite for MySQL]، التي سنستخدمها لإدارة نظام إدارة قواعد البيانات [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>
    <!-- For more information on Entity Framework configuration, visit 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>
 
  <!-- connecting chain-->
  <connectionStrings>
    <add name="monContexte"
         connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
         providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
  <!-- the factory provider -->
  <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]:

هناك أيضًا بعض الإعدادات في ملف [Entities.cs] حيث نحدد أسماء الجداول والمخطط الذي تنتمي إليه. قد يختلف هذا اعتمادًا على نظام إدارة قواعد البيانات (DBMS). هذا هو الحال هنا، حيث لن يكون هناك مخطط. يتغير ملف [Entities.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. وبما أن هذا لم يتم إنشاؤه من قبلنا بل من قبل موفر MySQL ADO.NET، فلا يمكننا تصحيح هذه المشكلة. ومع ذلك، يمكننا أن نرى أن الجداول قد تم إنشاؤها [1] أدناه:

  • في [2]، نرى بنية جدول [clients] [3].

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

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

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


# SQL Manager Lite for MySQL 5.3.0.2
# ---------------------------------------
# Host     : localhost
# Port     : 3306
# Database : 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`;
 
#
# Structure for the `clients` table : 
#
 
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=''
;
 
#
# Structure for the `medecins` table : 
#
 
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=''
;
 
#
# Structure for the `creneaux` table : 
#
 
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=''
;
 
#
# Structure for the `rvs` table : 
#
 
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: المفتاح الخارجي من جدول [slots] إلى جدول [doctors] مع جملة ON DELETE CASCADE؛
  • السطر 80: قيد التفرد في جدول [rvs]؛
  • السطر 83: المفتاح الخارجي من الجدول [rvs] إلى الجدول [slots] مع جملة ON DELETE CASCADE؛
  • السطر 84: المفتاح الخارجي من الجدول [rvs] إلى الجدول [clients] مع جملة ON DELETE CASCADE؛

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

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

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;
 
      // empty the current base
      Erase();
      // add a customer
      using (var context = new RdvMedecinsContext())
      {
        // customer creation
        client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
        // add customer to context
        context.Clients.Add(client1);
        // save the context
        context.SaveChanges();
      }
      // basic view
      Dump("1-----------------------------");
      // client1 is not in the context - we modify it
      client1.Nom = "y";
      // out-of-context entity modification
      using (var context = new RdvMedecinsContext())
      {
        // here we have a new empty context
        // we put client1 in the context in a modified state
        context.Entry(client1).State = EntityState.Modified;
        // save the context
        context.SaveChanges();
      }
      ...
    }
 
    static void Erase()
    {
      ...
    }
 
    static void Dump(string str)
    {
      ...
    }
  }
}
  • السطر 20: تم حفظ عميل. أصبح لديه الآن مفتاحه الأساسي، ولكن بناءً على إصداره؛
  • السطر 33: تم إجراء تعديل على client1. فشل التعديل لأنه لا يحتوي على الإصدار المخزن في قاعدة البيانات.

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


      // retrieve client1 to get its version
      using (var context = new RdvMedecinsContext())
      {
        // customer2 will be in the context
        Client client2 = context.Clients.Find(client1.Id);
        // set the version of client1 to that of 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]:


<!-- connecting chain-->
  <connectionStrings>
    <add name="monContexte"
         connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
         providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
  <!-- the factory provider -->
  <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 configuration -->
  <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 for the Web، نفتح الحل في مجلد [RdvMedecins-MySQL-03
  • في [3]، نقوم بتغيير اسم الحل واسم المشروع؛
  • في [4]، المراجع الحالية للمشروع؛
  • في [5]، نقوم بحذفها؛
  • في [6]، لاستبدالها بمراجع إلى ملفات DLL التي قمنا بتخزينها للتو في مجلد [lib] داخل مشروع [RdvMedecins-MySQL-02].

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

4.4. الخلاصة

دعونا نلخص ما تم القيام به للتحول من نظام إدارة قواعد البيانات SQL Server إلى نظام إدارة قواعد البيانات MySQL:

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

    [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 كل من المفتاح الأساسي والطابع الزمني. أما في MySQL، فلم يكن له سوى المفتاح الأساسي. وهذا تطلب تغييرًا في الكود.

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

  • Oracle Database Express Edition 11g Release
  • نظام إدارة قواعد البيانات PostgreSQL 9.2.1؛
  • نظام إدارة قواعد البيانات Firebird 2.1.