Enero de 2016

Volumen 31, número 1

Puntos de datos. Migraciones de EF7: no son nuevas, pero sí definitivamente mejores

Por Julie Lerman | Enero de 2016

Julie LermanLas Migraciones de Code First experimentaron cambios importantes en Entity Framework 7. Si bien los conceptos básicos y los pasos de migración no cambiaron, el flujo de trabajo es muchos más limpio y directo. Ahora, funciona perfectamente con el control de código fuente.

EF7 ofrece dos tipos de migración: la variedad conocida que se usa con NuGet y la mayoría de los proyectos .NET, y el tipo que se ejecuta como parte del tiempo de ejecución de DNX con las aplicaciones de ASP.NET 5.

Encontrará un análisis de estos en uno de mis cursos de Pluralsight (bit.ly/1MThS4J). Aunque cambiaron varias cosas entre la versión 4 beta que usé en ese curso y la versión candidata para lanzamiento 1 (RC1) que uso hoy en día, ese curso sigue siendo un excelente recurso para ver las migraciones en acción y aprender sobre algunas de las diferencias.

Sin embargo, con el lanzamiento de RC1, que se considera que cuenta con las características completas para el RTM que se lanzará próximamente, llegó la hora de mirar más de cerca a las migraciones de EF7. Comenzaré por los conocidos comandos de Windows PowerShell de la Consola del Administrador de paquetes de Visual Studio. Luego analizaré estos comandos tal como se usarían con DNX y ASP.NET 5. Para este artículo, se supone que tiene algo de conocimientos sobre la inicialización de bases de datos y las características de migración de EF6 o versiones anteriores.

Basta con la inicialización mágica de bases de datos: use migraciones

La magia es costosa, limitadora y crea mucha carga. Además, en el caso de los objetos DbInitializers, creaba mucha confusión. Por tanto, los objetos DbInitializers de EF, junto con el comportamiento predeterminado de los objetos CreateData­baseIfNotExists, desaparecieron. En cambio, hay una lógica que permite crear y eliminar bases de datos explícitamente (EnsureDeleted, Ensure­Created) y que felizmente usaré en las pruebas de integración.

EF7 está diseñado para depender de las migraciones. Deberían ser ahora su flujo de trabajo predeterminado.

El comando “enable-migrations” existía porque las migraciones se adjuntaban a EF y su uso requería la ampliación de un poco de arquitectura. Ese comando no instalaba ninguna API nueva, simplemente agregaba una carpeta de proyecto y una nueva clase DbMigrationConfiguration al proyecto. Con EF7, la lógica pertinente es interna y no hace falta indicar a EF explícitamente que quiere usar migraciones.

Recuerde que EF7 admite composición. No todos querrán o necesitarán migraciones. Por tanto, si las necesita, tendrá que optar por usar los comandos de migraciones con la instalación del paquete que los contiene:

install-package entityframework.commands -pre

Comandos de migración de EF7

El uso del cmdlet Get-Help de Windows PowerShell muestra los comandos de migración actuales (a partir de RC1), tal como se muestra en la Figura 1. Esta lista debería ser igual para RTM.

Comandos de migración tal como figuran a través del comando Get-Command del Administrador de paquetes de NuGet
Figura 1 Comandos de migración tal como figuran a través del comando Get-Command del Administrador de paquetes de NuGet

Observe que el comando Update-Database ya no tiene el parámetro “-script”. Solo hará lo que sugiere el nombre: actualizar la base de datos. Como siempre, no se recomienda usar este comando en las bases de datos de producción. En la Figura 2 se muestran detalles sobre el comando Update-Database.

Sintaxis y parámetros de Update-Database
Figura 2 Sintaxis y parámetros de Update-Database

Para crear scripts, puede usar el nuevo comando Script-Migration. Además, ya no existe una receta secreta para crear scripts idempotentes (presentados en EF6). Idempotente es un parámetro, tal como se puede ver en la Figura 3.

Parámetros de Script-Migration
Figura 3 Parámetros de Script-Migration

Grandes cambios para el historial de migraciones y las instantáneas de modelo

Las migraciones automáticas se quitaron de EF7. Por ello, la administración de migraciones es más sencilla y ahora se admite el control de código fuente.

Para que las migraciones determinen lo que debe suceder, tiene que existir un historial de las migraciones anteriores. Cada vez que ejecute el comando add-migration, la API consultará esa información histórica. Anteriormente, las migraciones almacenaban una versión codificada de una instantánea del modelo para cada migración de la base de datos, en una tabla (más recientemente) denominada _MigrationHistory. Esto es lo que EF usó para saber que tenía que hacer una migración. Cada vez que se ejecutaba el comando add-migration, se consultaba la base de datos para leer su historial, y esto era una fuente de muchos problemas.

Con EF7, cuando se crea una migración (a través del comando add-migration), EF7 no solo crea el conocido archivo de migración dentro de la carpeta Migraciones, también crea un archivo para almacenar la instantánea del modelo actual. El modelo se describe con la sintaxis de la API de Fluent que se usa para las configuraciones. Si hace un cambio en el modelo y luego agrega una migración, la instantánea se modifica con la descripción actual del modelo completo. En la Figura 4 se muestra un archivo de migración que introduce una nueva cadena denominada “MyProperty.”

Figure 4 Clase de migración completa newStringInSamurai

public partial class newStringInSamurai : Migration
  {
    protected override void Up(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.AddColumn<string>(
        name: "MyProperty",
        table: "Samurai",
        nullable: true);
    }
    protected override void Down(MigrationBuilder migrationBuilder)
    {
      migrationBuilder.DropColumn(name: "MyProperty", table: "Samurai");
    }
  }

Esta es la parte de la clase ModelSnapshot actualizada, donde puede ver que “MyProperty” ahora forma parte de la descripción de la clase Samurai:

modelBuilder.Entity("EF7Samurai.Domain.Samurai", b =>
  {
    b.Property<int>("Id")
      .ValueGeneratedOnAdd();
    b.Property<string>("MyProperty");
    b.Property<string>("Name");
    b.HasKey("Id");
  });

Por tanto, las migraciones individuales describen lo que ha cambiado, como lo han hecho siempre. Pero ahora, en el proyecto, siempre cuenta con una descripción del modelo completo. Esto significa que cuando agrega una nueva migración, EF no tiene que consultar la base de datos para saber qué aspecto tenía el modelo anterior. Lo que necesita lo tiene a mano. Y lo que es aún más importante, esto funciona mucho mejor con el control de código fuente porque se encuentra en el mismo código y se puede comparar y fusionar mediante combinación según el control de código fuente como en cualquier otro archivo. No está escondido en una base de datos que quizás no sea accesible para los otros miembros del equipo. Antes de EF7, no había una buena manera de administrar las migraciones en el control de código fuente. Encontrará una guía sobre las migraciones para las versiones anteriores a EF7 en “Code First Migrations in Team Environments” (bit.ly/1OVO3qr). Este artículo aconseja coger una taza de café para conseguir leer todo el artículo.

Con EF7, la tabla de historial de migración de la base de datos (Figura 5) ahora solo almacena los identificadores de la migración y la versión de EF que los creó. Nuevamente, esto es un importante cambio en comparación con las versiones anteriores, que almacenaban en esta tabla la instantánea. El comando Update-Database comprobará esta tabla para ver qué migraciones se aplicaron y aplica las que faltan, incluso si no son consecutivas. Siempre y cuando esta operación necesite consultar la base de datos, no veo ningún problema en que las migraciones hagan rápidamente una comprobación previa de la base de datos en este tipo de escenario.

Comando dbo_EFMigrationsHistory que muestra la tabla de migraciones agregadas
Figura 5 Comando dbo_EFMigrationsHistory que muestra la tabla de migraciones agregadas

Ya no hace falta preocuparse por no poder agregar una migración si la anterior todavía no aplicó a la base de datos. Eso era un efecto secundario de las migraciones automáticas y, por eso, muchos de nosotros caímos en una telaraña de confusión. Ahora, puede ampliar migraciones en la solución según sea necesario y luego actualizar la base de datos cuando esté listo.

Debido a la instantánea del modelo que EF7 almacena en el proyecto, no debería simplemente eliminar un archivo de migración de su proyecto porque entonces la instantánea sería incorrecta. Por tanto, junto con el cambio en el flujo de trabajo de migraciones se incluye un nuevo comando: remove-migration. El comando remove-migration está diseñado para deshacer una migración que seguramente acaba de agregar y sobre la que cambia de opinión antes de su aplicación a la base de datos. El comando hace dos cosas en el proyecto: quita el archivo de migración más reciente y luego actualiza el archivo de instantáneas de la carpeta de migraciones. Lo interesante es que no se regenera la instantánea basada en el modelo. En cambio, modifica la instantánea basada en el modelo que se encuentra en el archivo Designer.cs que se crea junto con cada instantánea. Al probarlo, observé que si no cambiaba el modelo (es decir, si no quitaba la propiedad no deseada de la clase Samurai), la instantánea aún se corregía según las migraciones. Sin embargo, no se olvide de corregir el modelo también. De lo contrario, el modelo y la instantánea no estarán sincronizados y, lo más probable, el esquema de base de datos tampoco estará sincronizado con el modelo.

