Skip to content

6. Caso de estudo com o PostgreSQL 9.2.1

6.1. Instalação das ferramentas

As ferramentas a instalar são as seguintes:

  • o SGBD: [http://www.enterprisedb.com/products-services-training/pgdownload#windows];
  • uma ferramenta de administração: EMS, SQL Manager para PostgreSQL, Freeware [http://www.sqlmanager.net/fr/products/postgresql/manager/download].

Nos exemplos que se seguem, o utilizador «postgres» tem a palavra-passe «postgres».

Vamos iniciar o PostgreSQL e, em seguida, a ferramenta [SQL Manager Lite for PostgreSQL], com a qual iremos administrar o SGBD.

  • no [1], iniciamos o SGBD e o PostgreSQL a partir dos serviços do Windows;
  • no [2], o serviço é iniciado;

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

  • no [4], criamos uma nova base de dados;
  • no [5], indicamos o nome da base de dados;
  • em [5], iniciamos sessão como postgres / postgres;
  • em [6], fornecemos algumas informações;
  • em [7], valida-se a ordem SQL que vai ser executada;
  • no [8], a base de dados foi criada. Deve agora ser registada no [EMS Manager]. As informações estão corretas. Executa-se o [OK];
  • no [9], ligamo-nos à base;
  • em [10], o [EMS Manager] apresenta a base de dados, por enquanto vazia. Note-se que as tabelas pertencerão a um esquema denominado «public [11]».

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

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

Começamos por duplicar a pasta do projeto [RdvMedecins-SqlServer-01] em [RdvMedecins-PostgreSQL-01] e [1]:

  • em [2]; em VS 2012, eliminamos o projeto [RdvMedecins-SqlServer-01] da solução;
  • em [3], o projeto foi eliminado;
  • em [4], adicionamos outro. Este está incluído na pasta [RdvMedecins-PostgreSQL-01] que criámos anteriormente;
  • em [5], o projeto carregado chama-se [RdvMedecins-SqlServer-01];
  • em [6], alteramos o seu nome para [RdvMedecins-PostgreSQL-01];
  • em [7], adiciona-se outro projeto à solução. Este encontra-se na pasta [RdvMedecins-SqlServer-01] do projeto que eliminámos anteriormente da solução;
  • em [8], o projeto [RdvMedecins-SqlServer-01] foi reintegrado na solução.

O projeto [RdvMedecins-PostgreSQL-01] é idêntico ao projeto [RdvMedecins-SqlServer-01]. Temos de fazer algumas alterações. No [App.config], vamos alterar a cadeia de ligação e o [DbProviderFactory], que temos de adaptar a cada SGBD.


<!-- cadeia de ligação à base de dados -->
  <connectionStrings>
    <add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
  </connectionStrings>
  <!-- o provedor de fábrica -->
  <system.data>
    <DbProviderFactories>
      <add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql Server" type="Npgsql.NpgsqlFactory, Npgsql, Version=2.0.11.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7" />
    </DbProviderFactories>
  </system.data>
  • linha 3: o utilizador e a sua palavra-passe;
  • linhas 7-9: o DbProviderFactory. A linha 8 faz referência a um DLL [Npgsql] que não temos. Pode-se obtê-lo através de NuGet [1]:
  • em [2], na área de pesquisa, introduz-se a palavra-chave postgresql;
  • em [3], selecione o pacote [Npgsql]. Trata-se de um conector ADO.NET para PostgreSQL;
  • no [4], foram adicionadas duas referências;
  • no [5], no [App.config], é necessário indicar a versão correta do DLL. Esta encontra-se nas suas propriedades.

No ficheiro [Entites.cs], é necessário adaptar o esquema das tabelas que vão ser geradas:


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

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

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

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

Vimos anteriormente, durante a criação de uma base de dados PostgreSQL, que as tabelas pertenciam a um esquema denominado «public».

Configuramos a execução do projeto:

  • em [1], atribuímos outro nome ao assembly que vai ser gerado;
  • em [2], definimos também outro espaço de nomes por predefinição;
  • em [3], indicamos o programa a executar.

Nesta fase, não há erros de compilação. Vamos executar o programa [CreateDB_01]. 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-Oracle-01\CreateDB_01.cs:ligne 15

Recordamo-nos de ter tido o mesmo erro com o MySQL e o Oracle. Está relacionado com o tipo do campo Timestamp das entidades. Fazemos a mesma alteração que fizemos com o Oracle. Nas entidades, substituímos as três linhas


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

pelas seguintes:


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

Alteramos o tipo da coluna de byte[] para int?. No SGBD, utilizaremos procedimentos armazenados para incrementar este inteiro em uma unidade sempre que uma linha for inserida ou alterada.

Efetuaremos a alteração anterior nas quatro entidades e, em seguida, reexecutaremos a aplicação. Obteremos então o seguinte erro:

1
2
3
4
5
6
Exception non gérée : System.Data.DataException: An exception occurred while initializing the database. See the InnerException for details. ---> System.Data.ProviderIncompatibleException: DeleteDatabase n'est pas pris en charge par le fournisseur.
   à System.Data.Common.DbProviderServices.DbDeleteDatabase(DbConnection connection, Nullable`1 commandTimeout, StoreItemCollection storeItemCollection)
   ...
   à System.Data.Entity.Internal.LazyInternalContext.InitializeDatabase()
   à RdvMedecins_01.CreateDB_01.Main(String[] args) dans d:\data\istia-1213\c#\d
vp\Entity Framework\RdvMedecins\RdvMedecins-Oracle-01\CreateDB_01.cs:ligne 15

A linha 1 indica que o conector ADO.NET de PostgreSQL não consegue eliminar a base de dados existente. Exatamente como acontece com o Oracle. Somos, então, obrigados a criar manualmente a base de dados [RDVMEDECINS-EF] com a ferramenta [EMS Manager for PostgreSQL]. Não descrevemos todos os passos, mas apenas os mais importantes.

A base de dados PostgreSQL terá a seguinte estrutura:

As tabelas

  • em [1], ID é a chave primária do tipo serial. Este tipo PostgreSQL é um inteiro gerado automaticamente pelo SGBD.

As diferentes tabelas têm as chaves primárias e estrangeiras que essas mesmas tabelas tinham nos exemplos anteriores. As chaves estrangeiras têm o atributo ON, DELETE e CASCADE.

As sequências

Tal como no Oracle, criámos aqui sequências. Trata-se de geradores de números consecutivos. Existem 5: [1].

  • em [2], vemos as propriedades da sequência [CLIENTS_ID_SEQ]. Esta gera números consecutivos de 1 em 1, a partir de 1 até um valor muito grande.

Todas as sequências seguem o mesmo modelo.

  • [CLIENTS_ID_seq] será utilizada para gerar a chave primária da tabela [CLIENTS];
  • [MEDECINS_ID_seq] será utilizada para gerar a chave primária da tabela [MEDECINS];
  • [CRENEAUX_ID_seq] será utilizada para gerar a chave primária da tabela [CRENEAUX];
  • [RVS_ID_seq] será utilizada para gerar a chave primária da tabela [RVS];
  • [sequence_versions] será utilizada para gerar os valores das colunas [VERSIONING] de todas as tabelas.

Os gatilhos

Um trigger é um procedimento executado pelo SGBD antes ou depois de um evento (inserção, modificação, eliminação) numa tabela. Temos 4 triggers [1]:

Vejamos o código DDL do trigger [CLIENTS_tr] que preenche a coluna [VERSIONING] da tabela [CLIENTS]:

1
2
3
4
CREATE TRIGGER "CLIENTS_tr"
  BEFORE INSERT OR UPDATE 
  ON public."CLIENTS" FOR EACH ROW 
EXECUTE PROCEDURE public.trigger_versions();
  • linhas 1-3: antes de cada operação INSERT ou UPDATE na tabela [CLIENTS];
  • linha 4: é executado o procedimento [public.trigger_versions()].

O procedimento [public.trigger_versions()] é o seguinte:

1
2
3
4
BEGIN
NEW."VERSIONING":=nextval('sequence_versions');
return NEW;
END
  • linha 2: NEW representa a linha que vai ser inserida ou alterada. NEW. «VERSIONING» é a coluna [VERSIONING] dessa linha. Atribui-se-lhe o seguinte valor do gerador de números: «sequence_versions». Assim, a coluna ["VERSIONING"] altera-se sempre que se efetua uma operação INSERT / UPDATE na tabela [CLIENTS].

Os gatilhos [MEDECINS_tr, CRENEAUX_tr, RVS_tr] funcionam de forma semelhante. As quatro colunas ["VERSIONING"] obtêm os seus valores da mesma sequência.

O script de geração das tabelas da base de dados PostgreSQL e [RDVMEDECINS-EF] foi colocado na pasta [RdvMedecins / databases / postgreSQL]. O leitor poderá carregá-lo e executá-lo para criar as suas tabelas.

Feito isto, os diferentes programas do projeto podem ser executados. Estes fornecem os mesmos resultados que com o SQL Server, exceto no caso do programa [ModifyDetachedEntities], que entra em falha pela mesma razão que entrava em falha com o Oracle. O problema resolve-se da mesma forma. Basta copiar o programa [ModifyDetachedEntities] do projeto [RdvMedecins-Oracle-01] para o projeto [RdvMedecins-PostgreSQL-01].

O programa [LazyEagerLoading] falha com a seguinte exceção:

1
2
3
4
Exception non gérée : System.Data.EntityCommandExecutionException: Une erreur s'est produite lors de l'exécution de la définition de la commande. Pour plus de détails, consultez l'exception interne. ---> Npgsql.NpgsqlException: ERREUR: 42601: erreur de syntaxe sur ou près de « LEFT »
   à Npgsql.NpgsqlState.<ProcessBackendResponses_Ver_3>d__a.MoveNext()
   ...
   à RdvMedecins_01.LazyEagerLoading.Main(String[] args) dans d:\data\istia-1213\c#\dvp\Entity Framework\RdvMedecins\RdvMedecins-PostgreSQL-01\LazyEagerLoading.cs:linha 23

O código com erro é o seguinte:


      using (var context = new RdvMedecinsContext())
      {
        // intervalo n.º 0
        creneau = context.Creneaux.Include("Medecin").Single<Creneau>(c => c.Id == idCreneau);
        Console.WriteLine(creneau.ShortIdentity());
}

Na linha n.º 1 da exceção, o erro sinalizado sugere uma junção, uma vez que LEFT é uma palavra-chave da junção. Uma vez que a linha 4 do código acima solicita o carregamento imediato da dependência [Medecin] de uma entidade [Creneau], o EF efetuou uma junção entre as tabelas [CRENEAUX] e [MEDECINS]. No entanto, parece que o conector ADO.NET gerou uma ordem SQL incorreta. Reescrevemos o código da seguinte forma:


      using (var context = new RdvMedecinsContext())
      {
        // intervalo n.º 0
        creneau = context.Creneaux.Find(idCreneau);
        Console.WriteLine(creneau.ShortIdentity());
        // força-se o carregamento do médico associado
        // isso é possível porque ainda estamos num contexto aberto
        Medecin medecin = creneau.Medecin;
}
  • linha 4: procuramos o intervalo sem junção;
  • linha 8: recuperamos a dependência em falta.

Funciona. Mais uma vez, constatamos que a alteração do SGBD tem impacto no código. Na verdade, não é o SGBD que está em causa aqui, mas sim o seu conector ADO.NET.

6.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, duplicamos o projeto de consola VS 2012 [RdvMedecins-SqlServer-02] em [RdvMedecins-PostgreSQL-02] [1]:

  • em [2], eliminamos o projeto [RdvMedecins-SqlServer-02];
  • em [3], adiciona-se um projeto existente à solução. Este é retirado da pasta [RdvMedecins-PostgreSQL-02] que acabou de ser criada;
  • em [4], o novo projeto tem o mesmo nome do que foi eliminado. Vamos alterar o seu nome;
  • em [5], alterámos o nome do projeto;
  • em [6], alteramos algumas das suas propriedades, como, neste caso, o nome do assembly;
  • em [7], a pasta [Models] é eliminada para ser substituída pela pasta [Models] do projeto [RdvMedecins-PostgreSQL-01]. Com efeito, os dois projetos partilham os mesmos modelos.
  • em [8], as referências atuais do projeto;
  • no [9], foi adicionado o conector ADO.NET do PostgreSQL com a ferramenta NuGet.

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


  <!-- cadeia de ligação à base de dados -->
  <connectionStrings>
    <add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
  </connectionStrings>
  <!-- o factory provider -->
  <system.data>
    <DbProviderFactories>
      <add name="Npgsql Data Provider" invariant="Npgsql" support="FF" description=".Net Framework Data Provider for Postgresql Server" type="Npgsql.NpgsqlFactory, Npgsql, Version=2.0.11.0, Culture=neutral, PublicKeyToken=5d8b90d52f46fda7" />
    </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-PostgreSQL-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-PostgreSQL-01]). O programa de teste falha com a seguinte exceção:

2012/10/12 13:56:27:188 [INFO]  Spring.Context.Support.XmlApplicationContext - A
pplicationContext Refresh: Completed
Liste des clients :
Client [47,Mr,Jules,Martin,468]
Client [48,Mme,Christine,German,469]
Client [49,Mr,Jules,Jacquard,470]
Client [50,Melle,Brigitte,Bistrou,471]
Liste des médecins :
Medecin [42,Mme,Marie,Pelissier,472]
Medecin [43,Mr,Jacques,Bromard,497]
Medecin [44,Mr,Philippe,Jandot,510]
Medecin [45,Melle,Justine,Jacquemot,511]
L'erreur suivante s'est produite : RdvMedecinsException[3,GetCreneauxMedecin,Une erreur s'est produite lors de l'exécution de la définition de la commande. Pour plus de détails, consultez l'exception interne.]

Na linha 13, a mensagem indica que o erro ocorreu no método [GetCreneauxMedecin] da camada [DAO]. Este método é o seguinte:


    // lista dos horários disponíveis de um determinado médico
    public List<Creneau> GetCreneauxMedecin(int idMedecin)
    {
      // lista de horários
      try
      {
        // abertura do contexto de persistência
        using (var context = new RdvMedecinsContext())
        {
          // recupera-se o médico com os seus horários
          Medecin medecin = context.Medecins.Include("Creneaux").Single(m => m.Id == idMedecin);
          // retorna a lista de horários do médico
          return medecin.Creneaux.ToList<Creneau>();
        }
      }
      catch (Exception ex)
      {
        throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
      }
}

Na linha 11, reconhece-se a palavra-chave Include, que já causou a falha de um programa anterior. O código anterior pode ser substituído pelo seguinte:


    // lista de horários de um determinado médico
    public List<Creneau> GetCreneauxMedecin(int idMedecin)
    {
      // lista de horários
      try
      {
        // abertura do contexto de persistência
        using (var context = new RdvMedecinsContext())
        {
          // retorna a lista de horários do médico
          return context.Creneaux.Where(c => c.MedecinId == idMedecin).ToList<Creneau>(); 
        }
      }
      catch (Exception ex)
      {
        throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
      }
}

O novo código parece até mais coerente do que o antigo. Seja como for, desta vez o programa de teste é executado com sucesso.

Criamos o DLL do projeto, tal como foi feito para o projeto [RdvMedecins-SqlServer-02], e reunimos otodos os ficheiros DLL do projeto numa pasta [lib] criada no [RdvMedecins-PostgreSQL-02]. Estas serão as referências do projeto web [RdvMedecins-PostgreSQL-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-PostgreSQL-03] [1]:

  • em [2], com o VS 2012 Express para a Web, abrimos a solução da pasta [RdvMedecins-PostgreSQL-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 DLL que acabámos de guardar numa pasta [lib] do projeto [RdvMedecins-PostgreSQL-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-PostgreSQL-02]. Feito isto, executamos o projeto web. Funciona. Não nos esqueçamos de preencher a base de dados antes de executar a aplicação web.