Migraciones de Code FirstCode First Migrations

Migraciones de Code First es la manera recomendada de desarrollar el esquema de base de datos de la aplicación si usa el flujo de trabajo de Code First.Code First Migrations is the recommended way to evolve your application's database schema if you are using the Code First workflow. Migraciones proporciona un conjunto de herramientas que permiten:Migrations provide a set of tools that allow:

  1. Crear una base de datos inicial que funciona con el modelo de EFCreate an initial database that works with your EF model
  2. Generar migraciones para realizar un seguimiento de los cambios realizados en el modelo de EFGenerating migrations to keep track of changes you make to your EF model
  3. Mantener actualizada la base de datos con esos cambiosKeep your database up to date with those changes

En el siguiente tutorial se proporciona información general sobre Migraciones de Code First en Entity Framework.The following walkthrough will provide an overview of Code First Migrations in Entity Framework. Puede realizar el tutorial completo o ir al tema que le interesa.You can either complete the entire walkthrough or skip to the topic you are interested in. Se tratan los siguientes temas:The following topics are covered:

Creación de un modelo inicial y una base de datosBuilding an Initial Model & Database

Antes de empezar a usar Migraciones, se necesitan un proyecto y un modelo de Code First con los que trabajar.Before we start using migrations we need a project and a Code First model to work with. En este tutorial se va a usar el modelo canónico Blog y Post.For this walkthrough we are going to use the canonical Blog and Post model.

  • Cree una nueva aplicación de consola MigrationsDemoCreate a new MigrationsDemo Console application
  • Agregue la versión más reciente del paquete NuGet de EntityFramework al proyectoAdd the latest version of the EntityFramework NuGet package to the project
    • Herramientas –> Administrador de paquetes de biblioteca–> Consola del Administrador de paquetesTools –> Library Package Manager –> Package Manager Console
    • Ejecute el comando Install-Package EntityFrameworkRun the Install-Package EntityFramework command
  • Agregue un archivo Model.cs con el código que se muestra a continuación.Add a Model.cs file with the code shown below. Este código define una sola clase Blog que conforma el modelo de dominio y una clase BlogContext que es el contexto de EF Code FirstThis code defines a single Blog class that makes up our domain model and a BlogContext class that is our EF Code First context
    using System.Data.Entity;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.Data.Entity.Infrastructure;

    namespace MigrationsDemo
    {
        public class BlogContext : DbContext
        {
            public DbSet<Blog> Blogs { get; set; }
        }

        public class Blog
        {
            public int BlogId { get; set; }
            public string Name { get; set; }
        }
    }
  • Ahora que tenemos un modelo, es hora de usarlo para el acceso a los datos.Now that we have a model it’s time to use it to perform data access. Actualice el archivo Program.cs con el código que se muestra a continuación.Update the Program.cs file with the code shown below.
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;

    namespace MigrationsDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                using (var db = new BlogContext())
                {
                    db.Blogs.Add(new Blog { Name = "Another Blog " });
                    db.SaveChanges();

                    foreach (var blog in db.Blogs)
                    {
                        Console.WriteLine(blog.Name);
                    }
                }

                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }
    }
  • Ejecute la aplicación y verá que se ha creado automáticamente una base de datos MigrationsCodeDemo.BlogContext.Run your application and you will see that a MigrationsCodeDemo.BlogContext database is created for you.

    Base de datos LocalDB

Habilitación de migracionesEnabling Migrations

Es hora de realizar más cambios en el modelo.It’s time to make some more changes to our model.

  • Vamos a introducir una propiedad Url en la clase Blog.Let’s introduce a Url property to the Blog class.
    public string Url { get; set; }

Si volviera a ejecutar la aplicación de nuevo, aparecería una excepción InvalidOperationException con el texto El modelo que respalda el contexto 'BlogContext' 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 ( http://go.microsoft.com/fwlink/?LinkId=238269 ).If you were to run the application again you would get an InvalidOperationException stating The model backing the 'BlogContext' context has changed since the database was created. Consider using Code First Migrations to update the database ( http://go.microsoft.com/fwlink/?LinkId=238269).

Como sugiere la excepción, es hora de empezar a usar Migraciones de Code First.As the exception suggests, it’s time to start using Code First Migrations. El primer paso es habilitar las migraciones para el contexto.The first step is to enable migrations for our context.

  • Ejecute el comando Enable-Migrations en la consola del Administrador de paquetesRun the Enable-Migrations command in Package Manager Console

    Este comando ha agregado una carpeta Migraciones al proyecto.This command has added a Migrations folder to our project. Esta nueva carpeta contiene dos archivos:This new folder contains two files:

  • La clase Configuration.The Configuration class. Esta clase permite configurar el comportamiento de Migraciones en el contexto.This class allows you to configure how Migrations behaves for your context. En este tutorial se usa la configuración predeterminada.For this walkthrough we will just use the default configuration. Puesto que hay un solo contexto de Code First en el proyecto, Enable-Migrations ha rellenado automáticamente el tipo de contexto al que se aplica esta configuración.Because there is just a single Code First context in your project, Enable-Migrations has automatically filled in the context type this configuration applies to.

  • Una migración InitialCreate.An InitialCreate migration. Esta migración se ha generado porque Code First ya había creado automáticamente una base de datos antes de que se habilitaran las migraciones.This migration was generated because we already had Code First create a database for us, before we enabled migrations. El código de esta migración con scaffolding representa los objetos que ya se han creado en la base de datos.The code in this scaffolded migration represents the objects that have already been created in the database. En nuestro caso, es la tabla Blog con las columnas BlogId y Name.In our case that is the Blog table with a BlogId and Name columns. El nombre de archivo incluye una marca de tiempo para ayudar con el orden.The filename includes a timestamp to help with ordering. Si la base de datos aún no se hubiera creado, esta migración InitialCreate no se hubiera agregado al proyecto. Por el contrario, la primera vez que se llamara a Add-Migration, al código para crear estas tablas se le aplicaría scaffolding para una nueva migración.If the database had not already been created this InitialCreate migration would not have been added to the project. Instead, the first time we call Add-Migration the code to create these tables would be scaffolded to a new migration.

Varios modelos con la misma base de datos como destinoMultiple Models Targeting the Same Database

Al usar versiones anteriores a EF6, solo se puede usar un modelo de Code First para generar o administrar el esquema de una base de datos.When using versions prior to EF6, only one Code First model could be used to generate/manage the schema of a database. Este es el resultado de una sola tabla __MigrationsHistory por base de datos sin forma de identificar qué entradas pertenecen a qué modelo.This is the result of a single __MigrationsHistory table per database with no way to identify which entries belong to which model.

A partir de EF6, la clase Configuration incluye una propiedad ContextKey.Starting with EF6, the Configuration class includes a ContextKey property. Esta actúa como identificador único para cada modelo de Code First.This acts as a unique identifier for each Code First model. Una columna correspondiente de la tabla __MigrationsHistory permite que entradas de varios modelos compartan la tabla.A corresponding column in the __MigrationsHistory table allows entries from multiple models to share the table. De forma predeterminada, esta propiedad se establece en el nombre completo del contexto.By default, this property is set to the fully qualified name of your context.

Generación y ejecución de migracionesGenerating & Running Migrations

Migraciones de Code First tiene dos comandos principales con los que se va a familiarizar.Code First Migrations has two primary commands that you are going to become familiar with.

  • Add-Migration aplica la técnica scaffolding a la siguiente migración en función de los cambios realizados en el modelo desde la creación de la última migraciónAdd-Migration will scaffold the next migration based on changes you have made to your model since the last migration was created
  • Update-Database aplica las migraciones pendientes a la base de datosUpdate-Database will apply any pending migrations to the database

Es necesario aplicar scaffolding a una migración para encargarse de la nueva propiedad Url que se ha agregado.We need to scaffold a migration to take care of the new Url property we have added. El comando Add-Migration permite poner un nombre a estas migraciones; a la nuestra la llamaremos AddBlogUrl.The Add-Migration command allows us to give these migrations a name, let’s just call ours AddBlogUrl.

  • Ejecute el comando Add-Migration AddBlogUrl en la consola del Administrador de paquetesRun the Add-Migration AddBlogUrl command in Package Manager Console
  • En la carpeta Migraciones ahora tenemos una nueva migración AddBlogUrl.In the Migrations folder we now have a new AddBlogUrl migration. El nombre de archivo de la migración lleva una marca de tiempo para ayudar con el ordenThe migration filename is pre-fixed with a timestamp to help with ordering
    namespace MigrationsDemo.Migrations
    {
        using System;
        using System.Data.Entity.Migrations;

        public partial class AddBlogUrl : DbMigration
        {
            public override void Up()
            {
                AddColumn("dbo.Blogs", "Url", c => c.String());
            }

            public override void Down()
            {
                DropColumn("dbo.Blogs", "Url");
            }
        }
    }

Ahora se podría editar esta migración o agregarle elementos, pero todo tiene bastante buen aspecto.We could now edit or add to this migration but everything looks pretty good. Vamos a usar Update-Database para aplicar esta migración a la base de datos.Let’s use Update-Database to apply this migration to the database.

  • Ejecute el comando Update-Database en la consola del Administrador de paquetesRun the Update-Database command in Package Manager Console
  • Migraciones de Code First compara las migraciones de la carpeta Migraciones con las que se han aplicado a la base de datos.Code First Migrations will compare the migrations in our Migrations folder with the ones that have been applied to the database. Verá que es necesario aplicar la migración AddBlogUrl y ejecutarla.It will see that the AddBlogUrl migration needs to be applied, and run it.

La base de datos MigrationsDemo.BlogContext se ha actualizado para incluir la columna Url en la tabla Blogs.The MigrationsDemo.BlogContext database is now updated to include the Url column in the Blogs table.

Personalización de migracionesCustomizing Migrations

Hasta ahora hemos generado y ejecutado una migración sin realizar ningún cambio.So far we’ve generated and run a migration without making any changes. Ahora vamos a editar el código que se genera de forma predeterminada.Now let’s look at editing the code that gets generated by default.

  • Es hora de realizar algunos cambios más en el modelo: vamos a agregar una nueva propiedad Rating a la clase BlogIt’s time to make some more changes to our model, let’s add a new Rating property to the Blog class
    public int Rating { get; set; }
  • También vamos a agregar una nueva clase PostLet's also add a new Post class
    public class Post
    {
        public int PostId { get; set; }
        [MaxLength(200)]
        public string Title { get; set; }
        public string Content { get; set; }

        public int BlogId { get; set; }
        public Blog Blog { get; set; }
    }
  • Además agregaremos una colección Posts a la clase Blog para formar el otro extremo de la relación entre Blog y PostWe'll also add a Posts collection to the Blog class to form the other end of the relationship between Blog and Post
    public virtual List<Post> Posts { get; set; }

Vamos a usar el comando Add-Migration para permitir que Migraciones de Code First aplique scaffolding de su mejor estimación en la migración.We'll use the Add-Migration command to let Code First Migrations scaffold its best guess at the migration for us. Vamos a llamar a esta migración AddPostClass.We’re going to call this migration AddPostClass.

  • Ejecute el comando Add-Migration AddPostClass en la consola del Administrador de paquetes.Run the Add-Migration AddPostClass command in Package Manager Console.

Migraciones de Code First hizo un muy buen trabajo de scaffolding de estos cambios, pero hay algunas cosas que es posible que queramos cambiar:Code First Migrations did a pretty good job of scaffolding these changes, but there are some things we might want to change:

  1. En primer lugar, vamos a agregar un índice único a la columna Posts.Title (adición en la línea 22 y 29 del código siguiente).First up, let’s add a unique index to Posts.Title column (Adding in line 22 & 29 in the code below).
  2. También vamos a agregar una columna Blogs.Rating que no admite valores null.We’re also adding a non-nullable Blogs.Rating column. Si hay datos existentes en la tabla, se les asigna el valor predeterminado CLR del tipo de datos para la nueva columna (Rating es entero, por lo que sería 0).If there is any existing data in the table it will get assigned the CLR default of the data type for new column (Rating is integer, so that would be 0). Pero queremos especificar un valor predeterminado de 3, para que las filas existentes en la tabla Blogs comiencen con una clasificación decente.But we want to specify a default value of 3 so that existing rows in the Blogs table will start with a decent rating. (Puede ver el valor predeterminado especificado en la línea 24 del código siguiente)(You can see the default value specified on line 24 of the code below)
    namespace MigrationsDemo.Migrations
    {
        using System;
        using System.Data.Entity.Migrations;

        public partial class AddPostClass : DbMigration
        {
            public override void Up()
            {
                CreateTable(
                    "dbo.Posts",
                    c => new
                        {
                            PostId = c.Int(nullable: false, identity: true),
                            Title = c.String(maxLength: 200),
                            Content = c.String(),
                            BlogId = c.Int(nullable: false),
                        })
                    .PrimaryKey(t => t.PostId)
                    .ForeignKey("dbo.Blogs", t => t.BlogId, cascadeDelete: true)
                    .Index(t => t.BlogId)
                    .Index(p => p.Title, unique: true);

                AddColumn("dbo.Blogs", "Rating", c => c.Int(nullable: false, defaultValue: 3));
            }

            public override void Down()
            {
                DropIndex("dbo.Posts", new[] { "Title" });
                DropIndex("dbo.Posts", new[] { "BlogId" });
                DropForeignKey("dbo.Posts", "BlogId", "dbo.Blogs");
                DropColumn("dbo.Blogs", "Rating");
                DropTable("dbo.Posts");
            }
        }
    }

La migración editada ya está lista, así que vamos a usar Update-Database para actualizar la base de datos.Our edited migration is ready to go, so let’s use Update-Database to bring the database up-to-date. Esta vez vamos a especificar la marca -Verbose para que pueda ver el SQL que ejecuta Migraciones de Code First.This time let’s specify the –Verbose flag so that you can see the SQL that Code First Migrations is running.

  • Ejecute el comando Update-Database –Verbose en la consola del Administrador de paquetes.Run the Update-Database –Verbose command in Package Manager Console.

Movimiento de datos o SQL personalizadoData Motion / Custom SQL

Hasta ahora hemos examinado las operaciones de migración que no cambian ni mueven datos, y ahora vamos a ver algo que necesita mover algunos datos.So far we have looked at migration operations that don’t change or move any data, now let’s look at something that needs to move some data around. Todavía no hay compatibilidad nativa con el movimiento de datos, pero podemos ejecutar algunos comandos SQL arbitrarios en cualquier punto del script.There is no native support for data motion yet, but we can run some arbitrary SQL commands at any point in our script.

  • Vamos a agregar una propiedad Post.Abstract al modelo.Let’s add a Post.Abstract property to our model. Luego, vamos a rellenar previamente el elemento Abstract de publicaciones existentes con algún texto del inicio de la columna Content.Later, we’re going to pre-populate the Abstract for existing posts using some text from the start of the Content column.
    public string Abstract { get; set; }

Vamos a usar el comando Add-Migration para permitir que Migraciones de Code First aplique scaffolding de su mejor estimación en la migración.We'll use the Add-Migration command to let Code First Migrations scaffold its best guess at the migration for us.

  • Ejecute el comando Add-Migration AddPostAbstract en la consola del Administrador de paquetes.Run the Add-Migration AddPostAbstract command in Package Manager Console.
  • La migración generada se encarga de los cambios de esquema, pero además queremos rellenar previamente la columna Abstract con los 100 primeros caracteres del contenido de cada publicación.The generated migration takes care of the schema changes but we also want to pre-populate the Abstract column using the first 100 characters of content for each post. Lo podemos hacer si recurrimos a SQL y ejecutamos una instrucción UPDATE después de agregar la columna.We can do this by dropping down to SQL and running an UPDATE statement after the column is added. (Adición en la línea 12 del código siguiente)(Adding in line 12 in the code below)
    namespace MigrationsDemo.Migrations
    {
        using System;
        using System.Data.Entity.Migrations;

        public partial class AddPostAbstract : DbMigration
        {
            public override void Up()
            {
                AddColumn("dbo.Posts", "Abstract", c => c.String());

                Sql("UPDATE dbo.Posts SET Abstract = LEFT(Content, 100) WHERE Abstract IS NULL");
            }

            public override void Down()
            {
                DropColumn("dbo.Posts", "Abstract");
            }
        }
    }

La migración editada tiene buen aspecto, así que vamos a usar Update-Database para actualizar la base de datos.Our edited migration is looking good, so let’s use Update-Database to bring the database up-to-date. Especificamos la marca –Verbose para poder ver el SQL que se ejecuta en la base de datos.We’ll specify the –Verbose flag so that we can see the SQL being run against the database.

  • Ejecute el comando Update-Database –Verbose en la consola del Administrador de paquetes.Run the Update-Database –Verbose command in Package Manager Console.

Migrar a una versión determinada (incluido un cambio a una versión anterior)Migrate to a Specific Version (Including Downgrade)

Hasta ahora siempre hemos actualizado a la migración más reciente, pero puede haber ocasiones en que quiera cambiar a una migración anterior o posterior.So far we have always upgraded to the latest migration, but there may be times when you want upgrade/downgrade to a specific migration.

Supongamos que queremos migrar la base de datos al estado en que estaba después de ejecutar la migración AddBlogUrl.Let’s say we want to migrate our database to the state it was in after running our AddBlogUrl migration. Podemos usar el modificador –TargetMigration para cambiar a esta migración anterior.We can use the –TargetMigration switch to downgrade to this migration.

  • Ejecute el comando Update-Database –TargetMigration: AddBlogUrl en la consola del Administrador de paquetes.Run the Update-Database –TargetMigration: AddBlogUrl command in Package Manager Console.

Este comando ejecuta el script Down de las migraciones AddBlogAbstract y AddPostClass.This command will run the Down script for our AddBlogAbstract and AddPostClass migrations.

Si quiere revertir a una base de datos vacía, puede usar el comando Update-Database –TargetMigration: $InitialDatabase.If you want to roll all the way back to an empty database then you can use the Update-Database –TargetMigration: $InitialDatabase command.

Obtención de un script SQLGetting a SQL Script

Si otro desarrollador quiere estos cambios en su equipo, puede sincronizar una vez que se protejan los cambios en el control de código fuente.If another developer wants these changes on their machine they can just sync once we check our changes into source control. Cuando tenga las nuevas migraciones, puede ejecutar el comando Update-Database para que los cambios se apliquen localmente.Once they have our new migrations they can just run the Update-Database command to have the changes applied locally. Pero si queremos enviar estos cambios a un servidor de prueba y, finalmente, a producción, probablemente querremos un script SQL que podamos pasar al DBA.However if we want to push these changes out to a test server, and eventually production, we probably want a SQL script we can hand off to our DBA.

  • Ejecute el comando Update-Database, pero esta vez especifique la marca –Script para que los cambios se escriban en un script en lugar de aplicarse.Run the Update-Database command but this time specify the –Script flag so that changes are written to a script rather than applied. También se especifican una migración de origen y de destino para las que generar el script.We’ll also specify a source and target migration to generate the script for. Queremos un script que abarque desde una base de datos vacía ($InitialDatabase) a la versión más reciente (migración AddPostAbstract).We want a script to go from an empty database ($InitialDatabase) to the latest version (migration AddPostAbstract). Si no se especifica una migración de destino, Migraciones usa la última migración como destino. Si no se especifica una migración de origen, Migraciones usa el estado actual de la base de datos.If you don’t specify a target migration, Migrations will use the latest migration as the target. If you don't specify a source migrations, Migrations will use the current state of the database.
  • Ejecute el comando Update-Database -Script -SourceMigration: $InitialDatabase -TargetMigration: AddPostAbstract en la consola del Administrador de paquetesRun the Update-Database -Script -SourceMigration: $InitialDatabase -TargetMigration: AddPostAbstract command in Package Manager Console

Migraciones de Code First ejecuta la canalización de migración, pero en lugar de aplicar los cambios, los escribe en un archivo .sql.Code First Migrations will run the migration pipeline but instead of actually applying the changes it will write them out to a .sql file for you. Una vez que se ha generado el script, se abre automáticamente en Visual Studio, listo para verse o guardarse.Once the script is generated, it is opened for you in Visual Studio, ready for you to view or save.

Generación de scripts idempotentesGenerating Idempotent Scripts

A partir de EF6, si especifica –SourceMigration $InitialDatabase, el script generado será "idempotente".Starting with EF6, if you specify –SourceMigration $InitialDatabase then the generated script will be ‘idempotent’. Los scripts idempotentes pueden actualizar una base de datos de cualquier versión a la versión más reciente (o la versión especificada si se usa –TargetMigration).Idempotent scripts can upgrade a database currently at any version to the latest version (or the specified version if you use –TargetMigration). El script generado incluye lógica para comprobar la tabla __MigrationsHistory y aplicar solo los cambios que no se han aplicado anteriormente.The generated script includes logic to check the __MigrationsHistory table and only apply changes that haven't been previously applied.

Actualización automática al iniciar la aplicación (inicializador MigrateDatabaseToLatestVersion)Automatically Upgrading on Application Startup (MigrateDatabaseToLatestVersion Initializer)

Si va a implementar la aplicación, es posible que quiera que actualice la base de datos automáticamente (al aplicar las migraciones pendientes) cuando se inicie.If you are deploying your application you may want it to automatically upgrade the database (by applying any pending migrations) when the application launches. Puede hacerlo si registra el inicializador de base de datos MigrateDatabaseToLatestVersion.You can do this by registering the MigrateDatabaseToLatestVersion database initializer. Un inicializador de base de datos simplemente contiene alguna lógica que se usa para asegurarse de que la base de datos se ha configurado correctamente.A database initializer simply contains some logic that is used to make sure the database is setup correctly. Esta lógica se ejecuta la primera vez que se usa el contexto dentro del proceso de aplicación (AppDomain).This logic is run the first time the context is used within the application process (AppDomain).

Se puede actualizar el archivo Program.cs, como se muestra a continuación, para establecer el inicializador MigrateDatabaseToLatestVersion para BlogContext antes de usar el contexto (línea 14).We can update the Program.cs file, as shown below, to set the MigrateDatabaseToLatestVersion initializer for BlogContext before we use the context (Line 14). Tenga en cuenta que también debe agregar una instrucción using para el espacio de nombres System.Data.Entity (línea 5).Note that you also need to add a using statement for the System.Data.Entity namespace (Line 5).

Cuando se crea una instancia de este inicializador, se debe especificar el tipo de contexto (BlogContext) y la configuración de las migraciones (Configuration): la configuración de las migraciones es la clase que se ha agregado a la carpeta Migraciones al habilitar Migraciones.When we create an instance of this initializer we need to specify the context type (BlogContext) and the migrations configuration (Configuration) - the migrations configuration is the class that got added to our Migrations folder when we enabled Migrations.

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Data.Entity;
    using MigrationsDemo.Migrations;

    namespace MigrationsDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                Database.SetInitializer(new MigrateDatabaseToLatestVersion<BlogContext, Configuration>());

                using (var db = new BlogContext())
                {
                    db.Blogs.Add(new Blog { Name = "Another Blog " });
                    db.SaveChanges();

                    foreach (var blog in db.Blogs)
                    {
                        Console.WriteLine(blog.Name);
                    }
                }

                Console.WriteLine("Press any key to exit...");
                Console.ReadKey();
            }
        }
    }

Ahora, siempre que se ejecute la aplicación, en primer lugar comprobará si la base de datos de destino está actualizada y, si no lo está, aplicará las migraciones pendientes.Now whenever our application runs it will first check if the database it is targeting is up-to-date, and apply any pending migrations if it is not.