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"); }}
Not a generic solution, but you can inherit from an abstract/interface class. Given this would need some code changes but its reasonably clean.
I have used this pattern to define my audit columns (UpdatedBy, UpdateDate etc.,) for all the tables.