种子数据

介绍

使用数据库的某些应用程序(或模块),可能需要有一些初始数据才能​​够正常启动和运行. 例如管理员用户和角色必须在一开始就可用. 否则你就无法登录到应用程序创建新用户和角色.

数据种子也可用于测试的目的,你的自动测试可以假定数据库中有一些可用的初始数据.

为什么要有种子数据系统?

尽管EF Core Data Seeding系统提供了一种方法,但它非常有限,不包括生产场景. 此外它仅适用于EF Core.

ABP框架提供了种子数据系统;

  • 模块化: 任何模块都可以无声地参与数据播种过程,而不相互了解和影响. 通过这种方式模块将种子化自己的初始数据.
  • 数据库独立: 它不仅适用于 EF Core, 也使用其他数据库提供程序(如 MongoDB).
  • 生产准备: 它解决了生产环境中的问题. 参见下面的On Production部分.
  • 依赖注入: 它充分利用了依赖项注入,你可以在播种初始数据时使用任何内部或外部服务. 实际上你可以做的不仅仅是数据播种.

IDataSeedContributor

将数据种子化到数据库需要实现 IDataSeedContributor 接口.

示例: 如果没有图书,则向数据库播种一个初始图书

  1. using System;
  2. using System.Threading.Tasks;
  3. using Volo.Abp.Data;
  4. using Volo.Abp.DependencyInjection;
  5. using Volo.Abp.Domain.Repositories;
  6. using Volo.Abp.Guids;
  7. namespace Acme.BookStore
  8. {
  9. public class BookStoreDataSeedContributor
  10. : IDataSeedContributor, ITransientDependency
  11. {
  12. private readonly IRepository<Book, Guid> _bookRepository;
  13. private readonly IGuidGenerator _guidGenerator;
  14. public BookStoreDataSeedContributor(
  15. IRepository<Book, Guid> bookRepository,
  16. IGuidGenerator guidGenerator)
  17. {
  18. _bookRepository = bookRepository;
  19. _guidGenerator = guidGenerator;
  20. }
  21. public async Task SeedAsync(DataSeedContext context)
  22. {
  23. if (await _bookRepository.GetCountAsync() > 0)
  24. {
  25. return;
  26. }
  27. var book = new Book(
  28. id: _guidGenerator.Create(),
  29. name: "The Hitchhiker's Guide to the Galaxy",
  30. type: BookType.ScienceFiction,
  31. publishDate: new DateTime(1979, 10, 12),
  32. price: 42
  33. );
  34. await _bookRepository.InsertAsync(book);
  35. }
  36. }
  37. }
  • IDataSeedContributor 定义了 SeedAsync 方法用于执行 数据种子逻辑.
  • 通常检查数据库是否已经存在种子数据.
  • 你可以注入服务,检查数据播种所需的任何逻辑.

数据种子贡献者由ABP框架自动发现,并作为数据播种过程的一部分执行.

DataSeedContext

如果你的应用程序是多租户, DataSeedContext 包含 TenantId,因此你可以在插入数据或基于租户执行自定义逻辑时使用该值.

DataSeedContext 还包含用于从 IDataSeeder 传递到种子提供者的name-value配置参数.

模块化

一个应用程序可以具有多个种子数据贡献者(IDataSeedContributor)类. 任何可重用模块也可以实现此接口播种其自己的初始数据.

例如Identity模块有一个种子数据贡献者,它创建一个管理角色和管理用户并分配所有权限.

IDataSeeder

通常你不需要直接使用 IDataSeeder 服务,因为如果你从应用程序启动模板开始,该服务已经完成. 但是建议阅读以了解种子数据系统背后的设计.

IDataSeeder 是用于生成初始数据的主要服务. 使用它很容易;

  1. public class MyService : ITransientDependency
  2. {
  3. private readonly IDataSeeder _dataSeeder;
  4. public MyService(IDataSeeder dataSeeder)
  5. {
  6. _dataSeeder = dataSeeder;
  7. }
  8. public async Task FooAsync()
  9. {
  10. await _dataSeeder.SeedAsync();
  11. }
  12. }

你可以注入 IDataSeeder 并且在你需要时使用它初始化种子数据. 它内部调用 IDataSeedContributor 的实现去完成数据播种.

可以将命名的配置参数发送到 SeedAsync 方法,如下所示:

  1. await _dataSeeder.SeedAsync(
  2. new DataSeedContext()
  3. .WithProperty("MyProperty1", "MyValue1")
  4. .WithProperty("MyProperty2", 42)
  5. );

然后种子数据提供者可以通过前面解释的 DataSeedContext 访问这些属性.

如果模块需要参数,应该在模块文档中声明它. 例如Identity Module使用 AdminEmailAdminPassword 参数,如果你提供了(默认使用默认值).

在何处以及如何播种数据?

重要的是要了解在何处以及如何执行 IDataSeeder.SeedAsync().

On Production

应用程序启动模板带有一个YourProjectName**.DbMigrator** 项目(图中的Acme.BookStore.DbMigrator). 这是一个控制台应用程序,负责迁移数据库架构(关系数据库)和初始种子数据:

bookstore-visual-studio-solution-v3

控制台应用程序已经为你正确配置,它甚至支持多租户场景,其中每个租户拥有自己的数据库(迁移和必须的数据库).

当你将解决方案的新版本部署到服务器时,都需要运行这个DbMigrator应用程序. 它会迁移你的数据库架构(创建新的表/字段…)和播种正确运行解决方案的新版本所需的新初始数据. 然后就可以部署/启动实际的应用程序了.

即使你使用的是MongoDB或其他NoSQL数据库(不需要进行架构迁移),也建议使用DbMigrator应用程序为你的数据添加种子或执行数据迁移.

有这样一个单独的控制台应用程序有几个优点;

  • 你可以在更新你的应用程序之前运行它,所以你的应用程序可以在准备就绪的数据库上运行.
  • 与本身初始化种子数据相比,你的应用程序启动速度更快.
  • 应用程序可以在集群环境中正确运行(其中应用程序的多个实例并发运行). 在这种情况下如果在应用程序启动时播种数据就会有冲突.

On Development

我们建议以相同的方式进行开发. 每当你创建数据库迁移(例如使用EF Core Add-Migration 命令)或更改数据种子代码(稍后说明)时,请运行DbMigrator控制台应用程序.

你可以使用EF Core继续执行标准的 Update-Database 命令,但是它不会初始化种子数据.

On Testing

你可能想为自动测试初始化数据种子, 这需要使用 IDataSeeder.SeedAsync(). 在应用程序启动模板中,它在TestBase项目的YourProjectNameTestBaseModule类的OnApplicationInitialization方法中完成.

除了标准种子数据(也在生产中使用)之外,你可能还希望为自动测试添加其他种子数据. 你可以在测试项目中创建一个新的数据种子贡献者以处理更多数据.