独立于模式的实体框架代码优先迁移

Posted

技术标签:

【中文标题】独立于模式的实体框架代码优先迁移【英文标题】:Schema independent Entity Framework Code First Migrations 【发布时间】:2015-12-04 02:12:18 【问题描述】:

我在使用针对 Oracle 数据库的实体框架迁移时遇到了麻烦,因为架构名称包含在迁移代码中,对于 Oracle,架构名称也是用户名。我的目标是进行独立于模式的 Code First 迁移(能够拥有一组用于测试和生产环境的迁移)。

我已经尝试过这种方法(使用 Entity Framework 6.1.3):

1) 我在 Web.config 中有架构名称:

<add key="SchemaName" value="IPR_TEST" />

2) 我的 DbContext 将架构名称作为构造函数参数:

public EdistributionDbContext(string schemaName) 
    : base("EdistributionConnection")

    _schemaName = schemaName;


protected override void OnModelCreating(DbModelBuilder modelBuilder)

    modelBuilder.HasDefaultSchema(_schemaName);

3) 我必须为实体框架迁移实现 IDbContextFactory 才能创建没有无参数构造函数的 DbContext:

public class MigrationsContextFactory : IDbContextFactory<EdistributionDbContext>

    public EdistributionDbContext Create()
    
        return new EdistributionDbContext(GetSchemaName());
    

4) 我还将迁移历史记录表配置为放置在正确的架构中:

public class EdistributionDbConfiguration : DbConfiguration

    public EdistributionDbConfiguration()
    
        SetDefaultHistoryContext((connection, defaultSchema) 
            => new HistoryContext(connection, GetSchemaName()));
    

5) 我修改了为迁移生成的代码以替换硬编码的模式名称。例如。我用CreateTable($"_schema.Users") 替换了CreateTable("IPR_TEST.Users")。 (_schema字段根据Web.config中的值设置)。

6) 我使用MigrateDatabaseToLatestVersion&lt;EdistributionDbContext, MigrationsConfiguration&gt;() 数据库初始化程序。

完成所有这些设置后,当我切换到不同的架构时(例如,通过 web.config 转换),我仍然遇到问题 - 抛出异常,告诉我数据库与我的模型不匹配并且 AutomaticMigrations 被禁用(这是需要的)。当我尝试执行add-migration 时,会生成一个新的迁移,其中所有对象都应移动到不同的架构(例如:MoveTable(name: "IPR_TEST.DistSetGroups", newSchema: "IPR");,这绝对是不希望的。

对我来说,架构名称似乎在迁移类的模型字符串哈希中的某处硬连线(例如 201509080802305_InitialCreate.resx),即:

<data name="Target" xml:space="preserve">
    <value>H4sIAAAAAAAEAO09227jO... </value>
</data> 

有办法告诉 Code First 迁移忽略模式名称吗?

【问题讨论】:

我确定你已经尝试过了,但是对于 SQL Server,如果你只是没有在模型绑定中指定架构,它只会使用用户默认的架构。 谢谢@Ben,您的评论可能会解决我的问题。当您省略 modelBuilder.HasDefaultSchema("SCHEMA_NAME"); 时,实体框架将“dbo”设置为默认架构。但是,现在我尝试了modelBuilder.HasDefaultSchema(string.Empty);,现在似乎(正确)使用了用户的默认模式。可悲的是,用于为迁移生成 SQL 的Oracle.ManagedDataAccess.EntityFramework.OracleMigrationSqlGeneratorstring.Empty-schema-name 存在问题,并在生成迁移的 SQL 时抛出异常......但这是另一个问题...... 您好 Jan Palas,您找到解决此问题的方法了吗?我有同样的问题,我将 EF Migration 与 Oracle 一起使用,当我更改我的架构时,所有迁移脚本都包含 MoveTable... @toregua:我没有找到解决方案,但有一个解决方法:我使用具有适用于我们测试环境的特定模式名称的迁移。当存在新的迁移时,它会在部署到测试环境后自动更新我们的数据库模式。对于生产环境,我使用迁移来生成数据库更新脚本 (update-database -script -sourceMigration Migration13 -targetMigration Migration18),之后我会在其中更改模式名称。部署到生产环境时,我手动执行数据库更新脚本。 (抱歉回答晚了,正在度假) @toregua:您可以选择使用可以与string.Empty-schema-name 一起使用的其他OracleMigrationSqlGenerator。我记得有一个来自 Devart (devart.com/dotconnect/oracle) 的人可能会这样做,但我的公司决定不购买这个库,因此我没有测试它。 【参考方案1】:

您可以创建派生的DbContext 并在OnModelCreating 中“覆盖”modelBuilder.HasDefaultSchema(...)

public class TestDbContext : ProductionDbContext

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        base.OnModelCreating(modelBuilder);

        modelBuilder.HasDefaultSchema("TestSchema");
    

然后您可以为这两个上下文创建迁移。请参阅this question,了解如何在一个项目中创建两个迁移。

这种方法的缺点是您必须维护两个单独的迁移。但它让您有机会调整TestDbContext 的配置。

【讨论】:

【参考方案2】:

我也遇到了同样的问题,多亏了你的方法,我终于找到了一个看起来效果很好的解决方案:

1) 我在 Web.config 应用设置中有架构名称:

