关于实体框架 4:EF 4.3 Auto-Migrations with multiple DbContexts in one database

EF 4.3 Auto-Migrations with multiple DbContexts in one database

我正在尝试将 EF 4.3 迁移与多个代码优先 DbContext 一起使用。我的应用程序分为几个插件,它们可能有自己的 DbContext 关于他们的域。应用程序应该使用一个单一的 sql 数据库。

当我尝试自动迁移空数据库中的上下文时,这仅对第一个上下文成功。每个其他上下文都需要将 AutomaticMigrationDataLossAllowed-Property 设置为 true,然后尝试删除前一个的表。

所以我的问题是:

  • 我怎么能告诉迁移配置只是照顾在其相应上下文中定义的表而让所有其他人不理会?
  • 在单个数据库中通过自动迁移处理多个 DbContext 的正确工作流程是什么?

谢谢!


这是你可以做的。很简单。

您可以为每个上下文创建配置类。
例如

1
2
3
4
5
6
7
8
9
10
11
12
13
internal sealed class Configuration1 : DbMigrationsConfiguration<Context1>{
   public Configuration1 (){
        AutomaticMigrationsEnabled = false;
        MigrationsNamespace ="YourProject.Models.ContextNamespace1";
   }
}

internal sealed class Configuration2 : DbMigrationsConfiguration<Context2>{
   public Configuration2 (){
        AutomaticMigrationsEnabled = false;
        MigrationsNamespace ="YourProject.Models.ContextNamespace2";
   }
}

现在您添加迁移。您不需要启用迁移,因为您已经使用上述 2 类。

1
Add-Migration -configuration Configuration1 Context1Init

这将为 context1 创建迁移脚本。您可以对其他上下文再次重复此操作。

1
Add-Migration -configuration Configuration2 Context2Init

更新你的数据库

1
2
Update-Database -configuration Configuration1
Update-Database -configuration Configuration2

这可以按任何顺序完成。除非您需要确保按顺序调用每个配置。


Code First 迁移假设每个数据库只有一个迁移配置(每个配置一个上下文)。

