Skip to content

4. Studium przypadku z plikiem MySQL 5.5.28

4.1. Instalacja narzędzi

Należy zainstalować następujące narzędzia:

  • SGBD: [http://dev.mysql.com/downloads/];
  • narzędzie administracyjne: EMS, SQL Manager dla MySQL, darmowe oprogramowanie [http://www.sqlmanager.net/fr/products/mysql/manager/download].

W poniższych przykładach użytkownik root ma hasło root.

Uruchommy MySQL5. W tym przypadku robimy to z poziomu okna usług systemu Windows [1]. W [2] uruchamiany jest SGBD.

Teraz uruchamiamy narzędzie [SQL Manager Lite for MySQL], za pomocą którego będziemy zarządzać SGBD i [3].

  • W [4] tworzymy nową bazę;
  • w [5] podajemy nazwę bazy;
  • w pliku [5] logujemy się jako root / root;
  • w [6] zatwierdzamy polecenie SQL, które zostanie wykonane;
  • w [7] baza została utworzona. Teraz należy ją zapisać w [EMS Manager]. Informacje są poprawne. Wykonujemy [OK];
  • w [8] logujemy się do niej;
  • w [9], [EMS Manager] wyświetla bazę danych, która na razie jest pusta.

Teraz podłączymy do tej bazy projekt VS 2012.

4.2. Tworzenie bazy danych na podstawie obiektów

Tworzymy poniższy projekt konsoli VS 2012 [RdvMedecins-MySQL-01] [1]:

  • w [2] dodajemy odniesienia do projektu za pomocą NuGet;
  • w pliku [3] dodajemy odniesienie EF 5;
  • w [4] znajduje się ona teraz wśród odniesień;
  • w [5], zaczynamy od nowa, aby tym razem dodać [MySQL.Data.Entities], który jest łącznikiem ADO.NET dla Entity Framework. Aby znaleźć pakiet, można skorzystać z pola wyszukiwania [6];
  • w przypadku [7] pojawiają się dwa odniesienia: [MySQL.Data.Entities] i [MySQL.Data], przy czym to drugie jest zależnością od pierwszego.

Teraz skompilujemy projekt [RdvMedecins-MySQL-01] na podstawie projektu [RdvMedecins-SqlServer-01].

  • do [1], kopiujemy wybrane elementy;
  • do projektu [2] wklejamy je do projektu [RdvMedecins-MySQL-01];
  • w pliku [3], ponieważ istnieje kilka programów o nazwie [Main], musimy określić projekt, z którego ma zostać uruchomiony projekt.

Na tym etapie generowanie projektu powinno zakończyć się powodzeniem. Teraz zmodyfikujemy plik konfiguracyjny [App.config], który konfiguruje ciąg połączenia z bazą danych oraz plik DbProviderFactory. Będzie on wyglądał następująco:


<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- Więcej informacji na temat konfiguracji Entity Framework można znaleźć na stronie 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>

  <!-- łańcuch połączenia-->
  <connectionStrings>
    <add name="monContexte"
         connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
         providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
  <!-- dostawca fabryki -->
  <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>
  • wiersz 17: ciąg połączenia z bazą danych MySQL [rdvmedecins-ef], który utworzyliśmy;
  • wiersz 24: wersja musi odpowiadać wersji odniesienia [MySql.Data] w projekcie [1]:

W pliku [Entites.cs] znajduje się również kilka ustawień, w których określa się nazwy tabel oraz schemat, do którego należą. Może się to różnić w zależności od pliku SGBD. Tak jest w tym przypadku, gdzie nie będzie schematu. Plik [Entites.cs] zmienia się w następujący sposób:


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

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

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

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

Uruchommy program [CreateDB_01] [2]. Otrzymujemy następujący wyjątek:

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

Ten sam błąd pojawia się cztery razy (wiersze 2–5). Typ rowversion przypomina pole z adnotacją [Timestamp] w encjach:


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

Postanawiamy zastąpić te trzy wiersze następującymi:


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

Zmieniamy typ kolumny z byte[] na DateTime?. Robimy to, ponieważ MySQL ma typ [TIMESTAMP], który reprezentuje datę i godzinę, a kolumna o tym typie jest automatycznie aktualizowana przez MySQL za każdym razem, gdy wiersz jest aktualizowany. Pozwoli nam to zarządzać dostępem współbieżnym.

Adnotacja [Timestamp] może mieć zastosowanie wyłącznie do kolumny typu byte[]. Zastępujemy ją adnotacją [ConcurrencyCheck]. Obie te adnotacje zarządzają współbieżnością dostępu. Wykonujemy to dla wszystkich czterech encji, a następnie ponownie uruchamiamy aplikację. Otrzymujemy wówczas następujący błąd:

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

Wiersz 1 wskazuje na błąd składniowy w adnotacji SQL wykonywanej przez MySQL. Ponieważ nie został on wygenerowany przez nas, lecz przez dostawcę ADO.NET z MySQL, nie możemy tego skorygować. Niemniej jednak można zauważyć, że poniżej utworzono tabele [1]:

  • W tabeli [2] widoczna jest struktura tabeli [clients] oraz [3].

W wygenerowanej bazie danych należy wprowadzić kilka zmian:

  • typ kolumny [VERSIONING] jest nieodpowiedni. Należy nadać jej typ MySQL [TIMESTAMP];
  • należy pamiętać, że tabela [rvs] ma ograniczenie unikalności. Nie została ona utworzona podczas tego generowania;
  • łącznik ADO.NET z serwera SQL wygenerował klucze obce z klauzulą ON DELETE CASCADE. Łącznik ADO.NET serwera MySQL tego nie zrobił.

Podobnie jak w przypadku serwera SQL, musimy zatem zmodyfikować wygenerowaną bazę danych. Nie pokazujemy, jak wprowadzić te zmiany. Podajemy jedynie skrypt służący do utworzenia bazy danych:


# SQL Manager Lite dla MySQL 5.3.0.2
# ---------------------------------------
# Host     : localhost
# Port: 3306
# Baza danych: 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`;

#
# Struktura tabeli `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=''
;

#
# Struktura tabeli `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=''
;

#
# Struktura tabeli `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=''
;

#
# Struktura tabeli `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=''
;
  • wiersze 22, 38, 54, 74: klucze główne ID tabel są typu AUTO_INCREMENT, a więc wygenerowane przez MySQL;
  • wiersze 26, 42, 60, 78: kolumna VERSIONING jest typu TIMESTAMP i jest aktualizowana podczas operacji INSERT lub UPDATE;
  • wiersz 63: klucz obcy z tabeli [creneaux] do tabeli [medecins] z klauzulą ON DELETE CASCADE;
  • wiersz 80: ograniczenie unikalności tabeli [rvs];
  • wiersz 83: klucz obcy z tabeli [rvs] do tabeli [creneaux] z klauzulą ON DELETE CASCADE;
  • wiersz 84: klucz obcy z tabeli [rvs] do tabeli [clients] z klauzulą ON DELETE CASCADE;

Skrypt do generowania tabel bazy danych MySQL i [rvmedecins-ef] został umieszczony w folderze [RdvMedecins / databases / mysql]. Czytelnik może go załadować i uruchomić w celu utworzenia swoich tabel.

Po wykonaniu tej czynności można uruchomić poszczególne programy projektu. Dają one takie same wyniki jak w przypadku serwera SQL, z wyjątkiem programu [ModifyDetachedEntities], który ulega awarii. Aby zrozumieć, dlaczego tak się dzieje, można przyjrzeć się wynikowi programu [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]
  • wiersze 1–2: klient przed zapisaniem kontekstu;
  • wiersze 3–4: klient po zapisaniu. Posiada klucz główny, ale nie ma wartości dla pola [Versioning], podczas gdy program SQL Server aktualizował pole [Timestamp] tej encji.

Przyjrzyjmy się teraz kodowi programu [ModifyDetachedEntities], który ulega awarii:


using System;
...

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

      // Opróżniamy obecną bazę danych
      Erase();
      // dodajemy klienta
      using (var context = new RdvMedecinsContext())
      {
        // tworzenie klienta
        client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
        // dodanie klienta do kontekstu
        context.Clients.Add(client1);
        // zapisywanie kontekstu
        context.SaveChanges();
      }
      // wyświetlanie podstawowe
      Dump("1-----------------------------");
      // klient1 nie znajduje się w kontekście – modyfikacja
      client1.Nom = "y";
      // modyfikacja encji poza kontekstem
      using (var context = new RdvMedecinsContext())
      {
        // tutaj mamy nowy, pusty kontekst
        // umieszczamy klienta1 w kontekście w stanie zmodyfikowanym
        context.Entry(client1).State = EntityState.Modified;
        // zapisujemy kontekst
        context.SaveChanges();
      }
      ...
    }

    static void Erase()
    {
      ...
    }

    static void Dump(string str)
    {
      ...
    }
  }
}
  • wiersz 20: zapisywany jest klient. Posiada on wówczas swój klucz główny, ale nie ma jeszcze wersji;
  • wiersz 33: wprowadzana jest zmiana dotycząca klienta1. Nie udaje się ona, ponieważ nie ma on wersji znajdującej się w bazie danych.

