此文章由机器翻译。

数据点

代码的第一次迁移之谜:解决

Julie Lerman

Julie Lerman一位朋友最近问我一个问题关于实体框架 (EF) 代码第一次迁移,把我弄糊涂,差不多一样困惑着他 — — 但出于不同的原因。

对话是这样的:

"嗨朱莉,为什么我的 Azure Web 站点会自动迁移我的数据库?"

"是你确定你没有自动迁移设置为 true 吗?它是假的默认情况。

"绝对不会。我并不改变该设置。

"迁移初始值设定项怎么样?那能做它,太好了"。

让我在这里打断的话,请确保你真正了解,初始值设定项。如果迁移是显式的东西你创建和执行在程序包管理器控制台中,为什么会有一个初始值设定项吗?

第一个初始值设定项的代码

第一次带有三个初始值设定项: 启动代码

CreateDatabaseIfNotExists:这将创建一个,如果数据库找不到它,也结束了它的工作。如果您修改数据库时,数据库不会重新创建或迁移。只是,你会建议你使用不同的方法来管理数据库异常。具体地说,这项建议将使用迁移。

DropCreateDatabaseAlways:这种集成测试的理想选择。它将删除现有的数据库和运行您已经添加在任何种子逻辑的从头重新创建它。 为集成测试,它是一个伟大的方式,总是有一个一致的数据库。

DropCreateDatabaseIfModelChanges:有迁移之前,这是十分有用的选项,在发展中,如果你不需要保留的数据或任何其他您可能已更改,如添加索引或触发器的数据库中的架构。

为完整起见,我想还指出在 EF6,我们得到了 NullDatabaseInitializer 为那些人发现很难记住 (或发现) 其他语法用于禁用初始化:SetDatabaseInitialier < MyContext > (null)。

然后迁移到达 EF4.3。 迁移并不是活跃在您的数据层,直到您显式启用他们从程序包管理器控制台窗口通过键入启用迁移。这确实就是将与配置类的一个新的文件夹添加到您的项目。如果启用过程发现绑在您的上下文的现有数据库,它将创建初始迁移类。这是为了确保当您执行迁移,代码优先不会尝试创建已存在的数据库对象。

事实上,你会看到一条说明性消息在控制台中的启用迁移后如中所示图 1

对由初始值设定项创建的现有数据库的启用迁移反应
图 1:对由初始值设定项创建的现有数据库的启用迁移反应

如果你看看在你的数据库中,在新的 __MigrationHistory 文件中,您将看到反映已经在数据库上执行初始迁移表中的行。

它是重要的是理解还有没有别的 — — 没有添加到您的应用程序配置或任何其他隐藏的移徙拼图板件。配置类是至关重要的虽然。

这里是从在启用 migra 的时的默认实现­,关于我的数据层,在那里我有 DbContext 类称为的 MigratoryBirdsContext:

internal sealed class Configuration : 
  DbMigrationsConfiguration<MigratoryBirdsContext>
  {
    public Configuration()
    {
      AutomaticMigrationsEnabled = false;
      ContextKey = "DataLayer.MigratoryBirdsContext";
    }
    protected override void Seed(MigratoryBirdsContext context)
    {
    }
  }

因为我会显式使用此类进一步,就会将其签名改成公。对于那些你只需扫描通过代码,这里是进行新的声明:

public class Configuration : DbMigrationsConfiguration<MigratoryBirdsContext>

迁移可以自动运行意义会发现模型变化和迁移对应变化将创建并在数据库上执行。所有这一切都发生在运行时期间数据库初始化。自动迁移是方便简单的应用程序,但你很少控制他们,我通常不推荐使他们。在代码第一切换默认值为 false 时,我很高兴。

在运行时,代码第一次看起来绑一个上下文,当这种情况下通过其数据的 DbMigrationsConfiguration 类­初始化过程的基础。如果找到此类,则任何显式调用或设置要将数据库初始化设置为原来的三个初始值设定项的任何 — — 其工作是创建或删除并创建数据库 — — 将创建使用迁移的数据库。这是一个为 EF6.1 的新模式。 在此之前,那些三个初始值设定项将会使用他们自 EF4.3,总是已经使用了推断模型和配置数据库架构的初始化逻辑。这种变化对 EF6.1 是方便使用的开发人员,像我一样,DropCreateDatabase­总是在他们的测试中的初始值设定项。现在,它将删除数据库,但使用我的迁移,然后重新创建它。如果我已经自定义迁移课,我会到我的数据库,以及应用该自定义。

