c# - 有没有一种扩展代码优先迁移的好方法

标签 c# sql entity-framework entity-framework-5 entity-framework-migrations

我正在开始一个使用 Entity Framework 的新项目。我研究了有关如何创建数据库的选项,发现代码优先迁移最有意义(如果您需要知道原因,请参阅底部)。 Code-First 迁移让我可以使用任意 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", "''" ):

这不是很糟糕,但对我来说仍然感觉像是一个 hack。我必须重复表名,并且需要确保生成的列名正确。我发现表名需要重复很多次,但列也是如此。然而,我真正想做的是添加到刚刚发生的表名和列名都已知的表声明中。

但是,我找不到一个好的扩展点来扩展流畅的界面或以其他方式以感觉一致的方式扩展代码优先迁移。我错过了什么吗?有没有人找到这样做的好方法?

关于我为什么处于这种情况的一些理由:

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

最佳答案

我找到了一个解决方案,但我不确定它是否好。我不得不在兔子洞里走得比我想要的更远一点,它并不是真正的扩展点。

它允许我编写如下语句:

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

我不喜欢:

  • 我正在反射(reflection)私有(private)成员的事实,通常不会使用这样的解决方案
  • 如果使用了列定义的“名称”可选参数,则用于选择列的 lambda 可能会返回错误的列名。

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

  • 我们运送 EF 程序集,因此我们确信使用的程序集将包含这些成员。
  • 一些单元测试会告诉我们新版本是否会打破这些。
  • 它与迁移隔离。
  • 我们拥有我们要反射(reflect)的所有信息,因此如果新版本确实打破了这一点,我们可以实现黑客攻击来替换此功能。
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;
    }
}

关于c# - 有没有一种扩展代码优先迁移的好方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12418144/

相关文章:

c# - Javascript 的 Onfocus() 事件不计算文本框值

c# - 如何使用Azure SDK获取Azure VM的公共(public)IP

php - 我想在 MySQL 数据库中有一列自动计算 DATETIME 差异

c# - 检查 C# 代码混淆的效率

c# - 如何在 ASP.NET MVC 中更轻松地访问我的自定义 IPrincipal?

c# - ETL 处理设计和性能

sql - 参数化 SQL 查询中变量的影响

c# - 在扩展 Roles 表后,实体类型 IdentityRole 不是当前上下文模型的一部分

c# - 无法在对象 'dbo.Account' 中插入重复键。重复键值为 (2)

c# - 如何将数据播种到 List<String> 模型成员?