EF自动迁移和种子数据的混淆 - 每次程序启动都进行种子数据填充

43

我最近将一个应用程序从以下内容更改为开发使用:

DropCreateDatabaseIfModelChanges<Context>


使用方法:

public class MyDbMigrationsConfiguration: DbMigrationsConfiguration<GrsEntities>
{
    public MyDbMigrationsConfiguration()
    {
        AutomaticMigrationsEnabled = true;
        AutomaticMigrationDataLossAllowed = true;
    }
}
在我的数据库上下文中,我有:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    // Tell Code First to ignore PluralizingTableName convention
    // If you keep this convention then the generated tables will have pluralized names.
    modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

    //set the initializer to migration
    Database.SetInitializer(new MigrateDatabaseToLatestVersion<GrsEntities, MigrationConfig>());
}
我已经使用AddOrUpdate扩展重写了DbMigrationsConfiguration中的Seed(context),在之前使用的是Add方法,并对删除数据库时进行了种子播种(DropCreateDatabaseIfModelChanges)。
我的困惑是,每次启动应用程序时都会运行迁移,无论DbContext是否有任何更改。每次我启动应用程序(通过服务运行的库)时,初始化程序都会运行,Seed也会运行。我期望的行为是检查是否需要迁移(背后检查模型是否与物理数据库匹配),然后更新任何新/删除的表/列,并只在有变化时运行种子。
在我的测试中,种子每次都运行,这是可以工作但似乎效率不高并且不是我所期望的结果。不幸的是,MSDN文档非常有限。
我是否完全误用了MigrateDatabaseToLatestVersion?有没有办法获得我期望的行为(即只有在模型更改时才进行种子操作),还是应该将我的种子方法更改为每次应用程序启动时都要运行?
3个回答

61

EF 4.1版本中Seed方法只有在数据库发生变化时才会运行,这对于内置数据库初始化程序来说是相当有局限性的。因为有时你需要更新种子数据,但又不想改变数据库,为了达到这个目的,你必须人为地让它看起来像是数据库已经发生了变化。

使用迁移后,Seed方法的使用有了一些不同,因为不能再假定数据库是空的--这也是迁移的关键点之一。因此,在迁移中的Seed方法必须假设数据库已经存在,并且可能已经有数据存在,但这些数据可能需要更新以考虑为Migrations所做的更改。因此,就有了AddOrUpdate的使用。

现在我们面临的情况是,Seed方法必须编写以考虑现有数据,这意味着没有必要沿用EF 4.1 Seed方法的限制,即为了使Seed方法运行而人为地让它看起来像是数据库已经发生了变化。因此,Seed现在每次在应用程序域中首次使用上下文时都会运行。这不应该改变Seed的实现方式,因为它需要处理数据已经存在的情况。

如果因为有大量的Seed数据而导致性能问题,则通常很容易在Seed方法中添加查询数据库的检查,以确定需要完成多少工作,然后再执行。


谢谢Arthur。那很清晰、简洁,正是我所需要的。我最终改变了我的种子以假设它每次都会运行,但知道背后的历史也是很好的(我之前找不到)。 - shox
9
曾经种子方法只会在迁移发生时运行,而现在种子方法会在每次首次使用上下文时运行,这一点非常令人困惑。是否有任何 MSDN 文章详细介绍了这个变化? - lee_mcmullen

18

我有点赞同Arthur Vickers的回答,但我认为 Seed 方法是用于 DbMigrations 的,而我不希望每次都检查所有内容,例如:如果我有 4 个迁移,那么我就需要测试哪些数据必须被种植,这将至少需要 4 次数据库访问。

如果你仍然想在迁移应用时运行 Seed 方法的行为,像我一样,我自己实现了IDatabaseInitializer 策略。

public class CheckAndMigrateDatabaseToLatestVersion<TContext, TMigrationsConfiguration>
    : IDatabaseInitializer<TContext>
    where TContext : DbContext
    where TMigrationsConfiguration : DbMigrationsConfiguration<TContext>, new()
{
    public virtual void InitializeDatabase(TContext context)
    {
        var migratorBase = ((MigratorBase)new DbMigrator(Activator.CreateInstance<TMigrationsConfiguration>()));
        if (migratorBase.GetPendingMigrations().Any())
            migratorBase.Update();
    }
}

1
谢谢,这正是我在寻找的。 - Jono

3

另一个选择可能是在种子方法中动态加载自定义的数据库初始化类。生产应用程序可以加载虚拟初始化程序,而开发应用程序可以加载真正的初始化程序。您可以使用Unity/MEF。

    // Unity Dependency Injection Prop
    [Dependency]
    property IMyInitializer initializer;

    protected override Seed(YourContextClass context)
    {
       initializer.Seed(context);
    }

有类似这样的情况。在生产环境中设置好数据库后,你需要切换初始化程序到一个虚拟的初始化程序,它不会执行任何操作。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接