它也是重要的是要时刻牢记,默认初始值设定项 — — CreateDatabaseIfNotExists — — 还将使用迁移创建一个数据库,如果迁移存在,但数据库不会。

所以,现在可以看看第四个初始值设定项,MigrateDatabaseToLatestVersion,这是的一个当他正要拿到的神秘迁移源在他的 Azure 网站上关于问我的朋友。由于迁移在 EF4.3 中引入此初始值设定项已经存在。

名称,MigrateDatabaseToLatestVersion,似乎表明它将工作就像自动迁移,但只有单一的、 重大的差别。而由现有迁移类触发此初始值设定项任务由改变在模型中,触发自动迁移。

要看到这是如何工作的让我们开始与默认状态后您已经启用迁移。在剧中还有没有 MigrateDatabaseToLatestVersion 初始值设定项和 AutomaticMigrations 设置为 false。

这里有个基本集成测试在 MigratoryBirdsContext 上执行一个简单的查询:

[TestMethod]
public void CanInitializeDatabase() {
  using (var context = new MigratoryBirdsContext())
  {
    context.MigratoryBirds.ToList();
  }
  Assert.Inconclusive("If we're here, it did not crash");
}

如果要更改类在我的模型之一,并在运行此测试会引发一个异常,告诉我,"自数据库创建以来,支持 MigratoryBirdsContext 上下文模型已经改变了。考虑使用代码第一次迁移来更新数据库"。

即使已启用迁移和我手边的 MigrationsConfiguration 类,那所有的 EF 知道是当前模型不匹配 MigrationHistory 表中存储的最新版本。在这一点上,我可以使用添加迁移和更新数据库来解决该问题,但我想告诉你,初始值设定项是如何工作的会让一个不同的路径:我将修改测试以便在初始值设定项。如果你不熟悉它的构造函数语法,你必须指向配置文件的目标 DbContext,像这样:

[TestMethod]
public void CanInitializeDatabase()
{
  Database.SetInitializer(
    new MigrateDatabaseToLatestVersion<MigratoryBirdsContext, 
    DataLayer.Migrations.Configuration>());
  using (var context = new MigratoryBirdsContext())
  {
    context.MigratoryBirds.ToList();
  }
  Assert.Inconclusive("If we're here, it did not crash");
}

再次运行测试引发另一个异常,和其消息突出显示此初始值设定项和自动迁移之间的区别:"无法更新数据库,以匹配当前的模型,因为有挂起的更改并自动迁移已停用。写入挂起模式更改基于代码的迁移或启用自动迁移。"

AutomaticMigrations 会注意的区别,创建迁移和更新数据库 — — 所有飞和安静。然而,AutomaticMigrations 并不存在任何迁移,它将创建为您的项目中的类。虽然此初始值设定项注意到变化,它不会自动创建为您迁移。它可以只使用现有的迁移。

如果你没有手动之前进行迁移,它是一个两步过程:

首先,用添加迁移命令创建迁移。这将会读取您的模型和查询的数据库中迁移历史记录表,最新的条目,然后比较这两个。如果您的模型已更改,因为它最后存储,它将创建一个新的迁移类迁移的数据库的当前架构的指示,以配合您的模型中的更改。接下来,你在数据库上执行,移徙 (或迁移命令语法的基础的某种组合)。键入程序包管理器控制台的基本命令是更新数据库。

不是很多开发人员意识到可以执行更新数据库,以编程方式,以及。我知道它是可能的但没这么做很久了,我不得不去看看我在 Pluralsight 上的代码第一次迁移过程中的详细信息。迁移 API 有一个 DbMigrator 类使您可以使用相同的选项,您可以从更新数据库命令访问控制台中更新数据库。

关键步骤是要实例化您的迁移配置文件 ; 实例化 API DbMigrator 类,通过实例迁移配置类 ; 然后调用更新:

var migrator = new DbMigrator(new DbMigrationsConfiguration());
migrator.Update();

