Traducción aproximada del artículo Entity Framework 4 “Code-First”: Custom Database Schema Mapping publicado en inglés por Scott Guthrie el 23 de julio del 2010.

 

La semana pasada escribí sobre la nueva opción para EF llamada código primero. Este nuevo componente permite un eficiente flujo de desarrollo centrado en código. Nos permite:

  • Trabajar con datos sin nunca tener que abrir un diseñador o lidiar con correspondencias en XML
  • Definir objetos del modelo usando simples clases tradicionales sin tener que derivar de clases base específicas
  • Usar la modalidad de convención en vez de configuración para el almacenaje de datos, evitándonos tener que configurarlo

El el artículo original introduje el concepto de código primero de EF y demostré como usar las reglas que incluye para la creación de bases de datos. Estas convenciones predeterminadas funcionan muy bien con aplicaciones nuevas y nos liberan de toda configuración a la hora de establecer relaciones ente las clases y la base de datos.

En este artículo voy a discutir cómo se pueden redefinir esas reglas para la creación de correspondencias de manera que podamos usar cualquier esquema de datos que deseemos. Un caso en el que esto es particularmente útil es cuando ya existe una base de datos (puesto que ya tiene un esquema de datos definido y es poco probable que pueda ser alterado), o cuando necesitamos adaptar el modelo a un sistema de almacenamiento diferente a las bases de datos relacionales.

 

Breve repaso del ejemplo NerdDinner

En el artículo de la semana pasada expliqué paso a paso la creación de una aplicación llamada NerdDinner y cómo la biblioteca de código primero de EF nos hace más productivos al trabajar con datos.

 

Aplicación a desarrollar en este ejemplo.

 

Las dos clases siguientes fueron creadas para representar los datos dentro de la aplicación. Son objetos simples y tradicionales del CLR (conocidos como POCO) que exponen tipos estándar de .NET:

 

public class Dinner
{
    public int DinnerID { get; set; }
    public string Title { get; set; }
    public DateTime EventDate { get; set; }
    public string Address { get; set; }
    public string HostedBy { get; set; }

    public virtual ICollection<RSVP> RSVPs { get; set; }
}

 

public class RSVP
{
    public int RsvpID { get; set; }
    public int DinnerID { get; set; }
    public string AttendeeEmail { get; set; }

    public virtual Dinner Dinner { get; set; }
}

 

Luego creamos una clase llamada NerdDinners que sirve como mediadora ente el modelo y la base de datos. Es derivada de la clase DbContext provista por la biblioteca de código primero de EF y expone dos propiedades:

 

public class NerdDinners : DbContext
{
    public DbSet<Dinner> Dinners { get; set; }
    public DbSet<RSVP> RSVPs { get; set; }
}

 

Para el almacenaje de datos, dejamos que el código primero de EF usara sus convecciones predeterminadas. En consecuencia, las propiedades Dinners y RSVPs corresponden a tablas con los mismos nombres en la base de datos. A su vez, cada propiedad en las clases Dinner y RSVP tiene una columna respectiva en las tablas Dinners y RSVPs.

Este es el esquema de datos de la tabla Dinners:

 

Definición de la tabla Dinners.

 

Y este el de la tabla RSVPs:

 

Definición de la tabla RSVPs.

 

El esquema de datos fue creado por el componente de código primero de EF4 sin requerir ningún tipo de configuración de nuestra parte. Bastó con escribir las clases anteriores.

 

Usando correspondencias personalizadas de datos en EF4

Código primero en EF4 nos da la opción de redefinir las reglas para la creación del esquema de datos y así alterar las correspondencias entre las clases y la base de datos.

Hay varias formas de hacerlo; unas de las más fáciles es redefinir el método OnModelCreating de la clase base DbContext:

 

public class NerdDinners : DbContext
{
    public DbSet<Dinner> Dinners { get; set; }
    public DbSet<RSVP> RSVPs { get; set; }

    protected override void OnModelCreating(DbModelBuilder
                                            modelBuilder)
    {
        // Insertar acá las reglas personalizadas...
    }
}

 

El método OnModelCreating es llamado la primera vez que la clase NerdDinners es usada en la aplicación, y recibe como argumento un objeto de tipo ModelBuilder. Este objeto puede ser usado para alterar las reglas de correspondencias para el almacenaje de nuestros objetos del modelo en la base de datos.

