繁体   English   中英

是否有一种扩展Code-First迁移的好方法

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

我正在开始一个使用Entity Framework的新项目。 我已经研究了如何创建数据库的选项,并发现Code-First Migrations最有意义(如果您需要知道原因,请参阅底部)。 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.访问它们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似乎是一个赢家,因为[代码优先]模型驱动的更改是自动的,这意味着只有一件事需要维护。

虽然我不确定它是否好,但我找到了解决方案。 我不得不在兔子洞的下方走得比我想要的更远,而且它不是真正的延伸点。

它允许我编写如下语句:

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

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

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

为了捎带ravi所说的,你可以扩展DbMigration类:

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

然后,在创建迁移时,将其从DbMigration更改为ExtendedDbMigration

using System.Data.Entity.Migrations;

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM