在实体框架代码优先应用程序中对抗迁移膨胀

时间:2017-05-26 18:10:34

标签: entity-framework entity-framework-6 ef-migrations

现在我们中的一些人已经在生产中运行了多年的Code First项目,并且已经积累了大量的迁移,有没有人遇到过大量的问题?是否有“太多迁移?”这样的事情。

如果是,那么补救措施是什么?一些警告: - 显然我们不能删除&重新生成生产数据库。 - 我们无法删除所有迁移,__ MigrationHistory,并创建一个新的Initial(在我的情况下),因为我们的许多迁移都有数据种子/更新,甚至可以调整生成的命令。

是否有方法/工具将迁移合并到较少的迁移中?这甚至会有所作为吗?

谢谢!

1 个答案:

答案 0 :(得分:1)

根据ISHIDA的建议,我创建了一种结合迁移的方法示例。这绝不是唯一/正确的解决方案,也不能解决迁移膨胀是否有问题的问题,但它是一个良好的开端。

为了测试这个,我有一个带有2个表的控制台应用程序,它们是:

public class Account
{
    [Required]
    [StringLength(100)]
    public string Id { get; set; }

    [Required]
    [StringLength(10)]
    public string AccountNumber { get; set; }

    public virtual List<Policy> Policies { get; set; }
}

public class Policy
{
    [Required]
    [StringLength(100)]
    public string Id { get; set; }

    [Required]
    public int PolicyNumber { get; set; }

    [Required]
    public string AccountId { get; set; }
    public virtual Account Account { get; set; }
}

有4次迁移创建了这些表,添加了数据,并将PolicyNumber的数据类型从string更改为int。假装这个程序是实时的,并且所有这些程序都在生产环境中运行。

public partial class InitialCreate : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.Accounts",
            c => new
                {
                    Id = c.String(nullable: false, maxLength: 100),
                    AccountNumber = c.String(nullable: false, maxLength: 10),
                })
            .PrimaryKey(t => t.Id);
    }

    public override void Down()
    {
        DropTable("dbo.Accounts");
    }
}

public partial class SeedAccounts : DbMigration
{
    readonly string[] accountIds = new string[] { "IdAcct101", "IdAcct102" };

    public override void Up()
    {
        Sql($"INSERT INTO Accounts (Id, AccountNumber) VALUES ('{accountIds[0]}','101')");
        Sql($"INSERT INTO Accounts (Id, AccountNumber) VALUES ('{accountIds[1]}','102')");
    }

    public override void Down()
    {
        Sql($"DELETE FROM Accounts WHERE ID = '{accountIds[0]}'");
        Sql($"DELETE FROM Accounts WHERE ID = '{accountIds[1]}'");
    }
}
}

public partial class AddPolicyTable : DbMigration
{
    public override void Up()
    {
        CreateTable(
            "dbo.Policies",
            c => new
                {
                    Id = c.String(nullable: false, maxLength: 100),
                    PolicyNumber = c.String(nullable: false, maxLength: 100),
                    AccountId = c.String(nullable: false, maxLength: 100),
                })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true)
            .Index(t => t.AccountId);

    }

    public override void Down()
    {
        DropForeignKey("dbo.Policies", "AccountId", "dbo.Accounts");
        DropIndex("dbo.Policies", new[] { "AccountId" });
        DropTable("dbo.Policies");
    }
}

public partial class ChangeAndSeedPolicies : DbMigration
{
    readonly string[] accountIds = new string[] { "IdAcct101", "IdAcct102" };
    readonly string[] policyIds = new string[] { "IdPol101a", "IdPol101b", "IdPol102a" };

    public override void Up()
    {
        AlterColumn("dbo.Policies", "PolicyNumber", c => c.Int(nullable: false));

        Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[0]}', '{accountIds[0]}', '10101')");
        Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[1]}', '{accountIds[0]}', '10102')");
        Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[2]}', '{accountIds[1]}', '10201')");

    }

    public override void Down()
    {
        Sql($"DELETE FROM Policies WHERE ID = '{policyIds[0]}'");
        Sql($"DELETE FROM Policies WHERE ID = '{policyIds[1]}'");
        Sql($"DELETE FROM Policies WHERE ID = '{policyIds[2]}'");

        AlterColumn("dbo.Policies", "PolicyNumber", c => c.String(nullable: false, maxLength: 100));
    }
}

