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 para MySQL, Freeware [http://www.sqlmanager.net/fr/products/mysql/manager/download].

Nos exemplos que se seguem, o utilizador root tem a palavra-passe root.

Vamos iniciar o MySQL5. Aqui, fazemo-lo a partir da janela de serviços do Windows [1]. No [2], o SGBD é iniciado.

Iniciamos agora a ferramenta [SQL Manager Lite for MySQL], com a qual iremos administrar o SGBD [3].

  • No [4], criamos uma nova base de dados;
  • no [5], indicamos o nome da base de dados;
  • em [5], iniciamos sessão como root / root;
  • em [6], validamos o comando SQL que vai ser executado;
  • em [7], a base de dados foi criada. Deve agora ser registada em [EMS Manager]. As informações estão corretas. Executa-se [OK];
  • em [8], ligamo-nos à base;
  • em [9], o [EMS Manager] apresenta a base de dados, por enquanto vazia.

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

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

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

  • no [2], adicionamos referências ao projeto através do NuGet;
  • no [3], adiciona-se a referência EF 5;
  • no [4], esta encontra-se agora nas referências;
  • em [5], repetimos o processo para adicionar, desta vez, [MySQL.Data.Entities], que é um conector ADO.NET para o Entity Framework. Para encontrar o pacote, podemos utilizar a barra de pesquisa com o termo [6];
  • em [7], aparecem duas referências: [MySQL.Data.Entities] e [MySQL.Data], sendo que a última é uma dependência da primeira.

Agora, vamos compilar o projeto [RdvMedecins-MySQL-01] a partir do projeto [RdvMedecins-SqlServer-01].

  • no [1], copiamos os elementos selecionados;
  • no [2], colamos esses elementos no projeto [RdvMedecins-MySQL-01];
  • no [3], uma vez que existem vários programas com o método [Main], é necessário especificar o projeto de partida.

Nesta fase, a geração do projeto deve ser bem-sucedida. Agora, vamos alterar 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>
    <!-- Para mais informações sobre a configuração do Entity Framework, visite 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>

  <!-- cadeia de ligação-->
  <connectionStrings>
    <add name="monContexte"
         connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
         providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
  <!-- o fornecedor de fábrica -->
  <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] do projeto [1]:

Existe também alguma configuração no ficheiro [Entites.cs], onde se especificam os nomes das tabelas, bem como o esquema a que pertencem. Este pode variar consoante o SGBD. É o caso aqui, onde não haverá esquema. O ficheiro [Entites.cs] evolui 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
  {...}

Executemos o programa [CreateDB_01] [2]. Obtém-se 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 faz pensar no campo com a anotação [Timestamp] nas entidades:


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

Decidimos substituir estas três linhas pelas seguintes:


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

Alteramos o tipo da coluna, que passa de byte[] para DateTime?. Fazemos isto porque MySQL tem um tipo [TIMESTAMP] que representa uma data/hora e porque uma coluna com este tipo é automaticamente atualizada por MySQL sempre que a linha é atualizada. Isto permitir-nos-á gerir os acessos simultâneos.

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 gerem a concorrência de acesso. 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. Como este não foi gerado por nós, mas sim pelo provedor ADO.NET do MySQL, não podemos corrigir este ponto. No entanto, é possível constatar que foram criadas tabelas [1], conforme se pode ver abaixo:

  • em [2], é possível ver a estrutura da tabela [clients] [3].

Há várias alterações a efetuar na base gerada:

  • o tipo da coluna [VERSIONING] não é adequado. É necessário atribuir-lhe o tipo MySQL [TIMESTAMP];
  • lembre-se de que a tabela [rvs] tem uma restrição de unicidade. Esta não foi criada por esta geração;
  • o conector ADO.NET do servidor SQL tinha gerado chaves estrangeiras com a cláusula ON DELETE CASCADE. O conector ADO.NET do MySQL não o fez.

Tal como fizemos com o servidor SQL, temos, portanto, de alterar a base de dados gerada. Não mostramos como efetuar as alterações. Apresentamos apenas o script de criação da base de dados:


# SQL Manager Lite para MySQL 5.3.0.2
# ---------------------------------------
# Host     : localhost
# Porta     : 3306
# Base de dados: 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`;

#
# Estrutura da tabela `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=''
;

#
# Estrutura da tabela `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=''
;

#
# Estrutura da tabela `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=''
;

#
# Estrutura da tabela `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=''
;
  • linhas 22, 38, 54, 74: as chaves primárias ID das tabelas são do tipo AUTO_INCREMENT, pelo que foram geradas por MySQL;
  • linhas 26, 42, 60, 78: a coluna VERSIONING é do tipo TIMESTAMP e é atualizada durante um INSERT ou um UPDATE;
  • linha 63: a chave estrangeira da tabela [creneaux] para a tabela [medecins] com a cláusula ON DELETE CASCADE;
  • linha 80: a restrição de unicidade da tabela [rvs];
  • linha 83: a chave estrangeira da tabela [rvs] para a tabela [creneaux] 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 de geração das tabelas da base de dados MySQL [rvmedecins-ef] foi colocado na pasta [RdvMedecins / databases / mysql]. O leitor poderá carregá-lo e executá-lo para criar as suas tabelas.

