Desde hace tiempo en el equipo que trabajando con Entity Framework Migrations (post 1, post 2) echábamos de menos la posibilidad de extender el conjunto de operaciones por defecto y crear las nuestras.

Así que como ahora el desarrollo de Entity Framework es ahora open source, que mejor que crear un pull request para cambiarlo.

¿Qué opciones había hasta ahora?

En la versión actual de Entity Framework, hasta ahora, si queríamos salirnos del conjunto de operaciones por defecto de Entity Framework, sólo nos quedaba la opción de utilizar la operación Sql. Esta operación nos permitía ejecutar cualquier sentencia SQL durante la migración, pero nos obligaba a salirnos de la API de las migraciones y llenaba la migración con strings, lo que no dejaba muy limpio nuestro código.

Aunque como podeis ver en este post existe la posibilidad de mitigar esta situación en cierta manera, seguía siendo un workaround y no una solución completa.

El escenario en EF6 Alpha 3

Con esta modificación que se ha incluido en la Alpha 3, ahora podemos crear nuestras propias operaciones de tal forma que se integren en la API que teníamos en las migraciones. Por ejemplo si quisiéramos hacer una operación que insertase datos en una tabla, podríamos extender de la clase MigrationOperation:

using System.Data.Entity.Migrations.Model;

namespace MyCustomMigrations
{
  public class InsertDataOperation : MigrationOperation
  {
    public InsertDataOperation(string table, object data)
      : base(null)
    {
      Table = table;
      User = user;
    }

    public string Table { get; private set; }
    public object Data { get; private set; }

    public override bool IsDestructiveChange
    {
      get { return false; }
    }
  }
}

Con esta clase InsertOperation, acabamos de crear la definición de nuestra operación. Para utilizarlo en nuestras migraciones podemos crear un método de extensión, para que este disponible como cualquier otra operación de la API de migraciones:

using System.Data.Entity.Migrations;
using System.Data.Entity.Migrations.Infrastructure;

namespace MyCustomMigrations
{
  public static class MigrationExtensions
  {
    public static void Insert(this DbMigration migration, string table, object data)
    {
      ((IDbMigration)migration)
        .AddOperation(new InsertOperation(table, data));
    }
  }
}

Si nos fijamos en el código anterior, veremos que estamos utilizando la interfaz IDbMigration. Esta interfaz nos permite acceder al método AddOperation, que no esta disponible directamente en la clase DbMigration para evitar su uso de forma directa en las migraciones, es una forma de motivar a mantener el diseño de la API de migraciones.

Por último, para que EntityFramework reconozca la nueva operación y sepa que hacer con ella (hasta ahora solo hemos creado su definición), tenemos que crear nuestro propio generador de sql. Para que resulte más sencillo, simplemente podemos extender el generador por defecto que ya viene en EntityFramework, SqlServerMigrationGenerator:

using System.Data.Entity.Migrations.Model;
using System.Data.Entity.Migrations.Sql;

namespace MyCustomMigrations
{
  public class CustomSqlServerMigrationSqlGenerator : SqlServerMigrationSqlGenerator
  {
    protected override void Generate(MigrationOperation migrationOperation)
    {
       if (operation != null)
       {
          dynamic currentOperation = migrationOperation;

          CustomGenerate(currentOperation);
       }
    }

    protected virtual void CustomGenerate(InsertOperation insertOperation)
    {
        using (var writer = Writer())
        {
          //Método eliminado para brevedad
          FieldDefinition fieldDefinition = GetFieldsFromAnonymousObject(inserOperation.Data);

          writer.WriteLine(
            "INSERT INTO {0} ({1}) VALUES ({2})",
            insertOperation.Table,
            fieldDefinition.FieldNames,
            fieldDefinition.FieldValues);

          Statement(writer);
        }
      }
    }

  }
}

Tan sólo nos queda utilizarlo en nuestra migración:

public class AddCustomerData : DbMigration
{
   public void Up()
   {
      Insert("Customers", new { Name="NewCustomer", Address="Elmo Street 1", Phone="678439832" });
   }
}

Y listo ya tenemos una operación que nos permite insertar datos de forma limpia en nuestras migraciones.

Cómo hay muchísimas posibilidades, y muchas de ellas nos interesan, según vaya creando nuevas operaciones las iré comentado en el blog.

Espero que os guste y lo podais utilizar en vuestros proyectos!