MasterData seed 使用 EntitySetCsvSeedStepBase<T> 做逐行 upsert。它是 ORM 中立的,但“可重复执行”只在自然键、资产表头和内容都正确时成立。
1. 执行路径
它不是 bulk merge,也没有在该基类中显式建立整步事务。
2. 八个步骤
| Order | SeedId | 自然键 | 资产行数 |
|---|---|---|---|
| 110 | sys_language | Code | 2 |
| 120 | sys_country | Alpha2 | 0 |
| 130 | sys_industry_setting | Code | 0 |
| 140 | sys_general_code_group | Code | 177 |
| 141 | sys_general_code | Code + Class | 43,603 |
| 142 | sys_general_code_text | Code + Language | 0 |
| 150 | sys_exchange_rate | Base + Target + ModifyTimee | 0 |
| 160 | sys_public_holiday | CountryCode + DayDate | 347 |
所有步骤继承默认 SeedScope.ProductionSafe、Version=1、DependsOn=[]。Order 提供排序,但没有显式依赖图。
3. 宽松 CSV 解析
CsvSeedReader 默认使用 ^ 分隔符、忽略大小写/BOM、允许缺字段,并为常见值类型提供空值宽松转换。HeaderValidated 与 MissingFieldFound 都关闭。
因此 warning 不是门禁,调用方不能把进程退出码 0 当作资产契约通过。
4. 汇率表头漂移实例
资产表头写 UpdateDate,记录属性和 seed key 写 ModifyTimee。当前资产没有数据行,所以不会触发关键列告警;未来直接追加行时,日期可能解析为默认值并仅告警。
# 这两处当前预期输出不同名称。head -1 src/Platform/MasterData/BitzOrcas.Platform.MasterData.Infrastructure/Seeders/Assets/150-sys_exchange_rate.csvrg -n "ModifyTimee|UpdateDate" src/Platform/MasterData -g '*.cs' -g '*.csv'GA 应迁移为正确的领域命名并提供兼容策略,而不是继续扩散拼写错误。
5. 幂等 upsert 的边界
// ① 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. 生产发布清单
- 权威来源、许可证、版本、负责人已记录;
- 表头与属性完整匹配;
- 自然键在资产内唯一,与索引/Match 一致;
- 行数、空值、父子引用、枚举范围通过;
- diff 经业务 owner 审核;
- 两个 ORM 做空库和重复执行;
- 大资产性能在窗口内;
- 失败恢复、备份和回滚已演练;
- 运行后验证最小行数与关键样本;
- 文档和 source facts 同步。
13. 测试命令
# Seed 架构与端到端测试。dotnet test tests/BitzOrcas.Architecture.Tests/BitzOrcas.Architecture.Tests.csproj --filter MasterDatadotnet 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