Entity Framework 4.3 Usando Migrations

En la versión de Entity Framework 4.3.1 que se ha revisado recientemente en Abril 2012 se incorpora por primera vez el módulo Migrations que permite mantener el esquema de la base de datos sincronizada con los cambios en nuestro modelo cuando usamos EF Code First.

EF 4.3.1 sólo esta disponible desde Nuget y debemos instalarlo usando el Nuget package manager.

Cuando instalamos EF 4.3.1 disponemos de nuevos tags de configuración para establecer el comportamiento de EF de forma declarativa. Esto es posible añadiendo nueva sección de configuración para entity framework que habilita estos tags:

image

Y que debemos asegurarnos de incluir en nuestros archivos de configuración si queremos usar la configuración de EF disponible en esta versión.

EF 4.3. Migrations incorpora 2 maneras de mantener sincronizada la base de datos con nuestro modelo: una primera de forma automatizada cada vez que se inicia la aplicación oy otra de forma manual ejecutando los comandos powershell que se instalan junto con EF 4.3.1

http://coding.abel.nu/2012/03/ef-migrations-command-reference/

Para poder usar Migrations con el proyecto de nuestro modelo de negocio, es necesario habilitar Migrations para el proyecto.

Comenzaremos abriendo la ventana del  “Package Manager Console” y ejecutamos el comando “Enable-Migrations”. Es muy importante seleccionar el proyecto sobre el que queremos habilitar Migrations:

 

image

Esto nos creara en nuestro proyecto una carperta “Migrations” con una clase “Configuration” en la que podremos  definir algunas de las características de comportamiento de Migrations como la activación de las actualizaciones automáticas, o, podremos incluir código especializado para el mapeo de nuestro modelo sobre la base de datos usando Fluent API :

image

image

En este post no centraremos en la capacidad que nos ofrece Migrations para mantener nuestra base de datos y nuestro modelo sincronizados  de forma manual. Para esto desactivaremos en primer lugar las actualizaciones automáticas como en el siguiente ejemplo:

   1: namespace Events.Data.Migrations

   2: {

   3:     using System;

   4:     using System.Data.Entity;

   5:     using System.Data.Entity.Migrations;

   6:     using System.Linq;

   7:  

   8:     internal sealed class Configuration : DbMigrationsConfiguration<Events.Data.EventContext>

   9:     {

  10:         public Configuration()

  11:         {

  12:             AutomaticMigrationsEnabled = false;

  13:         }

  14:  

  15:         protected override void Seed(Events.Data.EventContext context)

  16:         {

  17:             //  This method will be called after migrating to the latest version.

  18:  

  19:             //  You can use the DbSet<T>.AddOrUpdate() helper extension method 

  20:             //  to avoid creating duplicate seed data. E.g.

  21:             //

  22:             //    context.People.AddOrUpdate(

  23:             //      p => p.FullName,

  24:             //      new Person { FullName = "Andrew Peters" },

  25:             //      new Person { FullName = "Brice Lambson" },

  26:             //      new Person { FullName = "Rowan Miller" }

  27:             //    );

  28:             //

  29:         }

  30:     }

  31: }

 

Las migraciones automáticas las veremos en un siguiente post, pero considero que es mejor controlar las migraciones de forma manual y por eso comenzamos revisando este alternativa en primer lugar.

Crear una migración

Una vez habilitado MIgrations y una vez definido nuestro modelo (al menos en su mayor parte), podemos proceder  a preparar una migración. Para preparar una migración disponemos del comando:

Add-Migration:

Lo mas probable cuando usanmos code first es que partamos de una base de datos vacía, en cuyo caso “Add-Migration”  generará el código necesario para crear nuestras tablas completas con sus relaciones, claves, foreingKeys, etc… y creará también el snapshot que se almacenará en la tabla  (_MigrationHistory) que mantiene la historia de las migraciones.

Cada vez que se ejecuta el comando “Add-Migration” se compara el modelo con el último snapshot para determinar los cambios a realizar.

