数据种子设定

数据播种是使用一组初始数据填充数据库的过程。

可通过多种方式在 EF Core 中完成此过程:

  • 为种子数据建模
  • 手动迁移自定义
  • 自定义初始化逻辑

为种子数据建模

与 EF6 不同,在 EF Core 中,种子设定数据可以在模型配置中与实体类型相关联。 随后,将数据库升级为新版本模型时,EF Core 迁移可以自动计算需要应用的插入、更新或删除操作。

注意

迁移仅在确定应执行哪些操作以使种子数据达到所需状态时考虑模型更改。 因此,对迁移之外执行的任何数据更改都可能会丢失或导致错误。

例如,这将在 OnModelCreating 中为 Blog 配置种子数据:

modelBuilder.Entity<Blog>().HasData(new Blog { BlogId = 1, Url = "http://sample.com" });

若要添加具有关系的实体,需要指定外键值:

modelBuilder.Entity<Post>().HasData(
    new Post { BlogId = 1, PostId = 1, Title = "First post", Content = "Test 1" });

如果实体类型具有处于阴影状态的任何属性,可以使用匿名类来提供值:

modelBuilder.Entity<Post>().HasData(
    new { BlogId = 1, PostId = 2, Title = "Second post", Content = "Test 2" });

从属实体类型可以通过类似的方式进行种子设定:

modelBuilder.Entity<Post>().OwnsOne(p => p.AuthorName).HasData(
    new { PostId = 1, First = "Andriy", Last = "Svyryd" },
    new { PostId = 2, First = "Diego", Last = "Vega" });

请参阅完整示例项目以了解更多上下文。

将数据添加到模型后,应使用迁移来应用更改。

提示

如果需要在自动化部署过程中应用迁移,可以创建一个可在执行前进行预览的 SQL 脚本

或者,可以使用 context.Database.EnsureCreated() 创建包含种子数据的新数据库,例如,用于测试数据库,或者使用内存中提供程序或任何非关系数据库时。 请注意,如果数据库已存在,EnsureCreated() 既不会更新架构,也不会在数据库中设定数据种子。 对于关系数据库,如果计划使用迁移,则不应调用 EnsureCreated()

为种子数据建模的限制

此类种子数据由迁移管理,更新数据库中已有数据的脚本需要在不连接到数据库的情况下生成。 这施加了一些限制:

  • 即使主键值通常由数据库生成,也需要指定它。 它将用于检测迁移之间的数据更改。
  • 如果以任何方式更改主键,将删除之前设定种子的数据。

因此,此功能最适用于不应在迁移外部更改且不依赖于数据库中任何其他内容(例如邮政编码)的静态数据。

如果场景包括以下任一项,建议使用上一部分所述的自定义初始化逻辑:

  • 用于测试的临时数据
  • 依赖于数据库状态的数据
  • 数据量较大(种子设定数据在迁移快照中捕获,而数据量较大可能会快速导致大型文件和性能下降)。
  • 需要由数据库生成键值的数据,包括使用备用键作为标识的实体
  • 需要自定义转换(不由值转换处理)的数据,例如某些密码哈希
  • 需要调用外部 API 的数据,例如 ASP.NET Core 标识角色和用户创建

手动迁移自定义

添加迁移时,对使用 HasData 指定的数据所做的更改将转换为对 InsertData()UpdateData()DeleteData() 的调用。 解决 HasData 的某些限制的方法之一是改为手动向迁移添加这些调用或自定义操作

migrationBuilder.InsertData(
    table: "Blogs",
    columns: new[] { "Url" },
    values: new object[] { "http://generated.com" });

自定义初始化逻辑

执行数据种子设定的一种简单而强大的方法,是在主应用程序逻辑开始执行之前使用 DbContext.SaveChanges()

using (var context = new DataSeedingContext())
{
    context.Database.EnsureCreated();

    var testBlog = context.Blogs.FirstOrDefault(b => b.Url == "http://test.com");
    if (testBlog == null)
    {
        context.Blogs.Add(new Blog { Url = "http://test.com" });
    }

    context.SaveChanges();
}

警告

种子设定代码不应是正常应用执行的一部分,因为当多个实例正在运行时,这可能会导致并发问题,并且还需要应用有权修改数据库架构。

根据部署的约束,初始化代码可以通过不同的方式执行:

  • 在本地运行初始化应用
  • 使用主应用部署初始化应用,调用初始化例程并禁用或删除初始化应用。

这通常可以通过使用发布配置文件自动执行。