Skip to content

4. Estudo de caso com MySQL 5.5.28

4.1. Instalação das ferramentas

As ferramentas a serem instaladas 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 a seguir, o usuário root possui a senha root.

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

Agora iniciamos a ferramenta [SQL Manager Lite for MySQL], com a qual vamos administrar o SGBD e o [3].

  • No [4], criamos um novo banco de dados;
  • no [5], indicamos o nome do banco de dados;
  • em [5], fazemos login como root / root;
  • em [6], confirmamos o comando SQL que será executado;
  • em [7], o banco de dados foi criado. Agora ele deve ser registrado em [EMS Manager]. As informações estão corretas. Executa-se [OK];
  • em [8], conectamo-nos a ela;
  • no [9], o [EMS Manager] exibe o banco de dados, que, por enquanto, está vazio.

Agora vamos conectar um projeto VS 2012 a esse banco de dados.

4.2. Criação do banco de dados a partir das entidades

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

  • no [2], adicionamos referências ao projeto por meio do NuGet;
  • no [3], adiciona-se a referência EF 5;
  • no [4], ela agora consta 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 localizar o pacote, pode-se usar a área de pesquisa [6];
  • em [7], aparecem duas referências: [MySQL.Data.Entities] e [MySQL.Data], sendo que a última é uma dependência da primeira.

Agora, vamos construir 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], como há vários programas com o método [Main], precisamos especificar o projeto de partida.

Nesta etapa, a geração do projeto deve ser bem-sucedida. Agora, vamos modificar o arquivo de configuração [App.config], que configura a cadeia de conexão com o banco de dados, e o DbProviderFactory. Ele fica da seguinte forma:


<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <!-- Para obter mais informações sobre a configuração do Entity Framework, acesse 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 conexã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>

</configuration>
  • linha 17: a string de conexão com o banco de dados MySQL [rdvmedecins-ef] que criamos;
  • linha 24: a versão deve corresponder à da referência [MySql.Data] do projeto [1]:

Há também algumas configurações no arquivo [Entites.cs], onde se especifica o nome das tabelas, bem como o esquema ao qual elas pertencem. Isso pode variar de acordo com o SGBD. É o caso aqui, onde não haverá esquema. O arquivo [Entites.cs] é alterado 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]. Recebemos 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 a 5). O tipo rowversion remete ao campo com a anotação [Timestamp] nas entidades:


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

Decidimos substituir essas 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 isso porque MySQL tem um tipo [TIMESTAMP], que representa uma data/hora, e uma coluna com esse tipo é automaticamente atualizada por MySQL sempre que a linha é atualizada. Isso nos permitirá gerenciar acessos simultâneos.

A anotação [Timestamp] só pode ser aplicada a uma coluna do tipo byte[]. Substituímos essa anotação pela anotação [ConcurrencyCheck]. Ambas as anotações gerenciam o acesso simultâneo. Fazemos isso para as quatro entidades e, em seguida, reexecutamos o aplicativo. 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 na anotação SQL executada pela anotação MySQL. Como esse erro não foi gerado por nós, mas pelo provedor ADO.NET do MySQL, não podemos corrigir esse ponto. No entanto, é possível constatar que foram criadas as tabelas [1] abaixo:

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

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

  • o tipo da coluna [VERSIONING] não é adequado. É preciso definir o tipo MySQL [TIMESTAMP];
  • vale lembrar que a tabela [rvs] possui uma restrição de exclusividade. Ela não foi criada por essa geração;
  • o conector ADO.NET do servidor SQL havia gerado chaves estrangeiras com a cláusula ON DELETE CASCADE. O conector ADO.NET do MySQL não fez isso.

Assim como fizemos com o servidor SQL, precisamos, portanto, modificar o banco de dados gerado. Não mostramos como fazer as modificações. Apenas fornecemos o script de criação do banco de dados:


# SQL Manager Lite para MySQL 5.3.0.2
# ---------------------------------------
# Host     : localhost
# Porta     : 3306
# Banco 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, portanto 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 do banco de dados MySQL [rvmedecins-ef] foi colocado na pasta [RdvMedecins / databases / mysql]. O leitor poderá carregá-lo e executá-lo para criar suas tabelas.

Feito isso, os diversos programas do projeto podem ser executados. Eles fornecem os mesmos resultados que com o SQL Server, exceto pelo programa [ModifyDetachedEntities], que trava. Para entender o motivo, pode-se observar 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. Ele possui uma chave primária, mas não há valor para o campo [Versioning], enquanto o SQL Server atualizava o campo [Timestamp] da entidade.

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


using System;
...

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

      // esvazia-se o banco 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);
        // salvando o contexto
        context.SaveChanges();
      }
      // exibição básica
      Dump("1-----------------------------");
      // cliente1 não está no contexto — ele é modificado
      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 em um estado modificado
        context.Entry(client1).State = EntityState.Modified;
        // salvamos o contexto
        context.SaveChanges();
      }
      ...
    }

    static void Erase()
    {
      ...
    }

    static void Dump(string str)
    {
      ...
    }
  }
}
  • linha 20: um cliente é salvo. Ele passa a ter sua chave primária, mas com base em sua versão;
  • linha 33: é feita uma modificação no cliente1. Ela falha porque ele não possui a versão que está no banco de dados.

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


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

Agora, a entidade [client1] tem a mesma versão que está no banco de dados e, portanto, pode ser usada para atualizar a linha no banco de dados.

4.3. Arquitetura multicamadas baseada em EF 5

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

Começaremos construindo a camada [DAO] de acesso aos dados. Para isso, criamos o projeto de console VS 2012 [RdvMedecins-MySQL-02] [1]:

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

No arquivo [App.config], as informações do banco de dados SQL Server são substituídas pelas do banco de dados MySQL. Elas podem ser encontradas no arquivo [App.config] do projeto [RdvMedecins-MySQL-01]:


<!-- cadeia de conexã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 gerenciados pelo Spring também mudam. 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 isso, estamos prontos para executar o teste da camada [DAO]. Antes disso, é preciso preencher o banco de dados (programa [Fill] do projeto [RdvMedecins-MySQL-01]). O programa de teste é executado com sucesso.

Criamos o DLL do projeto, da mesma forma que foi feito para o projeto [RdvMedecins-SqlServer-02], e reunimos otodos os arquivos DLL do projeto em uma pasta [lib] criada no [RdvMedecins-MySQL-02]. Essas serão as referências do projeto web [RdvMedecins-MySQL-03] que virá a seguir.

  

Agora estamos prontos para construir a camada [ASP.NET] do nosso aplicativo:

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

  • no [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 quanto o nome do projeto;
  • no [4], as referências atuais do projeto;
  • em [5], as eliminamos;
  • em [6], para substituí-las por referências ao DLL que acabamos de armazenar na pasta [lib] do projeto [RdvMedecins-MySQL-02].

Resta-nos apenas modificar o arquivo [Web.config]. Substituímos seu conteúdo atual pelo conteúdo do arquivo [App.config] do projeto [RdvMedecins-MySQL-02]. Feito isso, executamos o projeto web. Ele funciona.

4.4. Conclusion

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

  • o campo usado para gerenciar a concorrência de acesso às entidades foi alterado. Sua versão no servidor SQL era:

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

E passou a ser:


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

com MySQL;

  • as anotações [Table] que vinculam uma entidade a uma tabela foram alteradas;
  • a string de conexão com o banco de dados e o [DbProviderFactory] foram modificados nos arquivos de configuração [App.config] e [Web.config];
  • após o salvamento no banco de dados, uma entidade SQL Server possuía tanto sua chave primária quanto seu Timestamp. Com o MySQL, ela possuía apenas sua chave primária. Isso levou à modificação de um trecho de código.

No fim das contas, foram poucas alterações, mas mesmo assim foi necessário revisar 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.