Skip to content

6. Caso de estudio con PostgreSQL 9.2.1

6.1. Instalación de las herramientas

Las herramientas que hay que instalar son las siguientes:

  • el SGBD: [http://www.enterprisedb.com/products-services-training/pgdownload#windows];
  • una herramienta de administración: EMS, SQL Manager para PostgreSQL, y el software gratuito [http://www.sqlmanager.net/fr/products/postgresql/manager/download].

En los siguientes ejemplos, el usuario postgres tiene la contraseña postgres.

Ejecutemos PostgreSQL y luego la herramienta [SQL Manager Lite for PostgreSQL], con la que administraremos el SGBD.

  • En [1], iniciamos SGBD y PostgreSQL desde los servicios de Windows;
  • en [2], se inicia el servicio;

Ahora iniciamos la herramienta [SQL Manager Lite for MySQL], con la que administraremos los servicios SGBD y [3].

  • En [4], creamos una nueva base de datos;
  • en [5], indicamos el nombre de la base de datos;
  • en [5], nos conectamos como postgres / postgres;
  • en [6], se ingresa cierta información;
  • en [7], se valida la orden SQL que se va a ejecutar;
  • en [8], se ha creado la base de datos. Ahora debe registrarse en [EMS Manager]. La información es correcta. Se ejecuta [OK];
  • en [9], nos conectamos a ella;
  • en [10], [EMS Manager] muestra la base de datos, que por el momento está vacía. Cabe señalar que las tablas pertenecerán a un esquema llamado «public [11]».

Ahora conectaremos un proyecto VS 2012 a esta base de datos.

6.2. Creación de la base de datos a partir de las entidades

Comenzamos duplicando la carpeta del proyecto [RdvMedecins-SqlServer-01] en [RdvMedecins-PostgreSQL-01] y [1]:

  • en [2]; en VS 2012, eliminamos el proyecto [RdvMedecins-SqlServer-01] de la solución;
  • en [3], se ha eliminado el proyecto;
  • en [4], se agrega otro. Este se encuentra en la carpeta [RdvMedecins-PostgreSQL-01] que creamos anteriormente;
  • en [5], el proyecto cargado se llama [RdvMedecins-SqlServer-01];
  • en [6], cambiamos su nombre a [RdvMedecins-PostgreSQL-01];
  • en [7], se agrega otro proyecto a la solución. Este se toma de la carpeta [RdvMedecins-SqlServer-01] del proyecto que hemos eliminado de la solución anteriormente;
  • en [8], el proyecto [RdvMedecins-SqlServer-01] se ha reintegrado a la solución.

El proyecto [RdvMedecins-PostgreSQL-01] es idéntico al proyecto [RdvMedecins-SqlServer-01]. Necesitamos hacer algunas modificaciones. En [App.config], vamos a modificar la cadena de conexión y el [DbProviderFactory], que debemos adaptar a cada SGBD.


<!-- cadena de conexión a la base de datos -->
  <connectionStrings>
    <add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
  </connectionStrings>
  <!-- el proveedor 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>
  • línea 3: el usuario y su contraseña;
  • líneas 7-9: el DbProviderFactory. La línea 8 hace referencia a un DLL y un [Npgsql] que no tenemos. Se obtiene con NuGet [1]:
  • en [2], en el campo de búsqueda se escribe la palabra clave postgresql;
  • en [3], se selecciona el paquete [Npgsql]. Se trata de un conector ADO.NET para PostgreSQL;
  • en [4], se han agregado dos referencias;
  • en [5], en [App.config], hay que poner la versión correcta de DLL. Se encuentra en sus propiedades.

En el archivo [Entites.cs], hay que adaptar el esquema de las tablas que se generarán:


  [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
  {...}

Anteriormente, al crear una base de datos PostgreSQL, vimos que las tablas pertenecían a un esquema llamado «public».

Configuramos la ejecución del proyecto:

  • en [1], le damos otro nombre al ensamblado que se generará;
  • en [2], así como otro espacio de nombres por defecto;
  • en [3], se designa el programa que se ejecutará.

En esta etapa, no hay errores de compilación. Ejecutemos el programa [CreateDB_01]. Se produce la siguiente excepción:

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

Recordamos haber tenido el mismo error con MySQL y Oracle. Esto se debe al tipo del campo Timestamp de las entidades. Realizamos la misma modificación que con Oracle. En las entidades, reemplazamos las tres líneas


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

por las siguientes:


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

Cambiamos el tipo de la columna de byte[] a int?. En el SGBD, utilizaremos procedimientos almacenados para incrementar este entero en una unidad cada vez que se inserte o modifique una fila.

Realizamos la modificación anterior para las cuatro entidades y luego volvemos a ejecutar la aplicación. Entonces obtenemos el siguiente error:

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

La línea 1 indica que el conector ADO.NET de PostgreSQL no puede eliminar la base de datos existente. Exactamente igual que con Oracle. Por lo tanto, debemos crear la base de datos [RDVMEDECINS-EF] manualmente con la herramienta [EMS Manager for PostgreSQL]. No describiremos todos los pasos, sino solo los más importantes.

La base de datos PostgreSQL tendrá la siguiente estructura:

Las tablas

  • en [1], ID es la clave primaria de tipo serial. Este tipo PostgreSQL es un entero generado automáticamente por el SGBD.

Las diferentes tablas tienen las claves primarias y externas que tenían esas mismas tablas en los ejemplos anteriores. Las claves externas tienen el atributo ON, DELETE y CASCADE.

Las secuencias

Al igual que en Oracle, aquí hemos creado secuencias. Son generadores de números consecutivos. Hay 5: [1].

  • En [2], vemos las propiedades de la secuencia [CLIENTS_ID_SEQ]. Genera números consecutivos de 1 en 1, desde 1 hasta un valor muy grande.

Todas las secuencias siguen el mismo modelo.

  • [CLIENTS_ID_seq] se utilizará para generar la clave primaria de la tabla [CLIENTS];
  • [MEDECINS_ID_seq] se utilizará para generar la clave primaria de la tabla [MEDECINS];
  • [CRENEAUX_ID_seq] se utilizará para generar la clave primaria de la tabla [CRENEAUX];
  • [RVS_ID_seq] se utilizará para generar la clave primaria de la tabla [RVS];
  • [sequence_versions] se utilizará para generar los valores de las columnas [VERSIONING] de todas las tablas.

Los disparadores

Un disparador es un procedimiento que ejecuta el SGBD antes o después de un evento (inserción, modificación, eliminación) en una tabla. Tenemos 4: [1]:

Veamos el código DDL del disparador [CLIENTS_tr] que alimenta la columna [VERSIONING] de la tabla [CLIENTS]:

1
2
3
4
CREATE TRIGGER "CLIENTS_tr"
  BEFORE INSERT OR UPDATE 
  ON public."CLIENTS" FOR EACH ROW 
EXECUTE PROCEDURE public.trigger_versions();
  • líneas 1-3: antes de cada operación INSERT o UPDATE en la tabla [CLIENTS];
  • línea 4: se ejecuta el procedimiento [public.trigger_versions()].

El procedimiento [public.trigger_versions()] es el siguiente:

1
2
3
4
BEGIN
NEW."VERSIONING":=nextval('sequence_versions');
return NEW;
END
  • línea 2: NEW representa la línea que se va a insertar o modificar. NEW. «VERSIONING» es la columna [VERSIONING] de esta línea. Se le asigna el siguiente valor del generador de números: «sequence_versions». De este modo, la columna ["VERSIONING"] cambia cada vez que se realiza una operación INSERT / UPDATE en la tabla [CLIENTS].

Los disparadores [MEDECINS_tr, CRENEAUX_tr, RVS_tr] funcionan de manera similar. Las cuatro columnas ["VERSIONING"] obtienen sus valores de la misma secuencia.

El script para generar las tablas de la base de datos PostgreSQL y [RDVMEDECINS-EF] se ha colocado en la carpeta [RdvMedecins / databases / postgreSQL]. El lector podrá cargarlo y ejecutarlo para crear sus tablas.

Una vez hecho esto, se pueden ejecutar los distintos programas del proyecto. Estos arrojan los mismos resultados que con el servidor SQL, excepto el programa [ModifyDetachedEntities], que falla por la misma razón por la que fallaba con Oracle. El problema se resuelve de la misma manera. Basta con copiar el programa [ModifyDetachedEntities] del proyecto [RdvMedecins-Oracle-01] al proyecto [RdvMedecins-PostgreSQL-01].

El programa [LazyEagerLoading] falla con la siguiente excepción:

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:línea 23

El código erróneo es el siguiente:


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

En la línea n.º 1 de la excepción, el error reportado sugiere que se trata de una unión, ya que LEFT es una palabra clave de la unión. Dado que la línea 4 del código anterior solicita la carga inmediata de la dependencia [Medecin] de una entidad [Creneau], EF realizó una unión entre las tablas [CRENEAUX] y [MEDECINS]. Pero parece que el conector ADO.NET generó un comando SQL incorrecto. Reescribimos el código de la siguiente manera:


      using (var context = new RdvMedecinsContext())
      {
        // franja n.º 0
        creneau = context.Creneaux.Find(idCreneau);
        Console.WriteLine(creneau.ShortIdentity());
        // se fuerza la carga del médico asociado
        // esto es posible porque aún estamos en un contexto abierto
        Medecin medecin = creneau.Medecin;
}
  • línea 4: buscamos el intervalo sin unión;
  • línea 8: recuperamos la dependencia que faltaba.

Funciona. Una vez más, observamos que el cambio en SGBD tiene un impacto en el código. De hecho, no es el SGBD el que está en cuestión aquí, sino su conector ADO.NET.

6.3. Arquitectura multicapa basada en EF 5

Volvemos a nuestro caso de estudio descrito en el párrafo 2.

Comenzaremos por construir la capa de acceso a datos [DAO]. Para ello, duplicamos el proyecto de consola VS 2012 [RdvMedecins-SqlServer-02] en [RdvMedecins-PostgreSQL-02] [1]:

  • en [2], eliminamos el proyecto [RdvMedecins-SqlServer-02];
  • en [3], se agrega un proyecto existente a la solución. Se toma de la carpeta [RdvMedecins-PostgreSQL-02] que acaba de crearse;
  • en [4], el nuevo proyecto lleva el nombre del que se eliminó. Vamos a cambiarle el nombre;
  • en [5], hemos cambiado el nombre del proyecto;
  • en [6], modificamos algunas de sus propiedades, como en este caso el nombre del ensamblado;
  • en [7], se elimina la carpeta [Models] para sustituirla por la carpeta [Models] del proyecto [RdvMedecins-PostgreSQL-01]. De hecho, ambos proyectos comparten las mismas plantillas.
  • en [8], las referencias actuales del proyecto;
  • en [9], se ha agregado el conector ADO.NET de PostgreSQL con la herramienta NuGet.

En el archivo [App.config], se reemplaza la información de la base SQL Server por la de la base PostgreSQL. Esta información se encuentra en el archivo [App.config] del proyecto [RdvMedecins-PostgreSQL-01]:


  <!-- cadena de conexión en la base -->
  <connectionStrings>
    <add name="monContexte" connectionString="Server=127.0.0.1;Port=5432;Database=rdvmedecins-ef;User Id=postgres;Password=postgres;" providerName="Npgsql" />
  </connectionStrings>
  <!-- el proveedor 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>

Los objetos administrados por Spring también cambian. Actualmente tenemos:


  <!-- Configuración de 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>

La línea 7 hace referencia al ensamblado del proyecto [RdvMedecins-SqlServer-02]. El ensamblado ahora es [RdvMedecins-PostgreSQL-02].

Una vez hecho esto, estamos listos para ejecutar la prueba de la capa [DAO]. Antes hay que asegurarse de llenar la base de datos (programa [Fill] del proyecto [RdvMedecins-PostgreSQL-01]). El programa de prueba falla con la siguiente excepción:

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.]

En la línea 13, el mensaje indica que el error se produjo en el método [GetCreneauxMedecin] de la capa [DAO]. Este es el siguiente:


    // lista de horarios disponibles de un médico específico
    public List<Creneau> GetCreneauxMedecin(int idMedecin)
    {
      // lista de horarios
      try
      {
        // apertura del contexto de persistencia
        using (var context = new RdvMedecinsContext())
        {
          // se recupera el médico con sus horarios
          Medecin medecin = context.Medecins.Include("Creneaux").Single(m => m.Id == idMedecin);
          // se devuelve la lista de horarios del médico
          return medecin.Creneaux.ToList<Creneau>();
        }
      }
      catch (Exception ex)
      {
        throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
      }
}

En la línea 11, se reconoce la palabra clave Include, que ya había provocado el fallo de un programa anterior. El código anterior puede sustituirse por el siguiente:


    // lista de horarios de un médico específico
    public List<Creneau> GetCreneauxMedecin(int idMedecin)
    {
      // lista de horarios
      try
      {
        // se abre el contexto de persistencia
        using (var context = new RdvMedecinsContext())
        {
          // devuelve la lista de horarios del médico
          return context.Creneaux.Where(c => c.MedecinId == idMedecin).ToList<Creneau>(); 
        }
      }
      catch (Exception ex)
      {
        throw new RdvMedecinsException(3, "GetCreneauxMedecin", ex);
      }
}

El nuevo código incluso parece más coherente que el anterior. De todos modos, esta vez el programa de prueba funciona.

Creamos el archivo DLL del proyecto tal como se hizo para el proyecto [RdvMedecins-SqlServer-02] y reunimos todos lostodos los archivos DLL del proyecto en una carpeta [lib] creada dentro de [RdvMedecins-PostgreSQL-02]. Estas serán las referencias del proyecto web [RdvMedecins-PostgreSQL-03] que vendrá a continuación.

  

Ahora estamos listos para construir la capa [ASP.NET] de nuestra aplicación:

Partiremos del proyecto [RdvMedecins-SqlServer-03]. Duplicamos la carpeta de este proyecto en [RdvMedecins-PostgreSQL-03] y [1]:

  • en [2], con VS 2012 Express para la web, abrimos la solución de la carpeta [RdvMedecins-PostgreSQL-03];
  • en [3], cambiamos tanto el nombre de la solución como el del proyecto;
  • en [4], las referencias actuales del proyecto;
  • en [5], las eliminamos;
  • a [6], para reemplazarlas por referencias a DLL, que acabamos de guardar en una carpeta [lib] del proyecto [RdvMedecins-PostgreSQL-02].

Ahora solo nos queda modificar el archivo [Web.config]. Reemplazamos su contenido actual por el contenido del archivo [App.config] del proyecto [RdvMedecins-PostgreSQL-02]. Una vez hecho esto, ejecutamos el proyecto web. Funciona. No olvidemos llenar la base de datos antes de ejecutar la aplicación web.