Skip to content
bitzorcas
中EN

Guide

Master Data 种子资产与幂等导入

八个 CSV seed step 的顺序、自然键、更新策略、宽松解析、资产现状、性能与安全发布门禁。

Last updated

MasterData seed 使用 EntitySetCsvSeedStepBase<T> 做逐行 upsert。它是 ORM 中立的,但“可重复执行”只在自然键、资产表头和内容都正确时成立。

1. 执行路径

IEntitySetEntitySet seed stepCsvSeedReaderSeed orchestratorIEntitySetEntitySet seed stepCsvSeedReaderSeed orchestratoralt[不存在][已存在]loop[每一行]ExecuteAsync(environment)读取 owner embedded CSV宽松映射的 rowsFirstOrDefault(Match)AddCopy managed fieldsUpdate

它不是 bulk merge,也没有在该基类中显式建立整步事务。

2. 八个步骤

OrderSeedId自然键资产行数
110sys_languageCode2
120sys_countryAlpha20
130sys_industry_settingCode0
140sys_general_code_groupCode177
141sys_general_codeCode + Class43,603
142sys_general_code_textCode + Language0
150sys_exchange_rateBase + Target + ModifyTimee0
160sys_public_holidayCountryCode + DayDate347

所有步骤继承默认 SeedScope.ProductionSafe、Version=1、DependsOn=[]。Order 提供排序,但没有显式依赖图。

3. 宽松 CSV 解析

CsvSeedReader 默认使用 ^ 分隔符、忽略大小写/BOM、允许缺字段,并为常见值类型提供空值宽松转换。HeaderValidated 与 MissingFieldFound 都关闭。

是否是否

CSV 行

表头匹配属性?

宽松类型转换

属性保留 default

WhereColumn 超过半数为空?

只写 Warning

继续 upsert

因此 warning 不是门禁,调用方不能把进程退出码 0 当作资产契约通过。

4. 汇率表头漂移实例

资产表头写 UpdateDate,记录属性和 seed key 写 ModifyTimee。当前资产没有数据行,所以不会触发关键列告警;未来直接追加行时,日期可能解析为默认值并仅告警。

Terminal window
# 这两处当前预期输出不同名称。
head -1 src/Platform/MasterData/BitzOrcas.Platform.MasterData.Infrastructure/Seeders/Assets/150-sys_exchange_rate.csv
rg -n "ModifyTimee|UpdateDate" src/Platform/MasterData -g '*.cs' -g '*.csv'

GA 应迁移为正确的领域命名并提供兼容策略,而不是继续扩散拼写错误。

5. 幂等 upsert 的边界

seed step 的核心语义
// ① Match 必须与数据库唯一键和 CSV 业务键一致。
var existing = await entities.FirstOrDefaultAsync(
row => row.Code == source.Code && row.Class == source.Class,
cancellationToken);
// ② 新行保留资产 ID;缺失 ID 时基类分配字符串 "0"。
if (existing is null)
await entities.AddAsync(source, cancellationToken);
else
{
// ③ Copy 只更新资产管理字段,保留持久化 ID/运行时审计字段。
CopyManagedFields(source, existing);
await entities.UpdateAsync(existing, cancellationToken);
}

如果自然键不完整,第二次执行可能覆盖错误行;如果数据库唯一键更严格,可能插入重复或冲突。

6. CodeText 碰撞

SysGeneralCodeText 数据库索引包含 Tenant+Code+Class+Language,seed matcher 却只有 Code+Language。相同 code 在两个 class 中会被视为同一行并覆盖。该资产当前为空,风险尚未被数据触发,但必须在导入本地化文本前修复。

7. 空资产的含义

country、industry、code-text、exchange-rate 只有表头。读取器返回 0 行,步骤记录 upserted 0 并成功。正确运维表达应是:

  • schema/step 已存在;
  • 产品未随包提供对应目录内容;
  • 环境若依赖该目录,应有最小行数/权威版本门禁;
  • 不能用“seed job 成功”证明数据可用。

8. 资产变更流程

资产发布检查示例
// ① 发布前解析同一 embedded asset,并校验表头、自然键和行数。
var report = await validator.ValidateAsync(new SeedAssetContract
{
SeedId = "sys_general_code",
RequiredHeaders = ["Code", "Class", "DisplayName"],
UniqueKey = ["TenantId", "Code", "Class"],
MinimumRows = 40_000
}, cancellationToken);
// ② 警告在 CI/生产启动门禁中升级为失败。
if (report.Errors.Count > 0 || report.Warnings.Count > 0)
throw new SeedAssetValidationException(report);

该 validator 是建议设计,当前源码尚未实现。

9. 更新与删除语义

Copy 会覆盖显示、状态和部分继承字段,但 CSV 删除一行不会软删数据库旧行。换言之,这是 append/update,不是 desired-state reconciliation。

如要下线 code,必须在资产显式保留行并设置 IsDeleted/IsActive,或建设带变更计划的删除流程。删除已被业务表引用的 code 前要做引用扫描和兼容期。

10. ID 与引用稳定性

现有大资产携带历史字符串 ID。业务应引用稳定 code,不应依赖 seed 内部 ID,除非已有数据库 FK 契约。若修改自然键相当于新增事实,不能只改 CSV 字段,否则旧行留存、新行插入。

发布前比较新增、更新、失活、自然键变更和可能孤儿引用,生成可审计 diff。

11. 性能与事务

43,603 个 general code 当前逐行 FirstOrDefault 后 Add/Update,可能产生大量往返。需要在 SqlSugar/EF Core、空库/已有库、不同网络延迟下建立基线。

优化不能牺牲:

  • 双 ORM 等价;
  • 业务键幂等;
  • 分批事务和失败恢复;
  • 取消传播;
  • 可读的进度与错误报告;
  • AOT/编译期元数据约束。

12. 生产发布清单

  1. 权威来源、许可证、版本、负责人已记录;
  2. 表头与属性完整匹配;
  3. 自然键在资产内唯一,与索引/Match 一致;
  4. 行数、空值、父子引用、枚举范围通过;
  5. diff 经业务 owner 审核;
  6. 两个 ORM 做空库和重复执行;
  7. 大资产性能在窗口内;
  8. 失败恢复、备份和回滚已演练;
  9. 运行后验证最小行数与关键样本;
  10. 文档和 source facts 同步。

13. 测试命令

Terminal window
# Seed 架构与端到端测试。
dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj --filter MasterData
dotnet test tests/BitzOrcas.Integration.Tests/BitzOrcas.Integration.Tests.csproj --filter Seed
# 统计资产规模,识别只有表头的文件。
for f in src/Platform/MasterData/*/Seeders/Assets/*.csv; do wc -l "$f"; done

模块总览 · 字典与缓存

100%

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