El comando “Add-Migration” necesita que le especifiquemos un nombre de migración, EF generará un fichero con el código necesario para realizar los cambios en la base de datos. El nombre de este fichero será el nombre que le hayamos proporcionado como parametro en el comando “Add-Migration” con un prefijo que la agrega EF basado en un timestamp, de forma que cada migración tenga un nombre único que la pueda identificar. Este comando necesita que le indiquemos la cadena de conexión que se utilizará para la conexión de la migración, hay varias formas de hacerlo pero en este ejemplo me ha decantado por usar el parametro:

–StartupProjectName = ‘Events.Data’

Que apunta a un proyecto con un app.Config que contiene la cadena de conexión para mi base de datos de SQL Server CE 4.0:

image

 

image

image

Si la base de datos está vacia, el código generado será parecido al siguiente:

   1: namespace Events.Data.Migrations

   2: {

   3:     using System.Data.Entity.Migrations;

   4:     

   5:     public partial class MyMigration1 : DbMigration

   6:     {

   7:         public override void Up()

   8:         {

   9:             CreateTable(

  10:                 "Events",

  11:                 c => new

  12:                     {

  13:                         Id = c.Int(nullable: false, identity: true),

  14:                         Name = c.String(nullable: false, maxLength: 4000),

  15:                         EventDate = c.DateTime(nullable: false),

  16:                         Title = c.String(maxLength: 30),

  17:                         State = c.Int(nullable: false),

  18:                     })

  19:                 .PrimaryKey(t => t.Id);

  20:             

  21:             CreateTable(

  22:                 "Assistants",

  23:                 c => new

  24:                     {

  25:                         Id = c.Int(nullable: false, identity: true),

  26:                         Alias = c.String(maxLength: 4000),

  27:                         Contact_Id = c.Int(nullable: false),

  28:                         Event_Id = c.Int(),

  29:                     })

  30:                 .PrimaryKey(t => t.Id)

  31:                 .ForeignKey("Contacts", t => t.Contact_Id, cascadeDelete: true)

  32:                 .ForeignKey("Events", t => t.Event_Id)

  33:                 .Index(t => t.Contact_Id)

  34:                 .Index(t => t.Event_Id);

  35:             

  36:             CreateTable(

  37:                 "Contacts",

  38:                 c => new

  39:                     {

  40:                         Id = c.Int(nullable: false, identity: true),

  41:                         Name = c.String(maxLength: 4000),

  42:                         Email = c.String(maxLength: 4000),

  43:                         Phone = c.String(maxLength: 4000),

  44:                     })

  45:                 .PrimaryKey(t => t.Id);

  46:             

  47:             CreateTable(

  48:                 "Speakers",

  49:                 c => new

  50:                     {

  51:                         Id = c.Int(nullable: false, identity: true),

  52:                         Alias = c.String(maxLength: 4000),

  53:                         Contact_Id = c.Int(nullable: false),

  54:                         Event_Id = c.Int(),

  55:                     })

  56:                 .PrimaryKey(t => t.Id)

  57:                 .ForeignKey("Contacts", t => t.Contact_Id, cascadeDelete: true)

  58:                 .ForeignKey("Events", t => t.Event_Id)

  59:                 .Index(t => t.Contact_Id)

  60:                 .Index(t => t.Event_Id);

  61:             

  62:             CreateTable(

  63:                 "Resources",

  64:                 c => new

  65:                     {

  66:                         Id = c.Int(nullable: false, identity: true),

  67:                         Url = c.String(maxLength: 4000),

  68:                         Size = c.Long(nullable: false),

  69:                         BinaryData = c.Binary(maxLength: 4000),

  70:                         Event_Id = c.Int(),

  71:                     })

  72:                 .PrimaryKey(t => t.Id)

  73:                 .ForeignKey("Events", t => t.Event_Id)

  74:                 .Index(t => t.Event_Id);

  75:             

  76:         }

  77:         

  78:         public override void Down()

  79:         {

  80:             DropIndex("Resources", new[] { "Event_Id" });

  81:             DropIndex("Speakers", new[] { "Event_Id" });

  82:             DropIndex("Speakers", new[] { "Contact_Id" });

  83:             DropIndex("Assistants", new[] { "Event_Id" });

  84:             DropIndex("Assistants", new[] { "Contact_Id" });

  85:             DropForeignKey("Resources", "Event_Id", "Events");

  86:             DropForeignKey("Speakers", "Event_Id", "Events");

  87:             DropForeignKey("Speakers", "Contact_Id", "Contacts");

  88:             DropForeignKey("Assistants", "Event_Id", "Events");

  89:             DropForeignKey("Assistants", "Contact_Id", "Contacts");

  90:             DropTable("Resources");

  91:             DropTable("Speakers");

  92:             DropTable("Contacts");

  93:             DropTable("Assistants");

  94:             DropTable("Events");

  95:         }

  96:     }

  97: }

 