而这是在 MigrateDatabaseToLatestVersion 初始值设定项以您的名义做什么。迁移需要已经存在于您的项目 (或您已编译的程序集) 和初始值设定项将它们在数据库上为您在运行时执行。

我已经对我的域类的改变,我现在会往前走并添加迁移命令中添加-迁移捷流。'这里是我获取生成的类:

public partial class JetStream : DbMigration
{
  public override void Up()
  {
    AddColumn("dbo.MigratoryBirds", "DependentOnJetStream", c =>
      c.Boolean(nullable: false));
  }
  public override void Down()
  {
    DropColumn("dbo.MigratoryBirds", "DependentOnJetStream");
  }
}

现在这个班是项目的我的一部分。当我再次运行测试时,在那里我已经建立了迁移-­数据库初始值设定项,测试不会引发异常。我探查器显示我之前选择被执行,数据库表模式进行更改以在新的 DependentOnJetStream 属性中添加。

我没有得到我喜欢的 DropCreateDatabaseAlways 初始值设定项的这种模式是新版本的数据库的集成测试。要做到这一点,我不得不前播种的数据库配置文件的种子方法中执行一些原始的 SQL 命令。或者,对于集成测试,我只是可以完全禁用初始化并显式控制数据库创建。Eric 何杰写了一个伟大的博客帖子,关于这一点,"使用 SQL 紧凑的集成测试与实体框架"(bit.ly/1qgh9Ps)。

MigrateDatabaseToLatestVersion 照耀的地方

如果你编译该项目并把它给没有迁移她自己开发数据库的另一个开发人员,初始值设定项将触发应用程序来寻找一个迁移。迁移会被发现,因为它是在项目中。初始值设定项将检查数据库的迁移历史记录表和更新数据库架构,如有必要。

考虑过这个问题在生产、 不发展、 迁移类将编译成程序集和部署应用程序的一部分。当应用程序运行时,它将回应 (在适当的位置在您的应用程序) 的 MigrateDatabaseToLatestVersion 设置,并相应地更新数据库架构。

那么蔚蓝的 Web 站点中发生了什么?

现在,你已经很好地理解如何 MigrateDatabase­ToLatestVersion 的作品,让我们回到我的朋友交谈。我们在离开:"迁移初始值设定项怎么样?这将做它,太好了。"

他回答说他没有在代码中设置的迁移初始值设定项在任何地方。所以,现在它是一个巨大的谜。行为完全匹配的 MigrateDatabaseToLatestVersion。他到 Azure 部署自己的应用程序。当他跑了它时,应用程序注意到他已经在重新部署之前在模型中所做的更改,并且它更新数据库架构。

所有的这些问题问和回答,让我有点困惑,我牵手去我最喜爱的搜索引擎,提供一些有用的信息,并发现了一个。

当您将一个 Web 应用程序发布到 Azure 时,设置页上有一个复选框,默认情况下未选中。但这个复选框表示"执行代码第一次迁移 (运行应用程序启动)。"检查这将 MigrateDatabaseToLatestVersion 设置声明方式添加配置文件中。

我的朋友通过电子邮件发送给他看一张带有该设置的页面的截图和问,"看起来熟悉吗?"谜团解开了。他曾创建迁移和更新他的开发数据库。所以迁移被编制成他的议会中,是当他部署它。他记得选中这个复选框。

所以当他第一次跑了他的网站上,初始值设定项将调用 DbMigrator.Update,其中比较迁移到 MigrationsHistory 表中的列表组件中的列表和跑任何不是表中存在。

现在他认识到作为一项功能,不是一个谜。


Julie Lerman是 Microsoft MVP、.NET 导师和顾问,住在佛蒙特州的山区。您可以在全球的用户组和会议中看到她对数据访问和其他 .NET 主题的演示。她的博客 thedatafarm.com/blog ,是作者的"编程实体框架"(2010 年),以及代码第一版 (2011 年) 和 DbContext 版 (2012 年),所有从 O'Reilly 媒体。通过她的 Twitter(网址为 twitter.com/julielerman)关注她,并在 juliel.me/PS-Videos 上观看其 Pluralsight 课程。

衷心感谢以下 Microsoft 技术专家对本文的审阅:罗文 · 米勒