El método es llamado sólo una vez por EF y los resultados son guardados para usos subsiguientes. Así se evita la reducción en rendimiento que resultaría de crear el modelo cada vez que un objeto de clase NerdDinners es creado; también implica que no es necesario que nosotros implementemos ese almacenamiento temporal para mejorar el rendimiento de nuestra aplicación.

 

Caso 1: Cambiando el nombre de la tabla

Veamos un par de formas en que podemos usar el método OnModelCreating para cambar el almacenamiento de nuestro modelo en la base de datos. Empecemos por un caso común en el que queremos que asociar el modelo a una base de datos cuyas tablas tienen nombre diferentes a los de nuestras clases.

Por ejemplo, supongamos que la base de datos usa una convención en la que las tablas usan el prefijo tbl. En ese caso nuestra tabla se llamaría tblDinners en vez de Dinners:

 

Ejemplode una tabla con nombre diferente a la clase que representa.

 

La intención es asociar nuestra simple clase Dinners en el modelo a esta tabla llamada tblDinners y sin tener que decorar la clase con atributos para su persistencia:

 

public class Dinner
{
    public int DinnerID { get; set; }
    public string Title { get; set; }
    public DateTime EventDate { get; set; }
    public string Address { get; set; }
    public string HostedBy { get; set; }

    public virtual ICollection<RSVP> RSVPs { get; set; }
}

 

Para obtener esta asociación, redefinimos el método OnModelCreating en la clase de contexto NerdDinners y especificamos allí la regla personalizada:

 

public class NerdDinners : DbContext
{
  public DbSet<Dinner> Dinners { get; set; }
  public DbSet<RSVP> RSVPs { get; set; }

  protected override void OnModelCreating(DbModelBuilder
                                          modelBuilder)
  {
    // Asociar la clase Dinners con la tabla tblDinners.
    modelBuilder.Entity<Dinner>().Map(d =>
                                      d.ToTable("tblDinners"));
  }
}

 

El código usa un tipo de interfase de programación fluida en la que varios métodos son encadenados creando código más legible. En este caso, usamos el objeto ModelBuilder para indicar que queremos asociar la clase Dinner a una tabla llamada tblDinners.

 

El código anterior es un poco diferente del usado en el artículo original por Scott. En el tiempo que ha transcurrido desde su publicación original, el objeto ModelBuilder ha sido modificado, requiriendo la sintaxis vista arriba.

 

Ese es todo el código que hay que escribir. Nuestra aplicación ahora usará la tabla tblDinners en vez de una llamada Dinners cada vez que necesite obtener o guardar objetos de tipo Dinner. No tuvimos que actualizar las clases Dinner o RSVP en el modelo para lograrlo. Así continúan siendo objetos POCO sin ningún conocimiento sobre su almacenaje.

 

Probando el cambio recién hecho

Si desean ver el almacenamiento personalizado en acción y descargaron la aplicación de ejemplo NerdDinner de mi artículo anterior, pueden modificarla para que incluya el código del método OnModelCreating() y ejecutarla de nuevo.

En ese artículo activamos la funcionalidad de creación y regeneración automática de la base de datos provista por la biblioteca de código primero de EF. Eso quiere decir que, al ejecutar la aplicación justo después de hacer los cambios citados, notarán que la base de datos de SQL CE es actualizada de manera que ahora tendrá una tabla llamada tblDinners en vez de sólo Dinners. EF detectó los cambios en la estructura del modelo y creó la base de datos de nuevo para estar al día con la nueva estructura. También siguió las instrucciones en OnModelCreating(), por lo que la tabla tiene ahora el nuevo nombre.

 

Varios me preguntaron al final de mi artículo anterior si hay forma de evitar que EF regenere la base de datos por nosotros. Aparentemente no fui suficientemente claro en explicar que esta es una funcionalidad opcional. También es posible crear la base de datos de otras formas (usando código, secuencias de instalación .sql, una herramienta de administración de SQL, y así por el estilo) usando la cadena de conexión para apuntar al resultado. En ese caso EF nunca modifica el esquema de datos.

La razón por la que demostré esta funcionalidad en mi artículo anterior es porque me parece muy útil para las etapas tempranas del desarrollo de un nuevo proyecto. No obstante, no es requerida en lo absoluto y habrá muchos que opten por no usarla nunca.

 

Significativamente, no tuvimos que tocar el código en los controladores o vistas de la aplicación ASP.NET MVC. No se vieron afectados por el cambio en la base de datos debido a que la clase Dinner se mantuvo igual.

 

Caso 2: cambiando correspondencias entre columnas y propiedades

