项目

数据植入

引言

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

数据植入对于测试目的也非常有用,这样您的自动化测试可以假设数据库中已有一些初始数据。

为什么需要数据植入系统?

虽然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访问这些属性。

如果模块需要一个参数,应在 模块文档 中声明。例如,如果您提供了 AdminEmailAdminPassword 参数,身份模块 可以使用这些参数(否则使用默认值)。

分离的工作单元

默认的植入将在一个工作单元中,并可能使用事务。如果有多个 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),这是一个控制台应用程序**,负责迁移数据库架构(对于关系数据库)并植入初始数据:

bookstore-visual-studio-solution-v3

这个控制台应用程序已为您正确配置。它甚至支持多租户场景,其中每个租户都有自己的数据库(迁移并植入所有必要的数据库)。

每当您将解决方案的新版本部署到服务器时,都应运行此DbMigrator应用程序。它将迁移您的数据库架构(创建新表/字段等)并植入新版本解决方案正常运行所需的初始数据。然后,您可以部署/启动实际的应用程序。

即使您使用MongoDB或其他NoSQL数据库(不需要架构迁移),也建议使用DbMigrator应用程序来植入数据或执行数据迁移。

拥有这样一个独立的控制台应用程序有几个优点:

  • 您可以在更新应用程序之前运行它,这样您的应用程序将在准备好的数据库上运行。
  • 与应用程序自行植入初始数据相比,您的应用程序启动速度更快
  • 您的应用程序可以在集群环境中正常运行(多个应用程序实例并发运行)。如果在应用程序启动时植入数据,在这种情况下会发生冲突。

在开发环境中

我们建议在开发环境中采用相同的方式。每当您创建数据库迁移(例如使用EF Core的Add-Migration命令)或更改数据植入代码时(稍后将解释),运行DbMigrator控制台应用程序。

您可以继续使用EF Core的标准Update-Database命令,但如果您创建了新的种子数据,它将不会植入。

在测试环境中

您可能还希望在自动化测试中植入数据,因此希望使用 IDataSeeder.SeedAsync()。在 应用程序启动模板 中,这是在TestBase项目的YourProjectNameTestBaseModule类的OnApplicationInitialization方法中完成的。

除了标准的种子数据(在生产环境中也使用),您可能还希望植入自动化测试独有的额外数据。如果是这样,您可以在测试项目中创建一个新的数据植入贡献者,以拥有更多可供使用的数据。

在本文档中