Feito isto, os diferentes programas do projeto podem ser executados. Estes produzem os mesmos resultados que com o SQL Server, exceto no caso do programa [ModifyDetachedEntities], que entra em falha. Para compreender o motivo, pode-se analisar o resultado do programa [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]
  • linhas 1-2: um cliente antes do salvamento do contexto;
  • linhas 3-4: o cliente após o salvamento. Possui uma chave primária, mas não tem valor para o seu campo [Versioning], enquanto o SQL Server atualizava o campo [Timestamp] da entidade.

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


using System;
...

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

      // esvazia-se a base de dados atual
      Erase();
      // adicionar um cliente
      using (var context = new RdvMedecinsContext())
      {
        // Criação de cliente
        client1 = new Client { Titre = "x", Nom = "x", Prenom = "x" };
        // adição do cliente ao contexto
        context.Clients.Add(client1);
        // guardar o contexto
        context.SaveChanges();
      }
      // visualização da base
      Dump("1-----------------------------");
      // O cliente1 não está no contexto — altera-se o contexto
      client1.Nom = "y";
      // alteração de entidade fora do contexto
      using (var context = new RdvMedecinsContext())
      {
        // aqui, temos um novo contexto vazio
        // colocamos o cliente1 no contexto num estado alterado
        context.Entry(client1).State = EntityState.Modified;
        // guardamos o contexto
        context.SaveChanges();
      }
      ...
    }

    static void Erase()
    {
      ...
    }

    static void Dump(string str)
    {
      ...
    }
  }
}
  • linha 20: um cliente é guardado. Passa então a ter a sua chave primária, mas com a sua versão;
  • linha 33: é efetuada uma alteração no cliente1. A alteração falha porque o cliente não tem a versão que consta na base de dados.

Resolvemos o problema inserindo o seguinte código entre as linhas 25 e 26:


      // recuperamos o cliente1 para obter a sua versão
      using (var context = new RdvMedecinsContext())
      {
        // o «client2» estará no contexto
        Client client2 = context.Clients.Find(client1.Id);
        // define-se a versão do cliente1 para a do cliente2
        client1.Versioning = client2.Versioning;
}

A partir de 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 em EF 5

Voltamos ao nosso caso de estudo descrito no parágrafo 2.

Vamos começar por construir a camada [DAO] de acesso aos dados. Para tal, criamos o projeto de consola VS 2012 [RdvMedecins-MySQL-02] [1]:

  • em [2], as referências [Common.Logging, EntityFramework, MySql.Data, MySql.Data.Entity, Spring.Core] são adicionadas juntamente com 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;
  • no [6], o projeto está configurado para executar o programa de teste da camada [DAO].

No ficheiro [App.config], substituem-se as informações da base de dados SQL Server pelas da base de dados MySQL. Estas informações encontram-se no ficheiro [App.config] do projeto [RdvMedecins-MySQL-01]:


<!-- cadeia de ligação-->
  <connectionStrings>
    <add name="monContexte"
         connectionString="Server=localhost;Database=rdvmedecins-ef;Uid=root;Pwd=root;"
         providerName="MySql.Data.MySqlClient" />
  </connectionStrings>
  <!-- o provedor de fábrica -->
  <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 sofrem alterações. Atualmente, temos:


  <!-- configuração do 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>

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

Feito isto, estamos prontos para executar o teste da camada [DAO]. Antes disso, é necessário certificar-se de que a base de dados está preenchida (programa [Fill] do projeto [RdvMedecins-MySQL-01]). O programa de teste é bem-sucedido.

Criamos o DLL do projeto, tal como foi feito para o projeto [RdvMedecins-SqlServer-02], e reunimos oconjunto de DLL do projeto numa pasta [lib] criada em [RdvMedecins-MySQL-02]. Estas serão as referências do projeto web [RdvMedecins-MySQL-03] que se seguirá.

  

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

Vamos partir do projeto [RdvMedecins-SqlServer-03]. Duplicamos a pasta deste projeto em [RdvMedecins-MySQL-03] e [1]:

  • em [2], com o VS 2012 Express para a Web, abrimos a solução da pasta [RdvMedecins-MySQL-03];
  • em [3], alteramos tanto o nome da solução como o nome do projeto;
  • no [4], as referências atuais do projeto;
  • em [5], eliminamo-las;
  • em [6], para as substituir por referências ao ficheiro DLL que acabámos de guardar na pasta [lib] do projeto [RdvMedecins-MySQL-02].

Resta-nos 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]. Feito isto, executamos o projeto web. Funciona.

4.4. Conclusion

Vamos recapitular o que foi feito para passar do servidor SGBD SQL para o SGBD MySQL:

  • o campo que servia para gerir a concorrência de acesso às entidades foi alterado. A sua versão no servidor SQL 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 associam uma entidade a uma tabela foram alteradas;
  • a cadeia de ligação à base de dados e o [DbProviderFactory] foram alterados nos ficheiros de configuração [App.config] e [Web.config];
  • após o salvamento na base de dados, uma entidade SQL Server possuía simultaneamente a sua chave primária e o seu Timestamp. Com o MySQL, possuía apenas a sua chave primária. Isto levou à alteração de um código.

No final, foram poucas alterações, mas mesmo assim foi necessário rever o código. Repetimos o mesmo procedimento para outros três SGBD:

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