尝试EF代码优先和迁移——绊脚石

本文关键字:迁移 绊脚石 EF 代码 尝试 | 更新日期: 2023-09-27 18:16:21

我一直是一个面向数据库的程序员,所以直到今天,我一直使用数据库驱动的方法来编程,我对T-SQL和SQL Server很有信心。

我正试图把我的头围绕实体框架6 代码优先方法-坦率地说-我很挣扎。

我有一个现有的数据库-所以我做了一个Add New Item > ADO.NET Entity Data Model > Code-First from Database,我得到了一堆c#类代表我现有的数据库。到目前为止一切顺利。

我现在要做的是探索如何处理正在进行的数据库升级——既包括模式升级,也包括"静态"(预填充)查找数据升级。我的第一个抱怨是,从数据库中反向工程的实体正在用Fluent API进行配置,而对我来说,创建我想要创建的带有数据注释的c#类的新表似乎更自然。"混合"这两种方法有什么问题吗?或者我可以告诉逆向工程步骤只使用数据注释属性而不是完全使用Fluent API吗?

我的第二个也是更大的抱怨:我试图创建漂亮的小迁移-我试图添加的每一组功能(例如,一个新表,一个新索引,几个新列等)-但似乎我只能有一个"未决"迁移......当我有一个,我修改我的模型类进一步,我试图获得第二次迁移使用add-migration (name of migration),我迎接:

无法生成显式迁移,因为以下显式迁移正在挂起:[201510061539107_CreateTableMdsForecast]。在尝试生成新的显式迁移之前,应用挂起的显式迁移。

认真呢? ! ? ! ? 我不能有多个一个,单个待迁移??我需要运行update-database 后每一个微小的迁移我添加?

似乎是一个相当缺点!我宁愿创建10、20个小的、紧凑的、易于理解的迁移,然后,然后一次性应用它们——没有办法做到这一点!这真的很难相信.....还有别的办法吗??

尝试EF代码优先和迁移——绊脚石

在开发期间,您一次只能打开一个挂起的迁移,这是真的。要理解其中的原因,您必须了解迁移是如何生成的。生成器的工作原理是将数据库(模式)的当前状态与模型代码的当前状态进行比较。然后,它有效地创建一个"脚本"(一个c#类),修改数据库的模式以匹配模型。您不希望同时挂起一个以上的脚本,否则这些脚本将相互冲突。让我们举一个简单的例子:

假设我有一个类Widget:

class Widget
{
    public int Id { get; set; }
    public string Name { get; set; }
}

和数据库中匹配的表Widgets:

Widgets
-------
Id (int, PK, not null)
Name (nvarchar(100), not null)

现在我决定添加一个新的属性Size到我的类。

class Widget
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Size { get; set; }  // added
}

当我创建迁移时,生成器查看我的模型,将其与数据库进行比较,并看到我的Widget模型现在具有Size属性,而相应的表没有Size列。因此,最终的迁移结果看起来像这样:

public partial class AddSizeToWidget : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.Widgets", "Size", c => c.Int());
    }
    public override void Down()
    {
        DropColumn("dbo.Widgets", "Size");
    }
}

现在,假设允许创建第二个迁移,而第一个迁移仍然挂起。我还没有运行Update-Database命令,所以我的基线数据库模式仍然相同。现在我决定添加另一个属性ColorWidget

当我为这个更改创建迁移时,生成器将我的模型与数据库的当前状态进行比较,并看到我添加了两个列。因此它创建了相应的脚本:

public partial class AddColorToWidget : DbMigration
{
    public override void Up()
    {
        AddColumn("dbo.Widgets", "Size", c => c.Int());
        AddColumn("dbo.Widgets", "Color", c => c.Int());
    }
    ...
}

所以现在我有两个待挂的迁移,它们都将在最终运行时尝试向数据库添加Size列。显然,这是行不通的。这就是为什么每次只允许打开一个挂起的迁移。

所以,开发过程中的一般工作流程是:
  1. 更改模型
  2. 生成迁移
  3. 更新数据库以建立新的基线

如果您犯了一个错误,您可以使用Update-Database命令的–TargetMigration参数将数据库回滚到以前的迁移,然后从项目中删除错误的迁移并生成一个新的迁移。(如果您真的想的话,您可以使用这种方法将几个小的迁移组合成一个更大的块,尽管我发现在实践中不值得这样做)。

Update-Database –TargetMigration PreviousMigrationName

现在,当需要更新生产数据库时,您不必每次手动应用每个迁移。这就是迁移的美妙之处——每当您对数据库运行更新后的代码时,它们都会自动应用。在初始化期间,EF查看目标数据库并检查迁移级别(这存储在您在数据库上启用迁移时创建的特殊__MigrationHistory表中)。对于代码中尚未应用的任何迁移,它都会为您运行它们,以使数据库保持最新状态。

希望这有助于澄清问题。

"混合"这两种方法有什么问题吗?

    不,混合是没有问题的。
  1. 与数据注释相比,使用流畅配置可以做更多的事情。
  2. Fluent配置在构建迁移脚本时覆盖数据注释。
  3. 你可以使用数据注释动态地生成dto和前端/UI约束-节省大量代码。
  4. Fluent API具有EntityTypeConfiguration类,它允许您动态地创建对象的域(DDD意义上)并存储它们-大大加快了DbContext的工作速度。

我不能有多个"待处理"迁移

  1. 不是100%正确。(也许是50%,但这并不令人震惊)
  2. 是的,DbMigrator在生成Db时将您的模型"哈希"与数据库模型"哈希"进行比较-因此在您进行新的小迁移之前它会阻止您。但这并不是认为不能进行小迁移的理由。我一直只做小的迁移步骤。
  3. 当你开发一个应用程序时,你使用你的本地数据库,当你开发功能时,你一个接一个地应用小迁移。最后,您将所有小型迁移部署到登台/生产环境中,并将其与所有新功能放在一个dll中,然后逐个应用它们。