Problem rozwiązujemy, wstawiając poniższy kod między wierszami 25 i 26:


      // pobieramy „client1”, aby uzyskać jego wersję
      using (var context = new RdvMedecinsContext())
      {
        // „client2” znajdzie się w kontekście
        Client client2 = context.Clients.Find(client1.Id);
        // ustawiamy wersję klienta1 na wersję klienta2
        client1.Versioning = client2.Versioning;
}

Teraz entyteta [client1] ma tę samą wersję co w bazie danych i może zatem zostać wykorzystana do aktualizacji wiersza w bazie danych.

4.3. Architektura wielowarstwowa oparta na EF 5

Wracamy do naszego studium przypadku opisanego w akapicie 2.

Zaczniemy od zbudowania warstwy dostępu do danych [DAO]. W tym celu tworzymy projekt konsoli VS 2012 [RdvMedecins-MySQL-02] [1]:

  • w [2] dodano odniesienia [Common.Logging, EntityFramework, MySql.Data, MySql.Data.Entity, Spring.Core] wraz z NuGet;
  • w pliku [3] folder [Models] jest kopiowany z projektu [RdvMedecins-MySQL-01];
  • w [4] foldery [Dao, Exception, Tests] oraz plik [App.config] zostały skopiowane z projektu [RdvMedecins-SqlServer-02];
  • w [5] plik [Program.cs] został usunięty;
  • w projekcie [6] skonfigurowano uruchamianie programu testowego warstwy [DAO].

