martes, 18 de marzo de 2014

[EntityFramework] Usando migraciones para actualizar cambios en nuestro modelo en Code First

En esta serie de artículos sobre Entity Framework Code First hemos visto diversos temas, desde creación y configuración inicial, hasta trabajo con las entidades. Y ahora que vamos en este punto surge la necesidad y la interrogante, de cómo podemos actualizar nuestra base de datos cuando surgen cambios en nuestras entidades. Pues bueno en este post vamos a ver cómo lo podemos hacer.

En primera instancia quiero mencionar que hay varios tipos de inicialización de base de datos en Entity Framework Code First, los cuales explico a continuación:

CreateDatabaseIfNotExists: Es el tipo de inicialización establecido por defecto, y solo crea la base de datos si esta no existe, es decir la primer vez que ejecutemos la inicialización a no ser que eliminemos la base de datos y volvamos a ejecutar, hay que tener en cuenta que si trabajamos con esta inicialización y cambiamos nuestro modelo obtendremos un error.

DropCreateDatabaseIfModelChanges:  Este tipo de inicialización elimina la base de datos existente y la crea de nuevo con las actualizaciones, siempre y cuando existan cambios en nuestro modelo de clases, la desventaja con esto es que tendremos perdida de datos.

DropCreateDatabaseAlways: Este tipo de inicialización elimina la base de datos existente y la crea de nuevo sin importar si hay nuevos cambios en el modelo o no. esto siempre conllevara perdida de datos y problemas de rendimiento al siempre tener que crear la base de datos.

Custom DB Initializer: Adicional podemos crear nuestro propio inicializador de base de datos si necesitamos algo muy específico, otro tipo de configuraciones, o ejecutar otro proceso externo para la inicialización, para esto podemos crear una clase que herede de una clase de inicialización cómo por ejemplo MigrateDatabaseToLatestVersion u otro de los mencionados.

MigrateDatabaseToLatestVersion: Nos permite actualizar automáticamente la base de datos, una vez inicie la aplicación, las actualizaciones pendientes se reflejaran en nuestra base de datos, este tipo de inicialización nos permite conservar los datos obviamente si no se trata de una instrucción Drop o Delete por ejemplo. Con este tipo es que podremos implementar migraciones en nuestra aplicación, por esto es importante conocer los diferentes tipos de inicialización que nos ofrece Entity Framework Code Fist.

Ahora sí, vamos a ver cómo implementar migraciones en nuestra base de datos.

Lo primero que debemos hacer es habilitar las migraciones, esto lo podemos hacer a través de la consola de paquetes nuget, de la siguiente forma:

En la barra de menús de Visual Studio seleccionamos Herramientas / Administrador de paquetes / Consola de administrador de paquetes y en esta consola seleccionamos en proyecto por defecto nuestro proyecto de acceso a datos donde tenemos nuestro modelo y escribimos lo siguiente:

enable-migrations -EnableAutomaticMigrations:$true

De esta forma se habilitan las migraciones en nuestro proyecto nos damos cuenta si leemos todo el log que se genera en la consola, ahora vamos a configurar nuestro contexto para sopórtalas.

Ahora si vamos al explorador de soluciones, veremos que se ha creado una carpeta llamada Migrations la cual contiene una clase llamada Configuration.cs.

Si exploramos la clase Configuration veremos que en su constructor el atributo AutomaticMigrationsEnabled se encuentra establecido en true, tal cual se lo indicamos en la consola de Nuget al ejecutar el comando, adicional veremos que hay un método sobre escrito llamado Seed, el cual nos permite cargar datos iniciales en nuestra base de datos, por ejemplo para cargar datos de configuración que se requieran desde el inicio para su correcto funcionamiento o para cargar tablas maestras de nuestra base de datos.

    internal sealed class Configuration : DbMigrationsConfiguration<EF6CodeFirst.Dominio.Context>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = true;
            AutomaticMigrationDataLossAllowed = true;
        }
 
        protected override void Seed(EF6CodeFirst.Dominio.Context context)
        {
            context.Productos.AddOrUpdate(
                p => p.Codigo,
                new Producto { Codigo = 1, Nombre = "Leche1", Descripcion = "Producto Lacteo" },
                 new Producto { Codigo = 2, Nombre = "Queso1", Descripcion = "Producto Lacteo" }
            );
        }
    }