Como nota adicional, Brice Lambson, el miembro del equipo de EF responsable de las migraciones, compartió conmigo que, si se eliminara manualmente un archivo de migración, la llamada inmediatamente subsiguiente a remove-­migration lo vería y corregiría la instantánea sin eliminar otro archivo de migración.

Al igual que el comando add-migration, el comando remove-­migration no afecta a la base de datos. Sin embargo, sí comprueba el historial de migración de la base de datos para ver si la migración ya se aplicó. En caso afirmativo, no podrá usar el comando remove-migration, al menos, no todavía.

Suena algo confuso, ya lo sé. Sin embargo, aún me gusta el comando remove-migration porque la alternativa es seguir avanzando y agregar una migración siempre que cambie de idea. Eso puede resultar algo confuso para los otros miembros del equipo o en el futuro para usted mismo. Además, es un costo insignificante por obtener la ventaja de permitir que las migraciones participen en el control de código fuente.

Para ayudar a aclarar las cosas, en la Figura 6 se muestra una guía para desenredar una migración no deseada. En el gráfico se presupone que “Migration1” se aplicó a la base de datos y que “Migration2” se creó, pero nunca se aplicó a la base de datos.

Figura 6 Tratar una migración no deseada

Estado de la base de datos comandos Tareas realizadas por comando
Update-Database no se ejecuta después de Migration2 Remove-Migration

1. Comprueba que Migration2 no es una tabla de historial.

2. Elimina Migration2.cs.

3. Corrige Snapshot.cs para reflejar la migración anterior.

Update-Database se ejecuta después de Migration2

Update-Database Migration1

Remove-Migration

1. Ejecuta SQL para el método “Drop” de Migration2.

2. Quita la fila Migration2 de la tabla de historial.

1. Elimina Migration2.cs.

2. Corrige Snapshot.cs para reflejar la migración anterior.

Si necesita deshacer varias migraciones, comience por llamar a update-database, seguido por el nombre de la última migración correcta. Luego, puede llamar a remove-migration para retroceder las migraciones y la instantánea, una a la vez.

Con todo este enfoque en las migraciones, no se olvide que esas migraciones son un reflejo de los cambios en el modelo. Recuerde que debe deshacer manualmente los cambios en el modelo y luego quitar las migraciones que reflejen dichos cambios.

Ingeniería inversa con Scaffold-DbContext

En las versiones anteriores de EF se proporcionaba la capacidad de hacer una ingeniería inversa de una base de datos en los modelos Code First. Microsoft puso esta capacidad a disposición de los usuarios a través de Entity Framework Power Tools y, a partir de EF6, a través de EF Designer. También existen herramientas de terceros, como la popular extensión gratuita EntityFramework Reverse POCO Generator (reversepoco.com). Dado que EF7 no cuenta con diseñador, el comando scaffold se creó para hacer este trabajo.

Estos son los parámetros de Scaffold-DbContext:

Scaffold-DbContext [-Connection] <String> [-Provider] <String>
                   [-OutputDirectory <String>] [-ContextClassName <String>]
                   [-Tables <String>] [-DataAnnotations] [-Project <String>]
                   [<CommonParameters>]

Soy una gran aficionada a las configuraciones de API fluida, por lo que me agrada que estas sean nuevamente la opción predeterminada y hay que optar por usar la anotación de datos en lugar de la marca DataAnnotations. A continuación se incluye un comando para hacer la ingeniería inversa de mi base de datos existente con ese parámetro (también podría seleccionar las tablas a las que aplicar el comando scaffold, pero no lo haré aquí):

Scaffold-DbContext
   -Connection "Server = (localdb)\mssqllocaldb; Database=EF7Samurai;"
   -Provider entityframework.sqlserver
   -OutputDirectory "testRE"

Como se muestra en la Figura 7, esto resultó en una nueva carpeta y varios archivos agregados a mi proyecto. Tengo que examinar mucho más esas clases porque se trata de una característica importante, pero lo dejaré para más adelante.

Resultado de Scaffold-DbContext
Figura 7 Resultado de Scaffold-DbContext

Versiones DNX de los comandos de migración

Los comandos que ejecute en la Consola del Administrador de paquetes son los comandos de Windows PowerShell y, por supuesto, Windows PowerShell forma parte de Windows. Una de las características más importantes de ASP.NET 5 (y también EF7) es que ya no dependen de Windows. DNX es un entorno de ejecución .NET ligero que se puede ejecutar en OS X y Linux, así como en Windows. Encontrará más información en bit.ly/1Gu34wX. El paquete entityframework.commands también contiene comandos de migración para el entorno DNX. A continuación se incluye un resumen de esos comandos.

