Entity Framework 6 y sus migraciones

En este artículo voy a intentar explicar cómo usar las migraciones en Entity Framework (EF6) y las operaciones que podemos realizar para acabar manejándolas sin ningún problema. Para que esté bien claro, este post es sobre EF6, en uno posterior trataré sobre EF Core.


Esto no pretende ser una guía de EF, por lo que doy por hecho que se conoce EF, y como funciona la creación de contextos. Sino para ello que mejor que un tutorial con toda la info: http://www.entityframeworktutorial.net.

Migraciones Automáticas y cuándo usarlas

Tener habilitadas las migraciones automáticas puede ser muy útil cuando arrancamos un proyecto o para hacer pruebas de concepto, pero más allá de esos casos yo aconsejo tenerlas siempre desactivadas. Tendremos las migraciones totalmente controladas por código sin que la magia del automático trabaje por nosotros.

La potencia sin control no sirve de nada. — Pirelli & C. S.p.A.


Para desactivarlas, lo tenemos que marcar por código en la clase Configuration que herede de DbMigrationsConfiguration:

AutomaticMigrationsEnabled = false;

Habilitar migraciones en una BD y viendo el detalle

public class SampleDbContext : DbContext
{
    public IDbSet<User> Users { get; set; }

    static SampleDbContext()
    {
    }

    public SampleDbContext() : base("name=Default")
    {

    }
    
    
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        this.CommonModelCreating(modelBuilder);
        
        base.OnModelCreating(modelBuilder);
    }

    private void CommonModelCreating(DbModelBuilder modelBuilder)
    {
        
    }
    
}

Ahora si inspeccionamos la tabla __MigrationHistory veremos el registro correspondiente a la migración creada. Select Migration

Extendiendo y personalizando una migración

Si la migración Initial queremos que sea un reset completo, automático, y que no falle aunque lo ejecutemos N veces, hay que ir un paso más allá personalizando y extendiendo operaciones de migración. Si en el punto anterior hemos borrado la BD y volvemos a ejecutar el mismo update-database obtendremos un error.
¿Por qué?
Porque el script que se genera de un DropTable no comprueba si la tabla existe previamente.
¿Y ahora qué hacemos?
Para todo hay solución y EF nos permite crear nuestra propia operación y personalizar el código generado en la migración, así que podemos crear un método de extensión llamado DropTableIfExists y ejecutar el código sql para verificar que la tabla existe, antes de borrarlo. La intención es que se ejecute el siguiente script sql (los ejemplos están contra una BD de SqlServer):

IF EXISTS (SELECT name FROM sys.tables WHERE name = N'tabla' AND object_id = object_id(N'[dbo].[tabla]', N'U'))
DROP TABLE [dbo].[tabla]

Para conseguir que EF genere nuestro sql personalizado seguiremos los siguientes pasos:


Ahora podemos ejecutar:

Update-Database -ProjectName "App1.Data" -TargetMigration 0 -verbose -Script

Estaremos bajando a la migración con nombre Initial (la cúal también se puede indicar con valor cero). Y si ahora ejecutamos nuevamente veremos que no falla:

Update-Database -ProjectName "App1.Data" -verbose

Con esto ya que podemos eliminar por completo nuestra BD y actualizarla sin problemas a la última migración.

Esto es un ejemplo que puedes utilizar para crear tus propias operaciones.

Como crear nuevas migraciones:

Nuestro código cambia y es normal que nuestra BD también evolucione, creando o borrando nuevos campos, tablas, etc…
Cuando tengamos listos nuestros cambios ejecutamos el siguiente comando para crear una nueva migración.

Add-Migration -Name NombreDeMigracion -ProjectName "App1.Data"


Esto generará una clase en la carpeta Migrations con el formato yyyyMMdd_NombreDeMigracion.cs
En este fichero podremos personalizar lo que nos interese, como por ejemplo inicializar el valor de los campos.
Y para establecer los cambios nuevamente ejecutamos el comando.

Update-Database -ProjectName "App1.Data" -verbose

agregar el comando -Script si queremos el ScriptSQL de ejecución de cambios.

Oh! te has olvidado de añadir algo en la migración??
que nadie se asuste ;-) vamos a explicarlo en el siguiente punto.

Editar una migración creada