以下是项目主要代码:

        using (var dc = new DataContext())
        {
            foreach (var account in dc.Accounts.OrderBy(q => q.AccountNumber).ToList())
            {
                Console.WriteLine("Account " + account.AccountNumber);

                foreach (var policy in account.Policies)
                    Console.WriteLine("    Policy " + policy.PolicyNumber);
            } 
        }

DataContext类:

public class DataContext : DbContext
{
    public DataContext() : base("DefaultConnection") { }

    public DbSet<Account> Accounts { get; set; }
    public DbSet<Policy> Policies { get; set; }
}

输出结果为:

Account 101
    Policy 10101
    Policy 10102
Account 102
    Policy 10201

很简单。现在我想将这些迁移合并为一个。记住:

  • 我们不想放弃&amp; rescaffold因为生产会有数据 除了迁移添加的内容
  • 以前的迁移必须能够重新运行以进行集成测试&amp;新环境

这些是我遵循的步骤:

  • 备份您将要运行的任何环境。
  • 创建一个新的迁移(它应该是空白的,因为没有任何变化)
  • 在Package Console Manager(PMC)中,运行&#34; update-database&#34;创建__MigrationHistory记录
  • 此时验证应用是否正常运行
  • 将所有Up方法从旧迁移复制到新迁移
  • 将旧迁移中的所有Down方法复制到新的以相反顺序
  • 此时验证应用是否正常运行(应检测不需要新的迁移)
  • 删除所有旧迁移
  • 删除所有旧的__MigrationHistory记录(只保留新记录)
  • 此时验证应用是否正常运行

要验证新迁移确实完成了旧迁移所做的一切(对于新环境或测试),只需删除数据库中的所有表(包括__MigrationHistory),运行&#34; update-database&#34;在PMC中,看它是否运行。

这就是我的新迁移:

public partial class CombinedMigration : DbMigration
{
    readonly string[] accountIds = new string[] { "IdAcct101", "IdAcct102" };
    readonly string[] policyIds = new string[] { "IdPol101a", "IdPol101b", "IdPol102a" };

    public override void Up()
    {
        CreateTable(
            "dbo.Accounts",
            c => new
            {
                Id = c.String(nullable: false, maxLength: 100),
                AccountNumber = c.String(nullable: false, maxLength: 10),
            })
            .PrimaryKey(t => t.Id);

        Sql($"INSERT INTO Accounts (Id, AccountNumber) VALUES ('{accountIds[0]}','101')");
        Sql($"INSERT INTO Accounts (Id, AccountNumber) VALUES ('{accountIds[1]}','102')");

        CreateTable(
            "dbo.Policies",
            c => new
            {
                Id = c.String(nullable: false, maxLength: 100),
                PolicyNumber = c.String(nullable: false, maxLength: 100),
                AccountId = c.String(nullable: false, maxLength: 100),
            })
            .PrimaryKey(t => t.Id)
            .ForeignKey("dbo.Accounts", t => t.AccountId, cascadeDelete: true)
            .Index(t => t.AccountId);

        AlterColumn("dbo.Policies", "PolicyNumber", c => c.Int(nullable: false));

        Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[0]}', '{accountIds[0]}', '10101')");
        Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[1]}', '{accountIds[0]}', '10102')");
        Sql($"INSERT INTO Policies (Id, AccountId, PolicyNumber) VALUES ('{policyIds[2]}', '{accountIds[1]}', '10201')");
    }

    public override void Down()
    {
        // Each prior "Down" section was added in reverse order.

        Sql($"DELETE FROM Policies WHERE ID = '{policyIds[0]}'");
        Sql($"DELETE FROM Policies WHERE ID = '{policyIds[1]}'");
        Sql($"DELETE FROM Policies WHERE ID = '{policyIds[2]}'");

        AlterColumn("dbo.Policies", "PolicyNumber", c => c.String(nullable: false, maxLength: 100));

        DropForeignKey("dbo.Policies", "AccountId", "dbo.Accounts");
        DropIndex("dbo.Policies", new[] { "AccountId" });
        DropTable("dbo.Policies");

        Sql($"DELETE FROM Accounts WHERE ID = '{accountIds[0]}'");
        Sql($"DELETE FROM Accounts WHERE ID = '{accountIds[1]}'");

        DropTable("dbo.Accounts");
    }
}

警告:如果您的任何迁移具有创建新DC的.NET代码并执行某些db更新,那么在迁移组合时这些可能无效。例如,如果迁移1添加了Account表,并且迁移2使用.NET代码将记录插入到Account中,则它将在组合迁移中崩溃,因为尚未创建Account技术。用Sql(&#39; INSERT INTO ...&#34;)语句替换这些语句将解决这个问题。