W pliku [App.config] informacje z bazy SQL Server zastępuje się informacjami z bazy MySQL. Znajdują się one w pliku [App.config] projektu [RdvMedecins-MySQL-01]:


<!-- łańcuch połączenia-->
  <connectionStrings>
    <add name="monContexte"
         connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
         providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
  <!-- dostawca fabryki -->
  <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>

Zmieniają się również obiekty zarządzane przez Spring. Obecnie mamy:


  <!-- konfiguracja 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>

Wiersz 7 odwołuje się do zestawu projektu [RdvMedecins-SqlServer-02]. Zestaw ten nosi teraz nazwę [RdvMedecins-MySQL-02].

Po wykonaniu tej czynności jesteśmy gotowi do przeprowadzenia testu warstwy [DAO]. Wcześniej należy jednak zadbać o wypełnienie bazy danych (program [Fill] z projektu [RdvMedecins-MySQL-01]). Test programu zakończył się powodzeniem.

Tworzymy plik DLL projektu w taki sam sposób, jak w przypadku projektu [RdvMedecins-SqlServer-02], a następnie gromadzimywszystkie pliki DLL z projektu w folderze [lib] utworzonym w [RdvMedecins-MySQL-02]. Będą to pliki referencyjne dla projektu internetowego [RdvMedecins-MySQL-03], który zostanie zrealizowany w następnej kolejności.

  

Jesteśmy teraz gotowi do stworzenia warstwy [ASP.NET] naszej aplikacji:

Wychodzimy od projektu [RdvMedecins-SqlServer-03]. Duplikujemy folder tego projektu w [RdvMedecins-MySQL-03] i [1]:

  • w pliku [2], przy użyciu programu VS 2012 Express for the Web, otwieramy rozwiązanie z folderu [RdvMedecins-MySQL-03];
  • w pliku [3] zmieniamy zarówno nazwę rozwiązania, jak i nazwę projektu;
  • w pliku [4] aktualne odniesienia projektu;
  • w pliku [5] usuwamy je;
  • na [6], aby zastąpić je odwołaniami do pliku DLL, który właśnie zapisaliśmy w folderze [lib] projektu [RdvMedecins-MySQL-02].

Pozostaje nam tylko zmodyfikować plik [Web.config]. Zastępujemy jego aktualną zawartość zawartością pliku [App.config] z projektu [RdvMedecins-MySQL-02]. Po wykonaniu tej czynności uruchamiamy projekt internetowy. Działa.

4.4. Conclusion

Podsumujmy, co zrobiono, aby przejść z serwera SGBD SQL na serwer SGBD MySQL:

  • zmieniono pole służące do zarządzania konkurencją dostępu do encji. Jego wersja na serwerze SQL brzmiała:

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

Została zmieniona na:


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

wraz z MySQL;

  • zmieniono adnotacje [Table], które łączą encję z tabelą;
  • ciąg połączenia z bazą danych oraz [DbProviderFactory] zostały zmodyfikowane w plikach konfiguracyjnych [App.config] i [Web.config];
  • po zapisaniu w bazie danych encja SQL Server posiadała zarówno klucz główny, jak i Timestamp. W przypadku MySQL posiadała jedynie klucz główny. Wymagało to modyfikacji kodu.

Ostatecznie zmiany były dość niewielkie, ale mimo to konieczne było przejrzenie kodu. Powtarzamy tę samą procedurę dla trzech innych obiektów SGBD:

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