Examinemos ahora otro caso, uno en el que queremos usar un esquema de datos que usa diferentes nombres para tablas y columnas que los que usamos en nuestras clases y propiedades del modelo.

Por ejemplo, supongamos que en la tabla tblDinners los nombres de columnas usan el prefijo col, y son diferentes a los nombres de las propiedades en la clase Dinner:

 

Tabla dinners cuyas columnas tienen nombres diferentes a la clase Dinner.

 

Todavía queremos poder asociar nuestra clase Dinner a la tabla tblDinners manteniéndola limpia de atributos para sus persistencia:

 

public class Dinner
{
    public int DinnerID { get; set; }
    public string Title { get; set; }
    public DateTime EventDate { get; set; }
    public string Address { get; set; }
    public string HostedBy { get; set; }

    public virtual ICollection<RSVP> RSVPs { get; set; }
}

 

Una vez más podemos establecer las correspondencias en el método OnModelCreating usando reglas un poco más detalladas:

 

public class NerdDinners : DbContext
{
  public DbSet<Dinner> Dinners { get; set; }
  public DbSet<RSVP> RSVPs { get; set; }

  protected override void OnModelCreating(DbModelBuilder
                                          modelBuilder)
  {
    // Asociar la clase Dinners con la tabla tblDinners.
    modelBuilder.Entity<Dinner>().Map(d =>
                                      d.ToTable("tblDinners"));

    modelBuilder.Entity<Dinner>().Property(d => d.DinnerID)
                                 .HasColumnName("colId");

    modelBuilder.Entity<Dinner>().Property(d => d.Title)
                                 .HasColumnName("colTitle");

    modelBuilder.Entity<Dinner>().Property(d => d.EventDate)
                                 .HasColumnName("colDate");

    modelBuilder.Entity<Dinner>().Property(d => d.Address)
                                 .HasColumnName("colAddr");

    modelBuilder.Entity<Dinner>().Property(d => d.HostedBy)
                                 .HasColumnName("colHost");
    }
}

 

EL texto original se refiere de nuevo al método MapSingleType que fue eliminado en versiones posteriores. El código mostrado es válido para el RC1 de EF 4.1.

Lo mismo aplica para el siguiente ejemplo.

 

La diferencia con el caso anterior es que esta vez también especificamos reglas para asignar nombres de columnas a las propiedades de la clase Dinner.

Puesto que el parámetro d de las expresiones lambda es un tipo reconocido por el compilador, el IntelliSense en Visual Studio es funcional. Igual con las tareas de reorganización de código, por lo que si necesitamos cambiarle el nombre a una de las propiedades, podemos dejar Visual Studio automáticamente actualice el código en las reglas de correspondencias.

 

Caso 3: partiendo una tabla en varias clases

Las tablas en una base de datos relacional suelen tener una estructura diferente a las clases de un modelo orientado a objetos. Información guardada en una tabla grande en la base de datos puede a veces ser mejor representada como clases múltiples desde una perspectiva puramente de objetos. Es común necesitar una forma de partir tablas en varias clases relacionadas con una misma entidad.

Por ejemplo, supongamos que en vez de una columna para Address, tenemos varias que, juntas, componen la dirección:

 

Una table puede a veces ser dividida en clases diferentes.

 

En vez de importar las columnas como cuatro propiedades diferentes en la clase Dinner, podríamos querer encapsularlas en una clase Address que es expuesta por Dinner:

 

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
    public string PostCode { get; set; }
    public string Country { get; set; }
}

 

 

public class Dinner
{
    public int DinnerID { get; set; }
    public string Title { get; set; }
    public DateTime EventDate { get; set; }
    public Address Address { get; set; }
    public string HostedBy { get; set; }

    public virtual ICollection<RSVP> RSVPs { get; set; }
}

 

Noten cómo simplemente declaramos una clase Address con cuatro propiedades públicas y luego la exponemos como una propiedad pública en Dinner. Nuestras clases siguen siendo POCO y sin ningún conocimiento sobre su almacenamiento.

Ahora podemos modificar el método OnModelCreating para que sepa relacionar esta jerarquía de clases a una única tabla en la base de datos:

 

public class NerdDinners : DbContext
{
  public DbSet<Dinner> Dinners { get; set; }
  public DbSet<RSVP> RSVPs { get; set; }

