Master Data 当前不是一套带审批、版本、质量规则和管理后台的主数据管理平台。它物理拥有九张租户化参考目录行、八个 CSV seed step,应用共享的 IDataDictionaryResolver 实现,以及一个只读治理查询入口。Infrastructure 仍是数据 owner,但 Contracts 与 Application 项目已经存在,用于暴露治理报告。
1. 当前产品面
SysTranslationCatalogRecord 是第九张表,但没有 MasterData CSV/seed step;I18n 模块通过它提供翻译读写能力。
2. 物理结构
src/Platform/MasterData/└── BitzOrcas.Platform.MasterData.Infrastructure/ ├── Persistence/ # 9 个 CatalogRecord ├── Seeders/ # 8 个 ORM 中立 seed step │ └── Assets/ # 8 个 ^ 分隔 CSV ├── Dictionary/ │ └── MasterDataDictionaryResolver.cs ├── MasterDataDependencyInjection.cs ├── MasterDataFeatures.cs ├── MasterDataPermissions.cs └── MasterDataModule.cs它引用 Application、Infrastructure、Persistence.Models 与编译期 Metadata Generator,但不引用 SqlSugar/EF Core adapter 程序集。
3. 九类参考目录
| 行类型 | 表 | 当前用途 |
|---|---|---|
SysLanguageCatalogRecord | SysLanguage | 租户语言目录,I18n 读取 |
SysCountryCatalogRecord | SysCountry | ISO 国家/地区字段 |
SysIndustrySettingCatalogRecord | SysIndustrySetting | 行业树 |
SysGeneralCodeGroupCatalogRecord | SysGeneralCodeGroup | 字典组层级 |
SysGeneralCodeCatalogRecord | SysGeneralCode | 字典代码、组、层级和显示名 |
SysGeneralCodeTextCatalogRecord | SysGeneralCodeText | 字典本地化显示名 |
SysExchangeRateCatalogRecord | SysExchangeRate | 币种对日期快照 |
SysPublicHolidayCatalogRecord | SysPublicHoliday | 国家/年份节假日 |
SysTranslationCatalogRecord | SysTranslation | I18n 多级作用域翻译 |
所有行继承 BizEntityBase,都标注 IsTenant=true 与 IsSoftDelete=true。
4. 所有权迁移为什么重要
旧实现曾把这些行和 SqlSugar 专属 seeder 放在 Framework。当前架构测试要求它们保持删除,并由 MasterData owner 本地拥有。这样做让 Framework 不再承载业务参考数据,也让同一 seed step 可以通过 IEntitySet<T> 运行于不同 ORM。
MasterData 是 ADR 统一聚合规则的显式“多表参考目录例外”。这些 record 不是待恢复的 *Entity.cs + mapper 模式。
5. 宿主组合真相
API 项目显式引用 MasterData Infrastructure,并在 PersistenceRegistration 调用:
// ① ORM adapter 先通过编译期注册表提供九种 IEntitySet<T>。services.AddBitzOrcasGeneratedPersistenceAdapters(persistenceProvider);
// ② MasterData 自己只注册字典解析器,不注册仓储或端点。services.AddBitzOrcasMasterDataPlatform();
// ③ 通用种子框架与生成式 seed manifest 决定是否执行八个步骤。services.AddBitzOrcasSeeders(configuration);services.AddBitzOrcasGeneratedSeedSteps();JobHost 也引用该项目。仅有项目引用不表示会自动执行种子;仍取决于种子编排配置和宿主生命周期。
6. 字典读取真实 API
MasterDataDictionaryResolver 实现共享 IDataDictionaryResolver:
| 方法 | 行为 |
|---|---|
ResolveAsync(group, code, culture) | 命中返回显示名,未命中返回原 code |
ResolveListAsync(group, csvCodes, culture) | 按输入顺序解析逗号列表 |
ResolveBatchAsync(requests, culture) | 按 group 分组加载并返回 request→文本 |
ListEntriesAsync(group, culture) | 列出启用、未删除项并按 Sort 排序 |
InvalidateCacheAsync(group?) | 不论 group 参数,清除整个 dictionary tag |
原文档中的 ReferenceKey、ReferenceValue? 和 resolver.ResolveAsync(stableKey, ...) 并不存在,已删除。
7. 正确消费示例
// ① 业务行保存稳定 code;不要保存可能变化的显示文案。order.PaymentMethodCode = request.PaymentMethodCode;await orders.UpdateAsync(order, cancellationToken);
// ② 读取投影时使用真实签名 groupKey + code + culture。var displayName = await dictionaries.ResolveAsync( groupKey: "PAYMENTMETHODCODE", code: order.PaymentMethodCode, culture: currentCulture.Name, cancellationToken);
// ③ Resolver 未命中会返回原 code,因此 API 同时返回二者。return new PaymentMethodDto(order.PaymentMethodCode, displayName);未命中不是 null,也没有 EffectiveCulture 信息。若产品需要区分“翻译缺失”与“显示值恰好等于 code”,当前契约不足。
8. 当前种子资产状态
| 资产 | 数据行现状 |
|---|---|
| language | 2 |
| country | 0(仅表头) |
| industry | 0(仅表头) |
| general-code-group | 177 |
| general-code | 43,603 |
| general-code-text | 0(仅表头) |
| exchange-rate | 0(仅表头) |
| public-holiday | 347 |
因此“拥有种子步骤”不等于“每类目录都随产品交付数据”。具体完整性见种子资产与幂等导入。
9. 当前保证与不保证
| 可以依赖 | 不可假设 |
|---|---|
| 九种编译期持久化元数据 | 已有主数据管理 API/UI |
| seed step ORM 中立 | CSV 表头/内容严格校验通过 |
| 字典缓存键带 Tenant scope | group 定向失效只影响当前租户 |
| code 比较大小写不敏感 | culture 有规范化与回退链 |
| 缺失 code 返回原值 | 可识别缺失原因 |
| API Host 注册 resolver | Feature/Permission 已在入口强制 |
10. 已确认风险
SysGeneralCodeTextseed 的自然键和 Resolver 查询只用 Code+Language,忽略 Class/Group,存在碰撞。- 本地化结果以 Code 建字典,重复 Code 会抛
ToDictionary异常。 - 任意 group 的失效都会清整个
dictionarytag。 ListEntriesAsync绕过 30 分钟组缓存。- CSV 读取关闭严格 Header/MissingField 校验,关键列异常只告警。
- 汇率 CSV 是
UpdateDate,模型/WhereColumns 是拼写遗留ModifyTimee。 - 四个资产只有表头,但 seed 执行可以记录“upserted 0”并成功。
- 43,603 个 code 逐行查询/写入,冷启动成本和事务边界需压测。
11. 文档导航
12. 治理查询入口
GET /api/master-data/governance(资源 master-data/catalog、动作 Read)是一个 owner-local 只读治理报告,供运营了解各参考目录的规模和字典内容。查询参数:GroupCode(可选,选定某个字典组)、Culture(可选,BCP-47)、Search(可选)、EntryLimit(默认 100,上限 200)。
返回 MasterDataGovernanceReport:
Catalogs:九个目录的计数摘要(languages、countries、industries、dictionaryGroups、dictionaryEntries、dictionaryTexts、exchangeRates、publicHolidays、translations),每项给出 Total/Active/SeedManaged;DictionaryGroups与Entries:当选定GroupCode时,分页返回该组下的字典条目(含本地化 DisplayName);SelectedGroup、SelectedGroupTotal、Culture、EntryLimit、GeneratedAt。
读存储 MasterDataGovernanceReadStore 在 Infrastructure 层实现 IMasterDataGovernanceReadStore,全部通过 IEntitySet<T> 投影;未注册时降级为 fail-closed 的 UnavailableMasterDataGovernanceReadStore。错误码:MasterData.Governance.StoreUnavailable(ServiceUnavailable)、MasterData.Governance.InvalidQuery(Validation)。
这条入口与 Operations 的适配器/连接器报告同类:只读投影,不做主动健康探测,回答”有多少、是什么”,不回答”是否正确、是否最新”。
13. 源码核查
# 核对九张表、八个 seed step 与八个资产。find src/Platform/MasterData -type f | sort
# 确认默认 Host 的实际组合位置。rg -n "AddBitzOrcasMasterDataPlatform|AddBitzOrcasGeneratedSeedSteps" src/Hosts -g '*.cs'
# 旧 Framework/Entity 所有权应无命中。find src/Framework src/Platform/MasterData -path '*MasterData*Entity.cs' -o -path '*SqlSugar*MasterData*'