Skip to content

4. Приклад використання з MySQL 5.5.28

4.1. Встановлення інструментів

Необхідно встановити такі інструменти:

  • SGBD: [http://dev.mysql.com/downloads/];
  • інструмент адміністрування: EMS, SQL Manager для MySQL, безкоштовне програмне забезпечення [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 5;
  • у [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())
      {
        // тут ми маємо новий порожній контекст
        // клієнт1 додано до контексту у зміненому стані
        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);
        // версію клієнта1 встановлюємо на версію клієнта2
        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:

  • SGBD — Oracle Database Express Edition 11g Release 2;
  • SGBD PostgreSQL 9.2.1;
  • SGBD Firebird 2.1.