数据植入
引言
使用数据库的某些应用程序(或模块)可能需要一些初始数据才能正常启动和运行。例如,必须在一开始就提供管理员用户和角色。否则,您将无法登录应用程序来创建新用户和角色。
数据植入对于测试目的也非常有用,这样您的自动化测试可以假设数据库中已有一些初始数据。
为什么需要数据植入系统?
虽然EF Core数据植入系统提供了一种方法,但它非常有限,并且不涵盖生产场景。此外,它仅适用于EF Core。
ABP提供了一个数据植入系统,该系统具有以下特点:
- 模块化:任何模块都可以在不相互了解和影响的情况下,静默地参与数据植入过程。这样,每个模块都可以植入自己的初始数据。
- 数据库无关性:它不仅适用于EF Core,还适用于其他数据库提供程序( 如 MongoDB )。
- 生产就绪:它解决了生产环境中的问题。请参见下面的“在生产环境中”部分。
- 依赖注入:它充分利用了依赖注入的优势,因此您可以在植入初始数据时使用任何内部或外部服务。实际上,您可以做的远不止数据植入。
IDataSeedContributor
IDataSeedContributor是一个接口,实现该接口以便向数据库植入数据。
示例:如果没有书籍,则向数据库植入一本初始书籍
using System;
using System.Threading.Tasks;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Guids;
namespace Acme.BookStore
{
public class BookStoreDataSeedContributor
: IDataSeedContributor, ITransientDependency
{
private readonly IRepository<Book, Guid> _bookRepository;
private readonly IGuidGenerator _guidGenerator;
private readonly ICurrentTenant _currentTenant;
public BookStoreDataSeedContributor(
IRepository<Book, Guid> bookRepository,
IGuidGenerator guidGenerator,
ICurrentTenant currentTenant)
{
_bookRepository = bookRepository;
_guidGenerator = guidGenerator;
_currentTenant = currentTenant;
}
public async Task SeedAsync(DataSeedContext context)
{
using (_currentTenant.Change(context?.TenantId))
{
if (await _bookRepository.GetCountAsync() > 0)
{
return;
}
var book = new Book(
id: _guidGenerator.Create(),
name: "银河系漫游指南",
type: BookType.ScienceFiction,
publishDate: new DateTime(1979, 10, 12),
price: 42
);
await _bookRepository.InsertAsync(book);
}
}
}
}
IDataSeedContributor定义了SeedAsync方法,用于执行数据植入逻辑。- 通常需要检查数据库,确认植入的数据是否已存在。
- 您可以注入服务并执行任何需要的数据植入逻辑。
数据植入贡献者会被ABP自动发现,并作为数据植入过程的一部分执行。
DataSeedContext
如果您的应用程序是 多租户 的,DataSeedContext 包含 TenantId ,因此您可以在插入数据或基于租户执行自定义逻辑时使用此值。
DataSeedContext 还包含名称-值样式的配置参数,用于从 IDataSeeder 传递给植入贡献者。
模块化
一个应用程序可以有多个数据植入贡献者( IDataSeedContributor )类。因此,任何可重用模块也可以实现此接口以植入其自己的初始数据。
例如,身份模块 有一个数据植入贡献者,用于创建管理员角色和用户,并分配所有权限。
IDataSeeder
您通常不需要直接使用
IDataSeeder服务,因为如果您从 应用程序启动模板 开始,它已经完成了。但建议阅读它以了解数据植入系统背后的设计。
IDataSeeder是用于植入初始数据的主要服务。使用起来非常简单:
public class MyService : ITransientDependency
{
private readonly IDataSeeder _dataSeeder;
public MyService(IDataSeeder dataSeeder)
{
_dataSeeder = dataSeeder;
}
public async Task FooAsync()
{
await _dataSeeder.SeedAsync();
}
}
您可以在需要时注入IDataSeeder并使用它来植入初始数据。它在内部调用所有IDataSeedContributor实现以完成数据植入。
可以向SeedAsync方法发送命名的配置参数,如下所示:
await _dataSeeder.SeedAsync(
new DataSeedContext()
.WithProperty("MyProperty1", "MyValue1")
.WithProperty("MyProperty2", 42)
);
然后,数据植入贡献者可以通过前面解释的DataSeedContext访问这些属性。
如果模块需要一个参数,应在 模块文档 中声明。例如,如果您提供了 AdminEmail 和 AdminPassword 参数,身份模块 可以使用这些参数(否则使用默认值)。
分离的工作单元
默认的植入将在一个工作单元中,并可能使用事务。如果有多个 IDataSeedContributor 或写入的数据过多,可能会导致数据库超时错误。
我们为IDataSeeder服务提供了一个扩展方法SeedInSeparateUowAsync,为每个IDataSeedContributor创建一个单独的工作单元。
public static Task SeedInSeparateUowAsync(this IDataSeeder seeder, Guid? tenantId = null, AbpUnitOfWorkOptions options = null, bool requiresNew = false)
在何处以及如何植入数据?
理解在何处以及如何执行IDataSeeder.SeedAsync()非常重要。
在生产环境中
应用程序启动模板 附带了一个YourProjectName**.DbMigrator项目(下图中为Acme.BookStore.DbMigrator),这是一个控制台应用程序**,负责迁移数据库架构(对于关系数据库)并植入初始数据:
这个控制台应用程序已为您正确配置。它甚至支持多租户场景,其中每个租户都有自己的数据库(迁移并植入所有必要的数据库)。
每当您将解决方案的新版本部署到服务器时,都应运行此DbMigrator应用程序。它将迁移您的数据库架构(创建新表/字段等)并植入新版本解决方案正常运行所需的初始数据。然后,您可以部署/启动实际的应用程序。
即使您使用MongoDB或其他NoSQL数据库(不需要架构迁移),也建议使用DbMigrator应用程序来植入数据或执行数据迁移。
拥有这样一个独立的控制台应用程序有几个优点:
- 您可以在更新应用程序之前运行它,这样您的应用程序将在准备好的数据库上运行。
- 与应用程序自行植入初始数据相比,您的应用程序启动速度更快。
- 您的应用程序可以在集群环境中正常运行(多个应用程序实例并发运行)。如果在应用程序启动时植入数据,在这种情况下会发生冲突。
在开发环境中
我们建议在开发环境中采用相同的方式。每当您创建数据库迁移(例如使用EF Core的Add-Migration命令)或更改数据植入代码时(稍后将解释),运行DbMigrator控制台应用程序。
您可以继续使用EF Core的标准
Update-Database命令,但如果您创建了新的种子数据,它将不会植入。
在测试环境中
您可能还希望在自动化测试中植入数据,因此希望使用 IDataSeeder.SeedAsync()。在 应用程序启动模板 中,这是在TestBase项目的YourProjectNameTestBaseModule类的OnApplicationInitialization方法中完成的。
除了标准的种子数据(在生产环境中也使用),您可能还希望植入自动化测试独有的额外数据。如果是这样,您可以在测试项目中创建一个新的数据植入贡献者,以拥有更多可供使用的数据。
抠丁客