我可以想到两种可能的解决方案:

  • 创建一个包含每个上下文的所有实体的聚合上下文,并从您的迁移配置类中引用这个"超级"上下文。这样所有的表都将在用户的数据库中创建,但数据只会在他们安装插件的表中。

  • 为每个上下文使用单独的数据库。如果您在上下文之间共享实体,请添加自定义迁移并将 CreateTable(...) 调用替换为 Sql("CREATE VIEW ...") 调用以从实体的"原始"数据库中获取数据。

  • 我会尝试#1,因为它将所有内容都保存在一个数据库中。您可以在解决方案中创建一个单独的项目来包含您的迁移和这个"超级"上下文。只需添加项目,引用所有插件的项目,创建一个包含所有实体的上下文,然后在这个新项目上调用 Enable-Migrations。之后事情应该会按预期进行。


    我有一个使用迁移的具有多个上下文的工作站点。但是,您确实需要为每个上下文使用一个单独的数据库,并且它都是由项目的 Migrations 命名空间中的 *Configuration 类驱动的,例如,CompanyDbContext 使用 CompanyConfiguration 指向 Company.sdf。 update-database -configurationtypename CompanyConfiguration。另一个 LogDbContext 使用 LogConfiguration 等指向 Log.sdf。

    鉴于此工作,您是否尝试过创建 2 个指向同一个数据库的上下文并告诉模型构建器忽略其他上下文的表列表?

    1
    2
    3
    4
    5
    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Ignore<OtherContextsClass>();
        // more of these
    }

    由于迁移与模型构建器一起工作,这可能会完成这项工作。

    糟糕的替代方案是避免使用自动迁移,每次生成迁移,然后手动筛选并删除不需要的语句,然后运行它们,尽管没有什么能阻止您创建一个查看上下文和生成语句的简单工具并为您进行迁移修复。


    我刚刚遇到了这个问题,并意识到我将它们分成不同的上下文的原因纯粹是为了将相关模型分组到可管理的块中,而不是出于任何其他技术原因。相反,我已将上下文声明为部分类,现在具有不同模型的不同代码文件可以将 DbSet 添加到 DbContext.

    这样,自动迁移魔法仍然有效。


    如上所述,最实用的解决方案是每个应用程序/数据库拥有 1 个超级 DbContext。

    整个应用程序只能使用 1 个 DbContext 似乎是一个关键的技术和方法劣势,因为它会影响模块化等。此外,如果您使用 WCF 数据服务,则每个应用程序只能使用 1 个数据服务,因为数据服务只能映射到 1 个 DbContext。所以这大大改变了架构。

    从好的方面来说,一个小的优势是所有与数据库相关的迁移代码都是集中的。


    好的,我已经为此苦苦挣扎了一天,这里是寻求答案的人的解决方案...

    我假设大多数阅读这篇文章的人都在这里,因为他们有一个带有很多 DbSet<> 属性的大型 DbContext 类,并且加载需要很长时间。您可能会想,哎呀,这很有意义,我应该拆分上下文,因为我不会一次使用所有 dbset,我只会根据需要的情况加载"部分"上下文它。因此,您将它们分开,却发现 Code First 迁移不支持您的革命性思维方式。

    所以您的第一步一定是拆分上下文,然后为每个新上下文添加了 MigrationConfiguration 类,添加了与新上下文类命名完全相同的连接字符串。

    然后您尝试通过执行 Add-Migration Context1 然后执行 Update-Database -Verbose 来逐一运行新拆分的上下文...

    一切似乎都运行良好,但随后您注意到每次后续迁移都删除了上一次迁移中的所有表,并且只保留了上次迁移中的表。

    这是因为,当前的迁移模型要求每个数据库有一个 DbContext,并且它必须是镜像匹配。

    我也尝试过,并且有人在这里建议这样做,是创建一个 SuperContext,其中包含所有 Db 集。创建一个单一的迁移配置类并在其中运行。保留部分上下文类,并尝试实例化并使用它们。 EF 抱怨 Backing 模型发生了变化。同样,这是因为 EF 将您的部分 dbcontext 与您的 Super Context 迁移留下的 All-Sets 上下文签名进行比较。

    在我看来,这是一个重大缺陷。

    就我而言,我认为性能比迁移更重要。所以,我最终做的是,在我在 Super 上下文中运行并准备好所有表之后,我进入数据库并手动删除了 _MigrationHistory 表。

    现在,我可以在没有 EF 抱怨的情况下实例化和使用我的部分上下文。它没有找到 MigrationHistory 表,只是继续前进,让我拥有数据库的"部分"视图。

    当然要权衡的是,对模型的任何更改都必须手动传播到数据库,所以要小心。

    它对我有用。


    我想让人们知道,下面的答案对我有用,但有一个警告:不要使用 MigrationsNamespace 行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    internal sealed class Configuration1 : DbMigrationsConfiguration<Context1>{
           public Configuration1 (){
            AutomaticMigrationsEnabled = false;
            MigrationsNamespace ="YourProject.Models.ContextNamespace1";
       }
     }

    internal sealed class Configuration2 : DbMigrationsConfiguration<Context2>{
       public Configuration2 (){
            AutomaticMigrationsEnabled = false;
            MigrationsNamespace ="YourProject.Models.ContextNamespace2";
       }
    }

    但是,我已经建立了 2 个数据库并定义了它们自己的上下文,所以我发现自己收到一条错误消息,提示"YourProject.Models 命名空间已经定义了 ContextNamespace1"。这是因为"MigrationsNamespace ="YourProject.Models.ContextNamespace2";"在我尝试 Init 之后,导致在 YourProjects.Models 命名空间下定义 dbcontext 两次(一次在迁移 Context1Init 文件中,一次在我之前定义的位置)。

    所以,我发现当时我必须做的是按照此处的说明从头开始我的数据库和迁移(幸好我没有需要保留的数据):
    http://pawel.sawicz.eu/entity-framework-reseting-migrations/

    然后我将代码更改为不包含 MigrationsNamespace 行。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    internal sealed class Configuration1 : DbMigrationsConfiguration<Context1>{
           public Configuration1 (){
            AutomaticMigrationsEnabled = false;
       }
     }

    internal sealed class Configuration2 : DbMigrationsConfiguration<Context2>{
       public Configuration2 (){
            AutomaticMigrationsEnabled = false;
       }
    }

    然后我再次运行 Add-Migration -configuration Configuration1 Context1Init 命令并再次运行 Update-Database -configuration Configuration1 行(对于我的第二个上下文),最后,现在一切似乎都很好。


    当然,解决方案应该是 EntityFramework 团队进行修改,以更改 API 以支持将 _MigrationHistory 表直接修改为您选择的表名,如 _MigrationHistory_Context1 以便它可以处理独立 DbContext 实体的修改。这样一来,它们都被分开处理,并由开发人员确保实体的名称不会发生冲突。

    似乎有很多人同意我的观点,即引用实体超集的重复 DbContext 是一种虚假的非企业友好的处理方式。对于基于模块化(Prism 或类似)的解决方案,重复的 DbContext 会惨遭失败。


    我已经让它与手动迁移一起工作,但你不能降级,因为它不能区分 __MigrationHistory 表中的配置。如果我尝试降级,那么它会将来自其他配置的迁移视为自动迁移,并且由于我不允许数据丢失,它会失败。我们只会用它来升级,所以它可以满足我们的目的。

    虽然它看起来确实有点 ommision,但我相信只要 DbContexts 之间没有重叠,支持它并不难。