En cambio si la base de datos existe y hemos añadido un nuevo campo a nuestro modelo que no existía previamente en el esquema actual, podría ser como el siguiente:

EF crear un archivo de migración con 2 métodos: uno con el código para aplicar los nuevos cambios y otro con el código necesario para deshacer estos cambios, más adelante veremos como podemos hacer esto con el comando Update-Database:

 

   1: namespace Events.Data.Migrations

   2: {

   3:     using System.Data.Entity.Migrations;

   4:     

   5:     public partial class MyMigration2 : DbMigration

   6:     {

   7:         public override void Up()

   8:         {

   9:             AddColumn("Assistants", "Name", c => c.String(maxLength: 4000));

  10:         }

  11:         

  12:         public override void Down()

  13:         {

  14:             DropColumn("Assistants", "Name");

  15:         }

  16:     }

  17: }

 

 

Cada vez que ejecutamos el comando Add-Migration EF nos agrega un nuevo archivo basado en la diferencias encontradas con el snapshot anterior.

image

Aplicar una migración

Una vez creada la migración ha llegado el momento de actualizar la base de datos con estos cambios. Para esto disponemos del comando:

Update-Database

Este comando se encarga de aplicar los cambios generados a nuestra base de datos. De nuevo nos encontramos con 2 escenarios posiobles: el primero cuando la base de datos está vacía y el segundo cuandoi la base de datos ya ha sido inicializada previamente.

Si la base de datos está vacía, el comando Update-Database creará la tabla (_MigrationHistory) además de aplicar los cambios en la bse de datos. También guardará el snapshot generado por el comando Add-Migration en la tabla _MigrationHistory.

Igual que antes, especificamos un proyecto con el archivo de configuración que contiente la cadena de conexión:

image

Y nuestra base de datos será actualizada según los cambios especificados en el archivo de migración.

Cada vez que ejecutamos un comando “Add-Migration” EF crea un nuevo archivo de migración con las diferencias encontradas con el estado anterior de la base de datos. El comando Update-Database también nos permite retroceder a un estado anterior de una migración específica. Lo podemos hacer mediante el parametro –TargetMigration especificando el nombre de una migración existente (mucho mejor si es el nombre completo incluyendo el timestamp):

-TargetMigration: ‘MyMigration1’ o  -TargetMigration: ‘201205021006543_MyMigration1’

Los archivos de migración nos permite navegar hacia adelante y hacia atrás en el estado de nuestra base de datos:

image

no los borréis salvo que ya no sea necesario mantener la posibilidad de volver a un estado anterior.

Se me han quedado algunas cosas que trataré de abordar en siguientes posts, pero espero que los conceptos básicos de como usar Migrations de forma manual y controlada hayan quedado  claros y sirvan de ayuda inicial.

He creado un pequeño modelo para que podáis hacer pruebas sin necesidad de crearos  uno por vuestra cuenta. Esta adjunto al post.

En el siguiente post veremos como exponer un modelo creado con Code First y un contexto DbContext mediante WCF Data Services 5.0.

Os recomiendo los libros de Julie Lerman sobre Code First y DbContext de la editorial O’Reilly. No deben faltar en vuestra biblioteca.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *