【问题标题】:DbMigrator does not detect pending migrations after switching database切换数据库后 DbMigrator 未检测到挂起的迁移
【发布时间】:2016-07-03 19:07:26
【问题描述】:

EntityFramework 迁移在切换到新上下文后变得无用。 DbMigrator 正在使用来自第一个数据库实例的 Pending Migrations 列表,这意味着没有迁移应用到其他数据库,这会导致 Seed() 期间出现错误;

  • 带有 EF 6 的 C# .NET 4.5 MVC 项目
  • MS SQL Server 2014,同一数据库模型的多个实例。
  • 带有迁移的 CodeFirst 方法。
  • DbContext 初始化程序设置为 null。

在应用程序启动时,我们有自定义 Db 初始化来创建和更新数据库。 CreateDatabaseIfNotExists 正在按预期工作,新数据库已应用所有迁移。但是,MigrateDatabaseToLatestVersion 初始化程序和我们的自定义初始化程序都无法更新列表中第一个以外的数据库。

foreach (var connectionString in connectionStrings)
{
    using (var context = new ApplicationDbContext(connectionString))
    {
        //Create database
        var created = context.Database.CreateIfNotExists();

        var conf = new Workshop.Migrations.Configuration();
        var migrator = new DbMigrator(conf);

        migrator.Update();

        //initial values
        conf.RunSeed(context);
    }
}
  • context.Database.CreateIfNotExists(); 工作正常。
  • migrator.GetLocalMigrations() 总是返回正确的值。
  • migrator.GetPendingMigrations() 第一个数据库返回后 空列表。
  • migrator.GetDatabaseMigrations() 是挂起迁移的镜像, 在第一个数据库之后,它包含空数据库的完整列表事件。
  • 从 Db 实例获取数据 (context.xxx.ToList()) 确认连接已建立且正常工作,并链接到正确的实例。

使用migrator.Update("migration_name"); 强制更新到最近的迁移不会改变任何事情。根据我通过阅读 EF 源代码收集到的信息,它会自行检查挂起的迁移列表,这会给出错误的结果。

引擎盖下似乎有一些缓存,但我不知道如何重置它。

有没有办法在多个数据库上执行迁移,还是 EF 中的另一个“设计错误”?


编辑:

真正的问题是DbMigrator 创建新的上下文供自己使用。它通过默认的无参数构造函数完成,在我的例子中,它回退到web.Config 中的默认(第一个)连接字符串。

我没有看到这个问题的好的解决方案,但在我的情况下,原始的解决方法是临时编辑默认连接字符串:

var originalConStr = WebConfigurationManager.ConnectionStrings["ApplicationDbContext"].ConnectionString;

var setting = WebConfigurationManager.ConnectionStrings["ApplicationDbContext"];

var fi = typeof(ConfigurationElement).GetField("_bReadOnly", BindingFlags.Instance | BindingFlags.NonPublic);

//disable readonly flag on field
fi.SetValue(setting, false);

setting.ConnectionString = temporaryConnectionString; //now it works

//DO STUFF

setting.ConnectionString = originalConStr; //revert changes

作弊来自:How do I set a connection string config programatically in .net?


我仍然希望有人能找到真正的解决方案,所以现在我会避免自我回答。

【问题讨论】:

    标签: c# entity-framework ef-code-first entity-framework-migrations


    【解决方案1】:

    您需要正确设置DbMigrationsConfiguration.TargetDatabase属性,否则迁移器将使用默认连接信息。

    所以理论上你可以做这样的事情

    conf.TargetDatabase = new System.Data.Entity.Infrastructure.DbConnectionInfo(...);
    

    不幸的是,DbConnectionInfo 中仅有的 2 个公共 constructors

    public DbConnectionInfo(string connectionName)
    

    connectionName:应用配置中连接字符串的名称。

    public DbConnectionInfo(string connectionString, string providerInvariantName)
    

    connectionString:用于连接的连接字符串。
    providerInvariantName:用于连接的提供者的名称。对 SQL Server 使用“System.Data.SqlClient”。

    我看到你有连接字符串,但不知道如何获得providerInvariantName

    更新:我没有找到一个好的“官方”获取所需信息的方式,所以我已经结束了通过反射访问 internal 成员的黑客行为,但 IMO 仍然是比您使用的更安全:

    var internalContext = context.GetType().GetProperty("InternalContext", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(context);
    var providerName = (string)internalContext.GetType().GetProperty("ProviderName").GetValue(internalContext);
    var conf = new Workshop.Migrations.Configuration();
    conf.TargetDatabase = new System.Data.Entity.Infrastructure.DbConnectionInfo(connectionString, providerName);
    

    【讨论】:

    • Hmmm... DbContextConfiguration 不想像 DbMigrationsConfiguration 那样工作(DbMigrator 构造函数想要它)所以实用的方法在这里失败了。自定义连接信息失败,因为 DbMigrator 忽略它并调用我的 Context 的无参数构造函数,该构造函数默认使用来自 web.config 的连接字符串。 (这是我真正的问题,它似乎)。最后,我只是在更新期间通过反射编辑ebConfigurationManager.ConnectionStrings["ApplicationDbContext"]。我希望有更好的方法(单例/静态字段听起来不是更好)。
    • 不,我不是。我的团队负责人是 ;p
    • 顺便说一句,connectionStrings 是什么?我注意到您使用了.Value,所以它似乎是某种字典?关键不是connectionName吗?
    • ConcurrentDictionary<string, string>,包含从其他应用获取的完整连接字符串。我从样本中删除了它以避免混淆:)
    • 是的,你的 hack 更好:)
    猜你喜欢
    • 2016-08-05
    • 1970-01-01
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 2020-10-12
    • 2015-10-28
    • 2015-09-13
    • 1970-01-01
    相关资源
    最近更新 更多