Puede ejecutar los comandos directamente en la Consola del Administrador de paquetes si usa Visual Studio 2015, que es lo que mostraré aquí. De lo contrario, puede ejecutarlos en la línea de comandos del sistema operativo pertinente. Consulte el vídeo de Nate McMaster en Channel 9 donde codifica EF7 y las migraciones en un Mac. (bit.ly/1OSUCdo)

En primer lugar, debe asegurarse de encontrarse en la carpeta correcta. Se trata de la carpeta de proyecto en la que se encuentra el modelo. El elemento desplegable del proyecto no está relacionado con la ruta de acceso al archivo. Use el comando dir para ver dónde se encuentra y luego use el comando cd para ir a la carpeta correcta. La finalización con tabulación es de utilidad aquí. En la Figura 8, puede observar que aunque el proyecto predeterminado sea src\EF7WebAPI, para ejecutar los comandos de DNX en ese directorio, tengo que cambiar explícitamente el directorio a la ruta de acceso para este proyecto que contiene mi modelo EF7.

Ir al directorio correcto antes de ejecutar los comandos de DNX
Figura 8 Ir al directorio correcto antes de ejecutar los comandos de DNX

Cuando me encuentre allí, podré ejecutar los comandos. De hecho, cada comando se debe reemplazar por dnx y puede usar dnx ef para obtener una lista de todos los comandos disponibles. Tenga en cuenta que ef es un acceso directo que configuré en project.json. Consulte mi vídeo Pluralsight, “Looking Ahead to Entity Framework 7” (bit.ly/PS_EF7Alpha), para obtener más información sobre la configuración.

Los comandos núcleo son database, dbcontext y migrations. El comando migrations cuenta con los siguientes subcomandos:

add     Add a new migration
list    List the migrations
remove  Remove the last migration
script  Generate a SQL script from migrations

Cada comando tiene parámetros que puede consultar al agregar --help, de este modo:

dnx ef migrations add --help

Por ejemplo, si el nombre de migración es initial, el comando es:

dnx migrations add initial

Recuerde que estos son los mismos comandos que se mencionaron anteriormente en este artículo. Por tanto, encontrará parámetros que le permiten especificar el proyecto, el objeto DbContext y otros detalles. El comando script tiene el parámetro idempotente, según se describió anteriormente. Una diferencia en el comando dnx script es que, de manera predeterminada, el script se enumera en la ventana de comandos. Tendrá que especificar explícitamente un parámetro output <archivo> para insertar el script en un archivo.

El siguiente comando aplicará las migraciones a la base de datos:

dnx ef database update

El comando dbcontext tiene dos subcomandos: dbcontext list enumerará todos los objetos DbContext que se pueden detectar en el proyecto. Esto se encuentra aquí porque no se puede obtener fácilmente el objeto tab-expansion para proporcionar las opciones, como se puede hacer cuando se usan los comandos en Windows PowerShell; scaffold es la versión dnx del comando DbContext-Scaffold que se describió anteriormente.

Mejor experiencia global con EF7

En EF7, las migraciones se simplificaron gracias a la eliminación de las migraciones automáticas. Aunque los conceptos básicos son los mismos, el flujo de trabajo es mucho más limpio. La eliminación de las barreras para usar las migraciones en un entorno de equipo fue fundamental. Me gusta que esto se haya corregido al llevar la instantánea de modelo al código fuente. Además, poder hacer la ingeniería inversa con el comando scaffold es una excelente manera de proporcionar esta importante capacidad sin tener que depender de un diseñador.

En las notas de reunión sobre el diseño del 23 de julio del equipo de EF (bit.ly/1S813ro) se incluye una tabla con todos los comandos de Windows PowerShell y DNX después de ajustar la asignación de nombres. Espero que, tarde o temprano, haya una versión aún más limpia en la documentación, aunque todavía considero esta de gran utilidad.

Escribí este artículo con la versión RC1 de EF7. Sin embargo, esta versión se considera de características completas, por lo que es probable que esta información se alinee con la versión de EF7 que se lanzará próximamente con ASP.NET 5 a principios de 2016.


Julie Lerman es una Microsoft MVP, mentora y consultora de .NET que vive en las colinas de Vermont. Puede encontrarla haciendo presentaciones sobre el acceso a datos y otros temas de .NET a grupos de usuarios y en conferencias en todo el mundo. Su blog es thedatafarm.com/blog y es la autora de “Programming Entity Framework” (2009) así como de una edición de Code First y una edición de DbContext, de O’Reilly Media. Sígala en Twitter en @julielerman y vea sus cursos de Pluralsight en juliel.me/PS-Videos.

Gracias al siguiente experto técnico de Microsoft por revisar este artículo: Brice Lambson