9

我开始一个使用实体框架的新项目。我研究了如何创建数据库的选项,并发现Code-First Migrations最有意义(如果需要知道原因,请参阅底部)。代码优先迁移让我下降到任意的SQL意味着我仍然有完全的控制权。在实践中,我发现这个问题是,对于一些常见任务来说,下降到SQL似乎是非常重复的。有没有扩展代码优先迁移的好方法

出于我的目的,我不在乎迁移中的扩展是与提供者无关(我不在内部的SQL)。但是,我并不是真的在迁移框架中找到添加此类事物的良好接口或扩展点。

举一个具体的例子,假设我想为MS-SQL复制指定一个RowGuid列。每一次出现需要的

Sql(
    string.Format(
     "Alter Table {0} Alter Column {1} Add ROWGUIDCOL", 
     table, 
     column)); 

所以我写静态方法的形式摆脱了一些冗余的

Sql(MigrationHelper.SetRowGuid(table, column); 

- 或 -

MigrationHelper.SetRowGuid(Sql, table, column); //passing the Sql method 

也许可以使那些既延长DbMigration上的方法,并通过this.访问它们但是,这仍然看起来不合适:

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

this.SetRowGuid(Sql, "dbo.CustomerDirectory", "RowGuid"); 
//Custom method here because of desired naming convention of Constraint 
this.SetDefaultConstraint(Sql, "dbo.CustomerDirectory", "''"): 

这不是很糟糕,但它仍然感觉像对我来说是一个黑客。我必须重复表名,并且我需要确保生成的列名称正确。我发现表名需要重复很多,但列也是如此。然而,我真的试图添加到表名和列名都已知的情况下发生的表声明。

但是,我无法找到一个很好的扩展点来扩展流畅的接口,或者以一种感觉一致的方式扩展代码第一次迁移。我错过了什么吗?有没有人找到一个这样做的好方法?

一些理由,为什么我在这种情况下:

我不喜欢的似乎像使用普通自定义的通用解决方案属性的解决方案表明,有几个原因非映射数据库,但最强烈因为它们不会被迁移自动拾取,这意味着额外的维护。模型优先的解决方案已经不存在了,因为它们不能完全控制数据库。数据库 - 首先因为控制而吸引人;但是,它并没有Code-First Migrations提供的开箱即用变更管理功能。因此,Code-First Migrations似乎是一个赢家,因为[code-first]模型驱动的更改是自动的,这意味着只有一件事情需要维护。

+0

+1突出EFCF作为唯一的实体框架的做法,真正促进了变更控制。 – bwerks

回答

5

我找到了一个解决方案,虽然我不确定它是否好。我不得不比兔子洞更远一点,这并不是一个延伸点。

这让我写语句,如:

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

我不喜欢:

  • ,我反思私有成员,通常不会使用这样的解决方案,这一事实
  • 如果使用了列定义的“name”可选参数,那么选择列的lambda可能会返回错误的列名称。

我只在这里使用它,因为考虑:

  • 我们船的EF总成,使我们相信使用将具有这些成员之一。
  • 几个单元测试会告诉我们新版本是否会打破这些。
  • 它被迁移。
  • 我们已经获得了所有我们正在反思的信息,因此如果新版本确实破坏了这一点,我们可能会采取一些措施来替代此功能。
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; 
    } 
} 
+2

+1使用'[Pure]' – nicodemus13

0

不是一个通用的解决方案,但可以从一个抽象/接口类继承。鉴于这将需要一些代码更改,但其相当干净。

我已经使用此模式为所有表定义我的审计列(UpdatedBy,UpdateDate等)。