Skip to content

4. Estudo de caso com o MySQL 5.5.28

4.1. Instalação das ferramentas

As ferramentas a instalar são as seguintes:

  • o SGBD: [http://dev.mysql.com/downloads/];
  • uma ferramenta de administração: EMS SQL Manager for MySQL Freeware [http://www.sqlmanager.net/fr/products/mysql/manager/download].

Nos exemplos seguintes, o utilizador root tem a palavra-passe root.

Vamos iniciar o MySQL5. Aqui, fazemos isso a partir da janela Serviços do Windows [1]. Em [2], o SGBD está a funcionar.

Agora, iniciamos a ferramenta [SQL Manager Lite for MySQL], que utilizaremos para administrar o SGBD [3].

  • Em [4], criamos uma nova base de dados;
  • em [5], especificamos o nome da base de dados;
  • em [5], iniciamos sessão como root / root;
  • Em [6], validamos a instrução SQL a ser executada;
  • Em [7], a base de dados foi criada. Agora, deve ser registada no [EMS Manager]. As informações estão corretas. Clique em [OK];
  • Em [8], ligamo-nos a ela;
  • Em [9], o [EMS Manager] apresenta a base de dados, que está atualmente vazia.

Vamos agora ligar um projeto VS 2012 a esta base de dados.

4.2. Criação da base de dados a partir das entidades

Criamos o projeto de consola VS 2012 [RdvMedecins-MySQL-01] [1] abaixo:

  • Em [2], adicione referências ao projeto através do NuGet;
  • Em [3], adicionamos a referência ao EF 5;
  • em [4], ela já consta nas referências;
  • em [5], repetimos o processo para adicionar [MySQL.Data.Entities], que é um conector ADO.NET para o Entity Framework. Para encontrar o pacote, podemos usar a caixa de pesquisa [6];
  • em [7], aparecem duas referências: [MySQL.Data.Entities] e [MySQL.Data], sendo esta última uma dependência da primeira.

Agora, vamos compilar o projeto [RdvMedecins-MySQL-01] com base no projeto [RdvMedecins-SqlServer-01].

  • Em [1], copie os itens selecionados;
  • Em [2], cole-os no projeto [RdvMedecins-MySQL-01];
  • em [3], como existem vários programas com um método [Main], precisamos de especificar o projeto de arranque.

Nesta altura, o projeto deverá ser compilado com sucesso. Agora, vamos modificar o ficheiro de configuração [App.config], que configura a cadeia de ligação à base de dados e o DbProviderFactory. Fica da seguinte forma:


<?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>
  • linha 17: a cadeia de ligação à base de dados MySQL [rdvmedecins-ef] que criámos;
  • linha 24: a versão deve corresponder à da referência [MySql.Data] no projeto [1]:

Existe também alguma configuração no ficheiro [Entities.cs] onde especificamos os nomes das tabelas e o esquema a que pertencem. Isto pode variar dependendo do SGBD. É o caso aqui, onde não haverá esquema. O ficheiro [Entities.cs] altera-se da seguinte forma:


  [Table("MEDECINS")]
  public class Medecin : Personne
  {...}
 
  [Table("CLIENTS")]
  public class Client : Personne
  {...}
 
  [Table("CRENEAUX")]
  public class Creneau
  {...}
 
  [Table("RVS")]
  public class Rv
  {...}

Vamos executar o programa [CreateDB_01] [2]. Obtemos a seguinte exceção:

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

O mesmo erro aparece quatro vezes (linhas 2–5). O tipo rowversion sugere o campo com a anotação [Timestamp] nas entidades:


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

Decidimos substituir estas três linhas pelo seguinte:


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

Alteramos o tipo da coluna de byte[] para DateTime?. Fazemos isto porque o MySQL possui um tipo [TIMESTAMP] que representa uma data/hora, e uma coluna deste tipo é automaticamente atualizada pelo MySQL sempre que a linha é atualizada. Isto permitir-nos-á gerir o acesso simultâneo.

A anotação [Timestamp] só pode ser aplicada a uma coluna do tipo byte[]. Substituímo-la pela anotação [ConcurrencyCheck]. Ambas as anotações tratam do acesso simultâneo. Fazemos isto para as quatro entidades e, em seguida, voltamos a executar a aplicação. Recebemos então o seguinte erro:

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

A linha 1 indica um erro de sintaxe no SQL executado pelo MySQL. Uma vez que isto não foi gerado por nós, mas pelo fornecedor ADO.NET do MySQL, não podemos corrigir este problema. No entanto, podemos ver que as tabelas foram criadas [1] abaixo:

  • em [2], vemos a estrutura da tabela [clients] [3].

Há várias alterações a fazer na base de dados gerada:

  • o tipo de dados da coluna [VERSIONING] está incorreto. Deve ser definido como o tipo [TIMESTAMP] do MySQL;
  • Lembre-se de que a tabela [rvs] tem uma restrição de unicidade. Esta não foi criada durante esta geração;
  • O conector ADO.NET do SQL Server gerou chaves estrangeiras com a cláusula ON DELETE CASCADE. O conector ADO.NET do MySQL não fez isso.

Tal como fizemos com o SQL Server, temos, portanto, de modificar a base de dados gerada. Não mostramos como efetuar as modificações. Apenas fornecemos o script para criar a base de dados:


# 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=''
;
  • linhas 22, 38, 54, 74: as chaves primárias (ID) das tabelas são do tipo AUTO_INCREMENT e, portanto, são geradas pelo MySQL;
  • linhas 26, 42, 60, 78: a coluna VERSIONING é do tipo TIMESTAMP e é atualizada durante um INSERT ou UPDATE;
  • linha 63: a chave estrangeira da tabela [slots] para a tabela [doctors] com a cláusula ON DELETE CASCADE;
  • linha 80: a restrição de exclusividade na tabela [rvs];
  • linha 83: a chave estrangeira da tabela [rvs] para a tabela [slots] com a cláusula ON DELETE CASCADE;
  • linha 84: a chave estrangeira da tabela [rvs] para a tabela [clients] com a cláusula ON DELETE CASCADE;

O script para gerar as tabelas na base de dados MySQL [rvmedecins-ef] foi colocado na pasta [RdvMedecins / databases / mysql]. O leitor pode carregá-lo e executá-lo para criar as tabelas.

Depois de feito isto, os vários programas do projeto podem ser executados. Produzem os mesmos resultados que com o SQL Server, exceto o programa [ModifyDetachedEntities], que falha. Para compreender porquê, podemos analisar a saída do programa [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]
  • Linhas 1-2: um cliente antes de o contexto ser guardado;
  • linhas 3-4: o cliente após a gravação. Possui uma chave primária, mas não tem valor para o seu campo [Versioning], enquanto o SQL Server atualizou o campo [Timestamp] da entidade.

Agora, vamos examinar o código do programa [ModifyDetachedEntities] que falha:


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)
    {
      ...
    }
  }
}
  • Linha 20: Um cliente é guardado. Agora tem a sua chave primária, mas com base na sua versão;
  • linha 33: é feita uma modificação no cliente1. A modificação falha porque não possui a versão armazenada na base de dados.