  protected override void OnModelCreating(DbModelBuilder
                                          modelBuilder)
  {
    modelBuilder.ComplexType<Address>();

    // Asociar la clase Dinners con la tabla tblDinners.
    modelBuilder.Entity<Dinner>().Map(d =>
                                      d.ToTable("tblDinners"));

    modelBuilder.Entity<Dinner>().Property(d => d.DinnerID)
                                 .HasColumnName("colId");

    modelBuilder.Entity<Dinner>().Property(d => d.Title)
                                 .HasColumnName("colTitle");

    modelBuilder.Entity<Dinner>().Property(d => d.EventDate)
                                 .HasColumnName("colDate");

    modelBuilder.Entity<Dinner>()
                         .Property(d => d.Address.Street)
                         .HasColumnName("colAddress_Street");

    modelBuilder.Entity<Dinner>()
                         .Property(d => d.Address.City)
                         .HasColumnName("colAddressCity");

    modelBuilder.Entity<Dinner>()
                         .Property(d => d.Address.PostCode)
                         .HasColumnName("colAddress_Zip");

    modelBuilder.Entity<Dinner>()
                         .Property(d => d.Address.Country)
                         .HasColumnName("colAddress_Country");

    modelBuilder.Entity<Dinner>().Property(d => d.HostedBy)
                                 .HasColumnName("colHost");
  }
}

 

Noten cómo hemos usado la misma estrategia para crear correspondencias del caso anterior, asignando nombres de columnas a las propiedades de las clases. En este caso hemos simplemente extendido la idea para que abarque también propiedades compuestas de un tipo complejo. Lo único nuevo es la llamada al método modelBuilder.ComplexType<Address>() para registrar la clase como un tipo usado en nuestras reglas de correspondencia.

 

En EF 4.1 RC1 no es necesario registrar la clase ya que EF lo hace automáticamente al examinar la clase Dinner, por lo que la llamada a ComplexType<>() puede ser eliminada.

 

Y eso es todo lo que se ocupa para partir una tabla en varios objetos.

 

Descarga de un ejemplo actualizado con reglas de correspondencia personalizadas

Pueden obtener aquí una versión actualizada de NerdDinners. Para ejecutarla necesitan tener Visual Studio 2010 (o la versión gratuita de Visual Web Developer 2010 Express).

También tienen que ya haber instalado SQL CE 4. Pueden obtener acá la biblioteca de código primero de EF. Ninguna de estas descargas tendrá impacto negativo en sus máquinas.

 

Conclusión

El CTP 4 de la biblioteca de código primero de EF provee una atractiva alternativa para modelar los datos partiendo desde el código. Trae consigo grandes ventajas en términos de productividad y flexibilidad. Espero que estos dos artículos provean algunas ideas de lo que posible hacer con ella.

Pueden obtener acá el CTP 4 de la biblioteca de código primero de EF. (O el RC1 aquí). Para obtener aún más información sobre código primero en EF asegúrense de consultar estos artículos por el equipo de ADO.NET:

 

 

Espero haberles sido de ayuda,

 

Scott

 

2 Respuestas a “Correspondencias personalizadas con código primero en Entity Framework 4”

  1. [...] Correspondencias personalizadas con código primero en Entity Framework 4 [...]

  2. Cristian dice:

    Muy buenos tus artículos sobre codefirst recién estoy empezando a usar este modelo. Y tengo una duda como hacer que el modelo no me vuelva a regenerar la base de datos osea si la ya la creo la primera vez que ejecute el código y le agregue registros como hacer para cuando vuelva a compilar el código no me vuelva a generar la base de datos y no me borre los datos uqe ya tiene

Responder



Licencia de uso

El contenido de las traducciones está sujeto a los términos de protección de derechos de uso de los autores originales quienes han autorizado su publicación en este blog. Asegúrese de entender los terminos de la licencia de cada autor antes de usar tal contenido.

Mis propios artículos son publicados bajo los términos de la Licencia Reconocimiento-Compartir bajo la misma licencia 3.0 Estados Unidos de Creative Commons:

Creative Commons License
Blog de Maromas Digitales by Maromas Digitales, LLC is licensed under a Creative Commons Reconocimiento-Compartir bajo la misma licencia 3.0 Estados Unidos License.

License

The contents of all translated articles is subject to the copyright and licensing terms of the original authors and has been published here with their express permission. Verify the original author's licensing terms before using the contents of these articles.

My own articles are licensed under the Creative Commons Attribution-Share Alike 3.0 United States License:

Creative Commons License
Blog de Maromas Digitales by Maromas Digitales, LLC is licensed under a Creative Commons Attribution-Share Alike 3.0 United States License.