Is there a good way to extend the Code-First Migrations

I have found a solution though I am not sure if it is good. I had to go a little farther down the rabbit hole than I wanted to get it, and it is not really an extension point.

It allows me to write statements such as:

CreateTable(
    "dbo.CustomerDirectory",
     c => new
        {
            Uid = c.Int(nullable: false),
            CustomerUid = c.Int(nullable: false),
            Description = c.String(nullable: false, maxLength: 50, unicode: false),
            RowGuid = c.Guid(nullable: false),
        })
    .PrimaryKey(t => t.Uid)
    .ForeignKey("dbo.Customer", t => t.CustomerUid)
      //SqlValue is a custom static helper class
    .DefaultConstraint( t => t.Description, SqlValue.EmptyString)
      //This is a convention in the project
      //Equivalent to
      //  .DefaultConstraint( t => t.RowGuid, SqlValue.EmptyString)
      //  .RowGuid( t => t.RowGuid )
    .StandardRowGuid()
      //For one-offs
    .Sql( tableName => string.Format( "ALTER TABLE {0} ...", tableName" );

I do not like:

  • The fact that I am reflecting on private members, and normally would not use such a solution
  • That the lambda to select a column could return the wrong column name if the "name" optional parameter of the column definition was used.

I am only considering using it here because:

  • We ship the EF assembly so we are sure the one used will have these members.
  • A couple unit tests will tell us if a new version will break these.
  • It is isolated to migrations.
  • We have all the information we are reflecting to get, so if a new version does break this, we could put in place a hack to replace this functionality.
internal static class TableBuilderExtentions
{
    internal static TableBuilder<TColumns> Sql<TColumns>(
        this TableBuilder<TColumns> tableBuilder,
        Func<string, string> sql,
        bool suppressTransaction = false,
        object anonymousArguments = null)
    {
        string sqlStatement = sql(tableBuilder.GetTableName());

        DbMigration dbMigration = tableBuilder.GetDbMigration();
        Action<string, bool, object> executeSql = dbMigration.GetSqlMethod();

        executeSql(sqlStatement, suppressTransaction, anonymousArguments);

        return tableBuilder;
    }

    [Pure]
    private static DbMigration GetDbMigration<TColumns>(this TableBuilder<TColumns> tableBuilder)
    {
        var field = tableBuilder.GetType().GetField(
            "_migration", BindingFlags.NonPublic | BindingFlags.Instance);
        return (DbMigration)field.GetValue(tableBuilder);
    }

    /// <summary>
    ///   Caution: This implementation only works on single properties.
    ///   Also, coder may have specified the 'name' parameter which would make this invalid.
    /// </summary>
    private static string GetPropertyName<TColumns>(Expression<Func<TColumns, object>> someObject)
    {
        MemberExpression e = (MemberExpression)someObject.Body;

        return e.Member.Name;
    }

    [Pure]
    private static Action<string, bool, object> GetSqlMethod(this DbMigration migration)
    {
        MethodInfo methodInfo = typeof(DbMigration).GetMethod(
            "Sql", BindingFlags.NonPublic | BindingFlags.Instance);
        return (s, b, arg3) => methodInfo.Invoke(migration, new[] { s, b, arg3 });
    }

    [Pure]
    private static string GetTableName<TColumns>(this TableBuilder<TColumns> tableBuilder)
    {
        var field = tableBuilder.GetType().GetField(
            "_createTableOperation", BindingFlags.NonPublic | BindingFlags.Instance);

        var createTableOperation = (CreateTableOperation)field.GetValue(tableBuilder);
        return createTableOperation.Name;
    }
}

To piggyback on what ravi said, you could extend the DbMigration class:

using System;
using System.Collections.Generic;
using System.Data.Entity.Migrations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

public abstract class ExtendedDbMigration : DbMigration
{
    public void DoCommonTask(string parameter)
    {
        Sql("** DO SOMETHING HERE **");
    }

    public void UndoCommonTask(string parameter)
    {
        Sql("** DO SOMETHING HERE **");
    }
}

Then, when you create a migration, change it from DbMigration to ExtendedDbMigration:

using System.Data.Entity.Migrations;

public partial class some_migration : ExtendedDbMigration
{
    public override void Up()
    {
        DoCommonTask("Up");
    }

    public override void Down()
    {
        UndoCommonTask("Down");
    }
}