在Entity Framework 4.3中逐步增加种子数据的最佳方法

41

我一直在使用Entity Framework 4.3操作现有数据库,并且有几个情况需要考虑。

首先,如果我删除我的数据库,我希望EF可以从头开始重新创建 - 我已经成功使用了CreateDatabaseIfNotExists数据库初始化器。

其次,如果我更新模型并且数据库已经存在,则希望自动更新数据库 - 我已经成功使用了Entity Framework 4.3迁移。

那么这里是我的问题。假设我向模型中添加一个需要一些引用数据的新表,最好的方法是什么,以确保在数据库初始化器运行时和迁移运行时都创建此数据。我希望在从头创建数据库时创建数据,并在由迁移运行导致的数据库更新时也创建数据。

在一些EF迁移示例中,我看到人们在迁移的UP方法中使用SQL()函数来创建种子数据,但如果可能的话,我宁愿使用上下文来创建种子数据(正如您在大多数数据库初始化器示例中看到的)。因为EF的整个理念就是将SQL抽象化,所以在这里使用纯SQL似乎很奇怪。我尝试在UP方法中使用上下文,但是由于某种原因,当我尝试在调用创建表的语句之后直接添加种子数据时,它无法确定在迁移中创建的表是否存在。

非常感谢您的帮助。

2个回答

55
如果您想使用实体来生成数据,您应该在迁移配置中使用Seed方法。如果您生成了新项目 Enable-Migrations,则将获得此配置类:

如果您想使用实体来种植数据,您应该在迁移配置中使用Seed方法。如果您生成了一个全新的项目 Enable-Migrations,则会得到这个配置类:

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

    protected override void Seed(CFMigrationsWithNoMagic.BlogContext context)
    {
        //  This method will be called after migrating to the latest version.

        //  You can use the DbSet<T>.AddOrUpdate() helper extension method 
        //  to avoid creating duplicate seed data. E.g.
        //
        //    context.People.AddOrUpdate(
        //      p => p.FullName,
        //      new Person { FullName = "Andrew Peters" },
        //      new Person { FullName = "Brice Lambson" },
        //      new Person { FullName = "Rowan Miller" }
        //    );
        //
    }
}

迁移中的数据种子方式并不是非常高效,因为它只应该用于一些基本的种子。每次更新到新版本时,都会遍历整个集合,尝试更新现有数据或插入新数据。如果您没有使用AddOrUpdate扩展方法,则必须手动确保仅在数据库中不存在这些数据时才将数据种植到数据库中。

如果您想要高效地进行种子播种,因为您需要种植许多数据,那么您可以使用通用的方式获得更好的结果:

public partial class SomeMigration : DbMigration
{
    public override void Up()
    {
        ...
        Sql("UPDATE ...");
        Sql("INSERT ...");
    }

    public override void Down()
    {
        ...
    }
}

8
你实际上可以在Up方法中创建一个上下文并使用AddOrUpdate插入行,但这样做不会被包含在迁移事务中,可能会导致问题。此外,不能保证当模型发生更改时该方法仍然能够编译通过。 - Betty
我尝试在Up方法中创建上下文,但它抛出一个错误,说表不存在。我将尝试在“Up”方法中使用SQL代替。 - Richard Beier
8
@Ladislav,你在 EF 方面的知识深度令我惊叹,你是否考虑撰写一本关于该主题的书籍,并解决你在这里遇到的常见误解? - kingdango
1
使用这种方法时,必须小心设置AutomaticMigrationDataLossAllowed = false。如果不这样做,您的数据库中可能会丢失数据。当我使用自动迁移时,这种情况曾经发生过。在我看来,最好使用种子方法。 - aBetterGamer
1
有没有办法从生产环境调用那段代码?我不确定如何在目标服务器上进行最初的数据库种子填充。我需要创建一个角色(“管理员”)并确保有几个用户在其中。 - Astaar

33

我不建议在 Up() 方法中使用 Sql() 调用,因为(在我看来)这实际上是为了实际的迁移代码而设计的,没有内置的函数,而不是种子代码。

我喜欢把种子数据看作是未来可能更改的内容(即使我的模式没有更改),所以我只需要在种子函数中对所有插入操作进行“防御性”检查,以确保该操作以前未执行过。

考虑一个场景,你有一个 "Types" 表,开始时有 3 条记录,但之后你添加了第 4 条。你不应该需要一个“迁移”来解决这个问题。

使用 Seed() 还可以给你一个完整的上下文环境,这比 Ladislav 演示的 Sql() 方法中使用简单的 sql 字符串要好得多。

此外,请记住使用内置的 EF 方法来处理迁移代码和种子代码的好处是你的数据库操作保持平台中立。这意味着你的模式更改和查询可以在 Oracle、Postgre 等上运行。如果你编写实际的原始 SQL,那么你可能会不必要地将自己锁定。

你可能不太关心这一点,因为使用 EF 的人中 90% 只会使用 SQL Server,但我只是提出这个观点,以便给你提供一种不同的解决方案的视角。


8
我认为“Up”方法是存储“引用”数据的好位置 - “引用”数据通常意味着应用程序需要该数据进行某种逻辑处理。 - nootn

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