martes, 17 de abril de 2012

Entity Framework Code First Fluent API (I)

Code First es un nuevo enfoque de trabajo que aparece en el Entity Framework a partir de las versión 4.1, que nos permite crear nuestro modelo mediante clases POCO (Plain Old CLR Object), a partir de las cuales se generará nuestro modelo de base de datos. Otra característica es que en este modelo no existe el fichero edmx de definición del modelo (conceptual y de datos).

La principal ventaja de usar clases POCO es que el modelo de clases es más limpio ya que las clases no tiene ninguna dependencia con el Entity Framework (clase EntityObject). Por defecto se utiliza el nombre de nuestras clases y correspondientes propiedades para generar los nombre de tablas y campos. Esto puede ser más que suficiente para muchos proyectos, sobre todo si están empezando de cero, pero, ¿que ocurre si   queremos hacer modificaciones en nuestro modelo de datos? Para eso tenemos dos alternativas
  • Data Annotations
  • Fluent Api
Como ya he comentado al principio, para poder trabajar con CodeFirst es necesario usar la versión 4.1 (o superior) del Entity Framework. Para descargar e instalar esta versión la mejor alternativa es utilizar NuGet Gallery. Para eso en nuestro Visual Studio vamos a Herramientas > Library Package Manager > Package Manager Console. Con esto se nos abrirá una consola


donde escribiremos
Install-Package EntityFramework -Version 4.3.1
Si todo ha ido bien veremos algo parecido a esto


Ya tenemos la última versión del Entity Framework instalada en nuestro proyecto, así que, ¿ahora qué? Para demostrar el uso de Fluent Api para definir nuestro modelo de datos, voy a usar la estructura que podría tener un blog (muy simplificado). Para estos tendremos
  • Un blog con su identificador y título.
  • Una lista de post asociada a nuestro blog. Cada post tendrá su identificador, titulo, un título en html y un texto.
  • Cada post pertenecerá a una categoría y podrá tener una lista de etiquetas (tags). Tanto las categorías como las etiquetas, tendrán tanto un identificador como un nombre.
Con este esquema tan simple crearemos nuestras clases de la siguiente manera.
public class Blog
{
    public int IdBlog { get; set; }
    public string Title { get; set; }

    // Navigation properties
    public virtual ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int IdPost { get; set; }
    public string Title { get; set; }
    public string HtmlTitle { get; set; }
    public string Body { get; set; }

    // Foreign key
    public int IdBlog { get; set; }

    // Navigation properties
    public virtual Blog Blog { get; set; }
    public virtual Category Category { get; set; }
    public virtual ICollection<Tag> Tags { get; set; }
}

public class Category
{
    public int IdCategory { get; set; }
    public string Name { get; set; }

    // Navigation properties
    public virtual ICollection<Post< Posts { get; set; }
}

public class Tag
{
    public int IdTag { get; set; }
    public string Name { get; set; }

    // Navigation properties
    public virtual ICollection<Post> Posts { get; set; }
}


Una vez hecho esto empezaremos a definir nuestro contexto de la siguiente manera
public class DataContext : DbContext { }
Ahora definiremos un constructor donde especificaremos el nombre de la cadena de conexión y el conjunto de entidades que serán mapeadas (en nuestro caso todas)
public class DataContext : DbContext
{
    public DataContext() : base("DataContext") { }

    public DbSet<Blog> Blog { get; set; }
    public DbSet<Post> Post { get; set; }
    public DbSet<Category> Category { get; set; }
    public DbSet<Tag> Tag { get; set; }
}
Lo siguiente que debemos hacer es definir nuestro modelo de datos, por ejemplo, definiendo cuales son las claves de nuestras tablas. Para estas definiciones usaremos la sintaxis que nos brinda Fluent Api, así que, sobreescribiremos el método OnModelCreating de nuestro DataContext y escribiremos lo siguiente para definir nuestras claves primarias
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Primary Keys
    modelBuilder.Entity<Blog>().HasKey(c => c.IdBlog);
    modelBuilder.Entity<Post>().HasKey(c => c.IdPost);
    modelBuilder.Entity<Category>().HasKey(c => c.IdCategory);
    modelBuilder.Entity<Tag>().HasKey(c => c.IdTag);           
}
Con este código "casi" ya podríamos crear nuestro primer modelo de datos, y ver como funciona Code First con Fluent Api. Tan solo deberíamos añadir este código a nuestro programa (para este ejemplo yo lo estoy poniendo en el constructor de un formulario)
public Main()
{
    InitializeComponent();

    Database.SetInitializer<DataContext>(new DropCreateDatabaseIfModelChanges<DataContext>());

    using (DataContext context = new DataContext()) {
        Blog b = context.Blog.FirstOrDefault();
    }
}
Bien este código tiene dos partes importantes
  • En la primera parte indicaremos cual es la estrategia de incialización de nuestro modelo. Entity Framework Code First incluye 3 inicializadores (ojo con estos inicializadores cuando pasemos nuestra aplicación a producción porque podemos perder todos nuestros datos): 
    1. CreateDatabaseIfNotExist
    2. DropCreateDatabaseAlways
    3. DropCreateDatabaseIfModelChanges
  • En la segunda parte hacemos una simple consulta (obtenemos el primer blog) a nuestro contexto porque si no realizamso una consulta no se ejecutaría nuestro inicializador (si nunca accedemos a nuestros datos, para que se va a perder el tiempo en crear el modelo de datos).
Si todo va bien veremos que el siguiente modelo de datos se ha creado

Vemos que con el modelo conceptual (clases POCO) y la definición de las claves primarias Entity Framework ha creado un esquena de base de datos que se ajusta perfectamente a nuestra definición. Inlcluso a creado una tabla intermedia (TagPost) para poder gestionar la relación muchos a muchos que hay entre los Posts y los Tags.

A partir de aquí podemos modificar nuestro modelo de datos a través de Fluent Api añadiendo estas lineas al método OnModeloCreating. Por ejemplo:

Evitar que la clave primaria sea un identity en la tabla Categoria
modelBuilder.Entity<Category>()
  .Property(p => p.IdCategory) 
  .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
Forzar a que un campo sea obligatorio (NOT NULL) y tenga una longitud máxima de 50 caracteres
modelBuilder.Entity<Blog>().Property(p => p.Title).IsRequired();
modelBuilder.Entity<Blog>().Property(p => p.Title).HasMaxLength(50);
Evitar que una propiedad de nuestro modelo conceptual esté en en la base de datos
modelBuilder.Entity<Post>().Ignore(p => p.HtmlTitle);
Si volvemos a ejecutar nuestra aplicación y dado que nuestra estrategia de inicialización era borrar la base de datos si el modelo ha cambiado, y éste ha cambiado, veremos que que nuestros cambios se ha realizado de la forma esperada.
  • El identificador de la tabla Category no es un identity
  • Se han modificado la características del campo Title de la tabla Blog

  • El campo HtmlTitle no se mapea en la tabla Post

Como vemos, tenemos total libertad para ir refinando nuestro modelo de datos a partir de nuestro modelo conceptual.

Happy coding !

2 comentarios:

  1. Justo lo que necesitaba. Una guia tan buena como esta de este Framework ORM. Muchas gracias ;)

    ResponderEliminar