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");
}
}