6. Estudo de caso com 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 for PostgreSQL Freeware [http://www.sqlmanager.net/fr/products/postgresql/manager/download].
Nos exemplos a seguir, o utilizador postgres tem a palavra-passe postgres.
Vamos iniciar o PostgreSQL e, em seguida, a ferramenta [SQL Manager Lite for PostgreSQL], que utilizaremos para administrar o SGBD.
![]() |
- Em [1], iniciamos o SGBD PostgreSQL a partir dos Serviços do Windows;
- Em [2], o serviço é iniciado;
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 postgres / postgres;
- Em [6], fornecemos algumas informações;
- Em [7], validamos a instrução SQL a ser executada;
![]() |
- Em [8], a base de dados foi criada. Agora, deve ser registada no [EMS Manager]. As informações estão corretas. Clique em [OK];
- Em [9], ligue-se a ela;
- Em [10], o [EMS Manager] apresenta a base de dados, que está atualmente vazia. Note 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] para [RdvMedecins-PostgreSQL-01] [1]:
![]() |
- em [2], no VS 2012, removemos o projeto [RdvMedecins-SqlServer-01] da solução;
![]() |
- em [3], o projeto foi removido;
- em [4], adicionamos outro. Este foi retirado da pasta [RdvMedecins-PostgreSQL-01] que criámos anteriormente;
![]() |
- em [5], o projeto carregado tem o nome [RdvMedecins-SqlServer-01];
- em [6], renomeamo-lo para [RdvMedecins-PostgreSQL-01];
![]() |
- Em [7], adicionamos outro projeto à solução. Este foi retirado da pasta [RdvMedecins-SqlServer-01] do projeto que removemos anteriormente da solução;
- em [8], o projeto [RdvMedecins-SqlServer-01] foi novamente adicionado à solução.
O projeto [RdvMedecins-PostgreSQL-01] é idêntico ao projeto [RdvMedecins-SqlServer-01]. Precisamos de fazer algumas alterações. Em [App.config], iremos modificar a cadeia de ligação e o [DbProviderFactory], que devem ser adaptados a cada SGBD.
<!-- connection chain on base -->
<connectionStrings>
<add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
</connectionStrings>
<!-- the 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>
- linha 3: o nome de utilizador e a palavra-passe;
- Linhas 7–9: o DbProviderFactory. A linha 8 faz referência a uma DLL [Npgsql] que não temos. Podemos obtê-la usando o NuGet [1]:
![]() |
- em [2], digite a palavra-chave postgresql na caixa de pesquisa;
- em [3], selecione o pacote [Npgsql]. Trata-se de um conector ADO.NET para PostgreSQL;
![]() |
- em [4], foram adicionadas duas referências;
- em [5], em [App.config], deve especificar a versão correta da DLL. Pode encontrá-la nas suas propriedades.
No ficheiro [Entities.cs], é necessário adaptar o esquema das tabelas que serão 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, ao criar uma base de dados PostgreSQL, que as tabelas pertenciam a um esquema chamado public.
Configuramos a execução do projeto:
![]() |
- em [1], atribuímos um nome diferente ao assembly que será gerado;
- em [2], especificamos também um namespace padrão diferente;
- em [3], especificamos o programa a ser executado.
Nesta fase, não há erros de compilação. Vamos executar o programa [CreateDB_01]. Recebemos a seguinte exceção:
Recordamos ter encontrado o mesmo erro com o MySQL e o Oracle. Isto está relacionado com o tipo do campo Timestamp nas entidades. Fazemos a mesma modificação que com o Oracle. Nas entidades, substituímos as três linhas
[Column("TIMESTAMP")]
[Timestamp]
public byte[] Timestamp { get; set; }
com o seguinte:
[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 um cada vez que uma linha for inserida ou modificada.
Fazemos a alteração acima em todas as quatro entidades e, em seguida, executamos novamente a aplicação. Recebemos então o seguinte erro:
A linha 1 indica que o conector PostgreSQL ADO.NET não consegue eliminar a base de dados existente. Tal como acontece com o Oracle. Por conseguinte, temos de criar manualmente a base de dados [RDVMEDECINS-EF] utilizando a ferramenta [EMS Manager for PostgreSQL]. Não iremos descrever todos os passos, mas apenas os mais importantes.
A base de dados PostgreSQL terá a seguinte estrutura:
As tabelas
![]() |
- Em [1], ID é uma chave primária do tipo serial. Este tipo do PostgreSQL é um número inteiro gerado automaticamente pelo SGBD.
![]() |
![]() |
![]() |
![]() |
As várias tabelas têm as chaves primárias e estrangeiras que estas mesmas tabelas tinham nos exemplos anteriores. As chaves estrangeiras têm o atributo ON DELETE CASCADE.
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]. Ela gera números consecutivos em incrementos de 1, começando em 1 até um valor muito grande.
Todas as sequências são construídas com base no 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];
- [SLOTS_ID_seq] será utilizado para gerar a chave primária da tabela [SLOTS];
- [RVS_ID_seq] será utilizado para gerar a chave primária da tabela [RVS];
- [sequence_versions] será utilizado para gerar os valores das colunas [VERSIONING] em todas as tabelas.
Gatilhos
Um gatilho é um procedimento executado pelo SGBD antes ou depois de um evento (Insert, Update, Delete) numa tabela. Temos 4 deles [1]:
![]() |
Vejamos o código DDL do trigger [CLIENTS_tr], que preenche a coluna [VERSIONING] da tabela [CLIENTS]:
- linhas 1-3: antes de cada operação INSERT ou UPDATE na tabela [CLIENTS];
- Linha 4: O procedimento [public.trigger_versions()] é executado.
O procedimento [public.trigger_versions()] é o seguinte:
- Linha 2: NEW representa a linha que está prestes a ser inserida ou modificada. NEW. "VERSIONING" é a coluna [VERSIONING] dessa linha. É-lhe atribuído o valor seguinte do gerador de números "sequence_versions". Assim, a coluna ["VERSIONING"] altera-se com cada INSERT ou UPDATE executado na tabela [CLIENTS].
Os gatilhos [MEDECINS_tr, CRENEAUX_tr, RVS_tr] funcionam da mesma forma. As quatro colunas ["VERSIONING"] obtêm os seus valores a partir da mesma sequência.
O script para gerar as tabelas na base de dados PostgreSQL [RDVMEDECINS-EF] foi colocado na pasta [RdvMedecins / databases / postgreSQL]. 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 pela mesma razão que falhou com o Oracle. O problema é resolvido 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:
O código incorreto é o seguinte:
using (var context = new RdvMedecinsContext())
{
// crenel n° 0
creneau = context.Creneaux.Include("Medecin").Single<Creneau>(c => c.Id == idCreneau);
Console.WriteLine(creneau.ShortIdentity());
}
Linha 1 da exceção: o erro relatado sugere uma junção, pois LEFT é uma palavra-chave de junção. Uma vez que a linha 4 do código acima solicita o carregamento imediato da dependência [Doctor] para uma entidade [Appointment], o EF realizou uma junção entre as tabelas [APPOINTMENTS] e [DOCTORS]. No entanto, parece que o conector ADO.NET gerou uma instrução SQL incorreta. Reescrevemos o código da seguinte forma:
using (var context = new RdvMedecinsContext())
{
// crenel n° 0
creneau = context.Creneaux.Find(idCreneau);
Console.WriteLine(creneau.ShortIdentity());
// force the loading of the associated doctor
// it's possible because we're still in an open context
Medecin medecin = creneau.Medecin;
}
- linha 4: recuperamos o slot sem uma junção;
- linha 8: recuperamos a dependência em falta.
Funciona. Mais uma vez, vemos que a mudança do SGBD tem impacto no código. Na verdade, o problema aqui não é o SGBD, mas sim o seu conector ADO.NET.
6.3. Arquitetura multicamadas baseada no EF 5
Voltamos ao nosso estudo de caso descrito no parágrafo 2.
![]() |
Começaremos por criar a camada de acesso aos dados [DAO]. Para tal, duplicamos o projeto de consola do VS 2012 [RdvMedecins-SqlServer-02] para [RdvMedecins-PostgreSQL-02] [1]:
![]() |
- Em [2], elimine o projeto [RdvMedecins-SqlServer-02];
![]() |
- Em [3], adicione um projeto existente à solução. Selecione-o da pasta [RdvMedecins-PostgreSQL-02] que acabou de ser criada;
- Em [4], o novo projeto tem o mesmo nome que o que foi eliminado. Vamos alterar o seu nome;
![]() |
- Em [5], alterámos o nome do projeto;
- Em [6], modificamos algumas das suas propriedades, como o nome do conjunto aqui;
- em [7], a pasta [Models] é eliminada e substituída pela pasta [Models] do projeto [RdvMedecins-PostgreSQL-01]. Isto deve-se ao facto de ambos os projetos partilharem os mesmos modelos.
![]() |
- em [8], as referências atuais do projeto;
- em [9], o conector PostgreSQL ADO.NET foi adicionado utilizando a ferramenta NuGet.
No ficheiro [App.config], substitua as informações da base de dados SQL Server pelas da base de dados PostgreSQL. Estas informações podem ser encontradas no ficheiro [App.config] do projeto [RdvMedecins-PostgreSQL-01]:
<!-- connection chain on base -->
<connectionStrings>
<add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
</connectionStrings>
<!-- the 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 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-PostgreSQL-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-PostgreSQL-01]). O programa de teste falha com a seguinte exceção:
Linha 13: A mensagem indica que o erro ocorreu no método [GetCreneauxMedecin] da camada [DAO]. Este método é o seguinte:
// list of time slots for a given doctor
public List<Creneau> GetCreneauxMedecin(int idMedecin)
{
// list of slots
try
{
// opening persistence context
using (var context = new RdvMedecinsContext())
{
// we get the doctor back with his slots
Medecin medecin = context.Medecins.Include("Creneaux").Single(m => m.Id == idMedecin);
// returns a list of the doctor's slots
return medecin.Creneaux.ToList<Creneau>();
}
}
catch (Exception ex)
{
throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
}
}
Linha 11: Reconhecemos a palavra-chave Include, que anteriormente causava uma falha no programa. O código anterior pode ser substituído pelo seguinte:
// list of time slots for a given doctor
public List<Creneau> GetCreneauxMedecin(int idMedecin)
{
// list of slots
try
{
// opening persistence context
using (var context = new RdvMedecinsContext())
{
// returns a list of the doctor's slots
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 consistente do que o antigo. De qualquer forma, desta vez o programa de teste é aprovado.
Criamos a DLL do projeto tal como fizemos para o projeto [RdvMedecins-SqlServer-02] e reunimos todas as DLLs do projeto numa pasta [lib] criada dentro de [RdvMedecins-PostgreSQL-02]. Estas serão as referências para o projeto web [RdvMedecins-PostgreSQL-03] que se segue.
![]() |
Estamos agora prontos para criar a camada [ASP.NET] da nossa aplicação:
![]() |
Começaremos pelo projeto [RdvMedecins-SqlServer-03]. Duplicamos a pasta deste projeto para [RdvMedecins-PostgreSQL-03] [1]:
![]() |
- em [2], utilizando o VS 2012 Express for the Web, abrimos a solução na pasta [RdvMedecins-PostgreSQL-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] no projeto [RdvMedecins-PostgreSQL-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-PostgreSQL-02]. Depois de feito isto, executamos o projeto web. Funciona. Não devemos esquecer-nos de preencher a base de dados antes de executar a aplicação web.


