Cómo vemos en el método Seed, creamos dos producto los cuales necesitamos crear al inicio para nuestro proyecto, adicional podemos ver que el constructor de la clase agregar el atributo AutomaticMigrationDataLossAllowed y lo establecimos en true, esto para asegurarnos que no suceda un error cuando posiblemente se pueda perder información tras una actualización, por ejemplo si decidimos eliminar un campo de alguna tabla tras la actualización nos diría que no se puede actualizar ya que se perdería la información que hay en ese campo, para esto habilitamos este atributo.

Para terminar con la configuración de las migraciones debemos indicar en el constructor de nuestro contexto que la estrategia de inicialización que vamos a usar es MigrateDatabaseToLatestVersion la cual explique anteriormente, y lo hacemos de la siguiente forma:

    public class Context : DbContext
    {
        public Context() : base("Productos")
        {
            Database.SetInitializer(new MigrateDatabaseToLatestVersion<ContextConfiguration>());            
        }
 
        public DbSet<Producto> Productos { getset; }
 
        public DbSet<Categoria> Categorias { getset; }
 
        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            // Aquí haremos nuestras configuraciones con Fluent API.
 
            modelBuilder.Configurations.Add(new ProductoMappings());
 
            modelBuilder.Entity<Producto>().HasMany<Categoria>(c => c.Categorias).WithMany(e => e.Productos).Map(e =>
                e.MapLeftKey("IdProducto"));
 
            modelBuilder.Entity<Producto>().HasMany<Categoria>(c => c.Categorias).WithMany(e => e.Productos).Map(e =>
                e.MapRightKey("IdCategoria"));
 
            modelBuilder.Entity<Producto>().HasMany<Categoria>(c => c.Categorias).WithMany(e => e.Productos).Map(e =>
                e.ToTable("ProductosCategorias"));
 
            base.OnModelCreating(modelBuilder);
        }
    }

Ahora podemos crear una app de consola para probar nuestro código y nos daremos cuenta que se creará la base de datos según nuestras configuraciones incluyendo los datos de carga inicial que establecimos en el método Seed, y por ultimo para probar las migraciones podemos eliminar una propiedad de alguna entidad de dominio y veremos que los cambios se ven reflejados en nuestra base de datos.

Y bueno amigos, eso es todo, espero les sea de utilidad y de interés este post acerca de migraciones en Entity Framework code First.

Saludos y buena suerte!

5 comentarios:

  1. Hola amigo, es con mucho gusto, gracias a ti por leerme, saludos!

    ResponderEliminar
  2. hola tavo deseo agregar unas columnas adicionales y ademas cambiar el nombre y crear una nueva base de datos donde coloco las instrucciones DropCreateDatabaseIfModelChanges y/o DropCreateDatabaseAlways.

    ademas quiero cambiar las tablas que vienen en la base de datos de identity
    asi:
    protected override void OnModelCreating(System.Data.Entity.DbModelBuilder modelBuilder)
    {
    base.OnModelCreating(modelBuilder);

    modelBuilder.Entity().ToTable("MyUsers").Property(p => p.Id).HasColumnName("UserId");
    modelBuilder.Entity().ToTable("MyUsers").Property(p => p.Id).HasColumnName("UserId");
    modelBuilder.Entity().ToTable("MyUserRoles");
    modelBuilder.Entity().ToTable("MyUserLogins");
    modelBuilder.Entity().ToTable("MyUserClaims");
    modelBuilder.Entity().ToTable("MyRoles");
    }

    al hacer el add-migration

    ResponderEliminar
  3. me falto terminar.... al hacer el add-migration xxxx y el update-database me sale esto
    El modelo correspondiente al contexto 'ApplicationDbContext' ha cambiado desde que se creó la base de datos. Considere la posibilidad de usar Migraciones de Code First para actualizar la base de datos

    al tratar de ejecutar el proyecto

    ResponderEliminar
    Respuestas
    1. Hola Greg, ya habilitaste las migraciones en tu proyecto a través de la consola de Nuget? enable-migrations -EnableAutomaticMigrations:$true

      Eliminar