Resolvemos o problema inserindo o seguinte código entre as linhas 25 e 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;
}

Agora, a entidade [client1] tem a mesma versão que na base de dados e pode, portanto, ser utilizada para atualizar a linha na base de dados.

4.3. Arquitetura multicamadas baseada no EF 5

Voltemos ao estudo de caso descrito na secção 2.

Começaremos por construir a camada de acesso a dados [DAO]. Para tal, criamos o projeto de consola do VS 2012 [RdvMedecins-MySQL-02] [1]:

  • em [2], as referências [Common.Logging, EntityFramework, MySql.Data, MySql.Data.Entity, Spring.Core] são adicionadas utilizando o NuGet;
  • em [3], a pasta [Models] é copiada do projeto [RdvMedecins-MySQL-01];
  • em [4], as pastas [Dao, Exception, Tests] e o ficheiro [App.config] são copiados do projeto [RdvMedecins-SqlServer-02];
  • em [5], o ficheiro [Program.cs] foi eliminado;
  • em [6], o projeto está configurado para executar o programa de teste da camada [DAO].

No ficheiro [App.config], as informações da base de dados SQL Server são substituídas pelas da base de dados MySQL. Estas informações podem ser encontradas no ficheiro [App.config] do projeto [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>

Os objetos geridos pelo Spring também mudam. Atualmente, temos:


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

A linha 7 faz referência ao assembly do projeto [RdvMedecins-SqlServer-02]. O assembly é agora [RdvMedecins-MySQL-02].

Feito isso, estamos prontos para executar o teste da camada [DAO]. Primeiro, temos de garantir que a base de dados está preenchida (utilizando o programa [Fill] do projeto [RdvMedecins-MySQL-01]). O programa de teste é executado com sucesso.

Criamos a DLL do projeto tal como fizemos para o projeto [RdvMedecins-SqlServer-02] e colocamos todas as DLLs do projeto numa pasta [lib] criada dentro de [RdvMedecins-MySQL-02]. Estas servirão de referências para o projeto web [RdvMedecins-MySQL-03], que abordaremos a seguir.

  

Estamos agora prontos para compilar a camada [ASP.NET] da nossa aplicação:

Começaremos pelo projeto [RdvMedecins-SqlServer-03]. Duplicamos a pasta deste projeto para [RdvMedecins-MySQL-03] [1]:

  • em [2], utilizando o VS 2012 Express for the Web, abrimos a solução na pasta [RdvMedecins-MySQL-03];
  • Em [3], alteramos tanto o nome da solução como o nome do projeto;
  • em [4], as referências atuais do projeto;
  • em [5], eliminamo-las;
  • em [6], para as substituir por referências às DLLs que acabámos de guardar numa pasta [lib] dentro do projeto [RdvMedecins-MySQL-02].

Resta apenas modificar o ficheiro [Web.config]. Substituímos o seu conteúdo atual pelo conteúdo do ficheiro [App.config] do projeto [RdvMedecins-MySQL-02]. Depois de feito isto, executamos o projeto web. Funciona.

4.4. Conclusão

Vamos resumir o que foi feito para mudar do SGBD SQL Server para o SGBD MySQL:

  • O campo utilizado para gerir o acesso simultâneo às entidades foi alterado. A sua versão no SQL Server era:

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

Passou a ser:


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

com MySQL;

  • as anotações [Table] que ligam uma entidade a uma tabela foram alteradas;
  • a cadeia de ligação à base de dados e o [DbProviderFactory] foram modificados nos ficheiros de configuração [App.config] e [Web.config];
  • Após o armazenamento na base de dados, uma entidade do SQL Server tinha tanto a sua chave primária como o seu carimbo de data/hora. Com o MySQL, tinha apenas a sua chave primária. Isto exigiu uma alteração no código.

No final, houve relativamente poucas alterações, mas ainda assim tivemos de rever o código. Estamos a repetir o mesmo processo para outros três SGBDs:

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