Skip to content
bitzorcas
中EN

Recipe

实战:声明式种子数据填充(Seed Data)与幂等初始化

掌握 BitzOrcas.Modern 种子数据初始化规范:实现 ISeedStep 契约、分层环境门禁(Global / Tenant / Demo / ProductionSafe)、依赖 DAG 拓扑解析与基于 --init-schema 的幂等自动化播种。

Last updated

在企业级多租户软件中,新环境交付、自动化 CI 测试以及日常本地开发均高度依赖数据库初始数据:平台内置语言币种、RBAC 系统角色与权限、示范律所与样本案件卷宗必须能够可靠、幂等地注入数据库。 传统的手工 SQL 脚本存在致命缺陷:反复执行时频发主键冲突(PK Violation)、无法表达模块间的数据依赖拓扑,更难以根据当前环境(生产环境 vs. 演示环境)进行精准的防污染阻断。

BitzOrcas.Modern 确立了基于 ISeedStep 与 ISeedRunner 的声明式分层种子体系:

  1. 统一步骤契约(ISeedStep):每个模块独立声明自身的种子步骤,包括全局唯一 SeedId、执行优先级 Order、版本号 Version 与强依赖 DependsOn;
  2. 四维环境作用域门禁(SeedScope):显式区隔 Global(全局通用)、Tenant(租户基线)、Demo(演示体验)与 ProductionSafe(生产安全增量),Demo 级别数据在 Staging 和 Production 环境被 Runner 自动阻断;
  3. DAG 依赖图拓扑排序:ISeedRunner 在启动时自动校验所有种子的前置依赖,确保外键父表数据先行就绪,杜绝隐式依赖导致的导入失败;
  4. 一键架构初始化命令行:通过 dotnet run --project src/Hosts/BitzOrcas.Api -- --init-schema 自动在表结构迁移完成后串联执行种子填充。

本指南以法律科技核心场景——在案件管理模块(BitzOrcas.Modules.Legal)中新增**“示范民商事案件卷宗种子步骤(LegalDemoMattersSeedStep)”**为例,演示完整的开发与注册过程。

种子数据分层拓扑与执行流程

所有环境所有环境仅 Dev / DemoProduction 环境

BitzOrcas 种子运行器 (ISeedRunner)

SeedScope 环境门禁检验

1. Global 平台基础数据
(Order: 100~199,国家/语言/字典)

2. Tenant & ProductionSafe
(Order: 200~299,角色/权限/根租户)

3. Demo 示范体验数据
(Order: 300+,示范案件/演练客户)

自动跳过 Demo 种子
(生产数据防污染红线)


第一步:编写模块级 ISeedStep 种子步骤

在业务模块的基础设施层实现 ISeedStep 接口。通过注入仓储或只读模型 Store 执行幂等写入,并指定 SeedScope.Demo 确保其仅在非生产环境中激活:

src/Modules/Legal/BitzOrcas.Modules.Legal/Seeders/LegalDemoMattersSeedStep.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using BitzOrcas.Domain.Abstractions;
using BitzOrcas.Domain.Results;
using BitzOrcas.Infrastructure.Seeders;
using BitzOrcas.Modules.Legal.Domain;
using Microsoft.Extensions.Logging;
namespace BitzOrcas.Modules.Legal.Seeders;
/// <summary>
/// 民商事案件示范卷宗种子数据步骤
/// </summary>
/// <remarks>
/// <para>负责在本地开发与演练环境中幂等注入示范案件卷宗数据。</para>
/// <para>依赖 <c>identity_roles</c> 种子,确保主办律师用户已先行就绪。</para>
/// </remarks>
public sealed class LegalDemoMattersSeedStep : ISeedStep
{
private readonly ICommandRepository<MatterIntake, string> _repository;
private readonly ILogger<LegalDemoMattersSeedStep> _logger;
/// <summary>
/// 初始化示范案件种子步骤
/// </summary>
/// <param name="repository">写侧命令仓储窄端口。</param>
/// <param name="logger">结构化日志记录器。</param>
public LegalDemoMattersSeedStep(
ICommandRepository<MatterIntake, string> repository,
ILogger<LegalDemoMattersSeedStep> logger)
{
_repository = repository;
_logger = logger;
}
/// <summary>
/// 执行顺序:300+ 属于业务演示数据层,位于基础设施与权限角色之后
/// </summary>
public int Order => 320;
/// <summary>
/// 全局唯一种子标识符 (建议 snake_case)
/// </summary>
public string SeedId => "legal_demo_matters";
/// <summary>
/// 声明作用域分类:Demo 级别在 Staging 与 Production 环境自动跳过
/// </summary>
public SeedScope SeedScope => SeedScope.Demo;
/// <summary>
/// 种子数据版本号:用于追踪变更
/// </summary>
public int Version => 1;
/// <summary>
/// 强依赖的种子标识符列表:必须在此步骤执行之前完成
/// </summary>
public string[] DependsOn => new[]
{
"identity_platform_tenants",
"identity_roles"
};
/// <summary>
/// 异步执行幂等播种
/// </summary>
public async Task ExecuteAsync(string environment, CancellationToken cancellationToken)
{
const string targetMatterId = "MAT-DEMO-2026-0001";
// 1. 严格幂等防重检查:若目标示范案件已存在,直接跳过
var existing = await _repository.FindAsync(targetMatterId, cancellationToken);
if (existing.IsSuccess)
{
_logger.LogInformation("[LegalDemoMattersSeedStep] 目标示范案件【{MatterId}】已存在,跳过播种。", targetMatterId);
return;
}
// 2. 构造示范聚合根并触发领域创建方法
var demoMatter = MatterIntake.Create(
id: targetMatterId,
tenantId: "tenant-alpha-lawfirm",
matterCode: "CIV-2026-0081",
title: "示范案例:上海华芯微电子商业秘密侵权纠纷",
clientId: "CLI-DEMO-001",
leadLawyerId: "USR-LAWYER-01",
claimAmount: 12000000.00m);
if (demoMatter.IsFailure)
{
_logger.LogError("[LegalDemoMattersSeedStep] 构造示范案件失败: {Error}", demoMatter.Error.Message);
return;
}
// 3. 提交持久化
var saveResult = await _repository.SaveAsync(demoMatter.Value, cancellationToken);
if (saveResult.IsSuccess)
{
_logger.LogInformation("[LegalDemoMattersSeedStep] 成功幂等播种示范案件: {MatterId}", targetMatterId);
}
else
{
_logger.LogError("[LegalDemoMattersSeedStep] 持久化示范案件失败: {Error}", saveResult.Error.Message);
}
}
}

第二步:在依赖注入容器中注册 SeedStep

在模块扩展或基础设施注册方法中,将该步骤注册为 ISeedStep:

src/Modules/Legal/BitzOrcas.Modules.Legal/LegalModuleExtensions.cs (片段)
using BitzOrcas.Infrastructure.Seeders;
using BitzOrcas.Modules.Legal.Seeders;
using Microsoft.Extensions.DependencyInjection;
namespace BitzOrcas.Modules.Legal;
public static class LegalModuleExtensions
{
public static IServiceCollection AddLegalModuleSeeders(this IServiceCollection services)
{
// 注册当前模块的种子步骤,由 ISeedRunner 自动扫描并参与拓扑排序
services.AddSingleton<ISeedStep, LegalDemoMattersSeedStep>();
return services;
}
}

第三步:执行数据库架构初始化与种子数据注入

在终端中通过 API Host 提供的命令行入口执行建库与播种:

执行数据库架构初始化与种子数据导入
# 1. 生产环境安全模式:自动初始化数据表结构,仅播种 Global、Tenant 与 ProductionSafe 种子
dotnet run --project src/Hosts/BitzOrcas.Api -- --init-schema
# 2. 仅校验或跳过种子播种 (用于只更新表结构)
dotnet run --project src/Hosts/BitzOrcas.Api -- --init-schema --no-seed
# 3. 开发环境重置并强制重新播种演示密码
USER__ADMIN__PASSWORD="YourStrongPassword123!" dotnet run --project src/Hosts/BitzOrcas.Api -- --init-schema --reset-demo-passwords

总结

基于 ISeedStep 的声明式种子体系具备高度工程自洽性:

  • 绝对幂等:实现层通过主键预检或 EntitySetCsvSeedStepBase 差量合并,多次执行绝不抛出主键冲突;
  • 生产绝对防污染:SeedScope.Demo 受到 Runner 硬编码环境门禁控制,生产部署时自动阻断任何演示数据;
  • 拓扑依赖安全:DependsOn 机制在启动期形成校验闭环,杜绝因模块加载顺序错乱而导致的外键约束崩溃。

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%