<add key="Schema" value="TEST" />

2) 我有一个历史背景:

public class HistoryDbContext : HistoryContext

    internal static readonly string SCHEMA;

    static HistoryDbContext()
    
        SCHEMA = ConfigurationManager.AppSettings["Schema"];
    

    public HistoryDbContext(DbConnection dbConnection, string defaultSchema)
            : base(dbConnection, defaultSchema)
     

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        base.OnModelCreating(modelBuilder);
        modelBuilder.HasDefaultSchema(SCHEMA);
    

3) 我有一个引用我的历史数据库上下文的数据库配置:

public class MyDbConfiguration : DbConfiguration

    public MyDbConfiguration()
    
        SetDefaultHistoryContext((connection, defaultSchema) => new HistoryDbContext(connection, defaultSchema));
    

4) 这是我的数据库上下文:

public partial class MyDbContext : DbContext

    public MyDbContext()
        : base("name=MyOracleDbContext")
     

    public static void Initialize()
    
        DbConfiguration.SetConfiguration(new MyDbConfiguration());
        Database.SetInitializer(new MigrateDatabaseToLatestVersion<MyDbContext, Migrations.Configuration>());
    

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    
        modelBuilder.HasDefaultSchema(string.Empty);
    

5) 最后我从 global.asax 中调用 Initialize 方法

protected void Application_Start()

    MyDbContext.Initialize();

关键是将db上下文的默认模式设置为String.Empty,并将历史上下文的模式设置为正确的模式。 因此,当您创建迁移时,它们与架构无关:迁移的 resx 的 DefaultSchema 变量将为空。但是历史数据库上下文模式仍然是正确的,允许迁移检查通过。

我正在使用以下 nugets 包:

<package id="EntityFramework" version="6.2.0" targetFramework="net452" />
<package id="Oracle.ManagedDataAccess" version="12.2.1100" targetFramework="net452" />
<package id="Oracle.ManagedDataAccess.EntityFramework" version="12.2.1100" targetFramework="net452" />

然后您可以在不同的数据库上成功使用 Oracle 迁移。

【讨论】:

确认工作。仅在 HistoryDbContext 上设置模式名称确实是产生差异的区别,它使一切都到位。我想强调一个事实,如果您使用流利的配置/数据注释并且您想采用这种方法,您必须确保您没有在流利的配置中的任何地方设置架构名称/data-annotations(只需将架构名称保留为空字符串)。 其实不行:EF在db初始化时抛出'seqOwner cannot be null'异常。

以上是关于独立于模式的实体框架代码优先迁移的主要内容,如果未能解决你的问题,请参考以下文章

实体框架代码优先 - 初始代码迁移不起作用

调试代码优先的实体框架迁移代码

实体框架代码优先:启用迁移错误

实体框架 - 迁移 - 代码优先 - 每次迁移播种

无法在 .NET 中的实体框架代码优先方法中迁移 AspNetUser 实体

如何为实体框架代码优先迁移设置隔离级别