¿Y si quiero editar una migración sin tener que crear otra nueva con el scaffolding aplicado? Durante el desarrollo de una tarea, es muy probable que nos encontremos con la necesidad de cambiar nuestra migración por ejemplo para incluir esa columna que nos haya podido olvidar en un primer momento. Por otra parte tampoco queda muy elegante tener 5 o 6 migraciones para una misma tarea de desarrollo.
Para editar nuestra migración:


Si intentamos hacer el proceso de forma manual y no usando los comandos, tendremos problemas al agregar próximas migraciones. El comando add-migration también se encarga de modificar el fichero .resx que contiene un valor serializado del estado de la BD.

podemos consultar la tabla __MigrationHistory order by MigrationId desc
Para editar nuestra migración: tendremos que ejecutar el comando de Add-Migrations indicando el nombre exacto de la migración que queremos sobrescribir, por lo general yyyymmdddhhMMss_NombreDeMigracion, y agregar el parámetro -Force para forzar el re-scaffold de la migración entera. Si no lo haces verás un warning amarillo que te lo indicará.

Add-Migration -Name full_name_including_timestamp_of_last_migration -ProjectName "App1.Data" -Force

OJO!
si ya ejecutaste un UpdateDatabase antes de editar la migración, sería recomendable poner la BD en el estado anterior a la migración. Y posteriormente volver a realizar un update-database.

Crear una migración vacía

Hay veces que podemos tener cambios en objetos de nuestra BD que no interfieren directamente sobre nuestro contexto y el scaffolding no lo detecta. Cambios por ejemplo en un store procedure, function, views, etc…
Para tener controlados en nuestro historial de migraciones este tipo de cambios , tenemos que generar una migración vacía.
¿Cómo se hace?
Exactamente de la misma manera que creando una migración normal.

Add-Migration -Name ChangesInStoreProcedures -ProjectName "App1.Data" 

Esto nos genera nuestra migración con la diferencia de que los métodos Up() y Down() están vacíos.

Cómo agregar mi script SQL a una migración

Para ejecutar cualquier script sql en los métodos Up() y Down() de nuestro fichero migration, tenemos el método Sql(…) heredado de DbMigration.
¿Queremos agregar una vista al modelo de datos? Pues un ejemplo sería así:

public partial class ChangesInViews : DbMigration
    {
        public override void Up()
        {
            string scriptSql = @"
CREATE VIEW [dbo].[vw_Users]
AS
    select Id, Name, Email, IsBlocked
    from dbo.Users
GO
";
            Sql(scriptSql);
        }
        
        public override void Down()
        {
            string scriptSql = @"DROP VIEW [dbo].[vw_Users]";

            Sql(scriptSql);
        }
    }

Obviamente, si el escript no es correcto la migración fallará. Es tú responsabilidad que se ejecute correctamente, tanto para Up() como para Down().

Migrar a una versión concreta (Downgrade)

Para migrar la Bd a una situación en concreto debe indicarse el parámetro -TargetMigration al comando update-database. Tal que así:

Update-Database -ProjectName "App1.Data" –TargetMigration NombreDeMigracion


Para volver al inicio del todo y a una base de datos “vacía” (primera migración):

Update-Database -ProjectName "App1.Data" –TargetMigration $InitialDatabase

o también indicando un cero:

Update-Database -ProjectName "App1.Data" –TargetMigration 0

Listado de migraciones en nuestra BD

Para obtener las migraciones que están aplicadas sobre nuestra BD usaremos el siguiente comando

Get-Migrations -ProjectName "App1.Data"

Depurar el Seed de Inicializacion de datos:

Descomentar o añadir las siguientes líneas en la clase Configuration que sería algo como: App1.Data.Migrations.Configuration

protected override void Seed(App1.Data.Context.ApplicationDbContext context) {
    if (System.Diagnostics.Debugger.IsAttached == false){
        System.Diagnostics.Debugger.Launch();
    }
    System.Diagnostics.Debugger.Break();

[...]
}

Cómo usar las migraciones con el resto del equipo de desarrollo

Hay un artículo muy completo en la documentación de Microsoft que explica con ejemplos claros y sencillos como manejar las migraciones y las diferentes situaciones que nos podemos encontrar al usarlas cuando nuestro equipo de trabajo hay varios programadores que pueden estar cambiando la base de datos.
Un artículo muy recomendado: https://msdn.microsoft.com/en-us/data/dn481501.aspx.




Espero que alguien más a parte de mi yo futuro encuentre útil estas notas.

Saludos.


Comparte esto: