Skip to content
bitzorcas
中EN

Guide

Master Data 字典、缓存与本地化

IDataDictionaryResolver 的真实契约、查询、租户缓存、negative cache、预热贡献者、本地化覆盖、碰撞与失效边界。

Last updated

字典解析器是 MasterData 当前唯一面向应用的运行时服务。它把持久化 code 转为显示文本,但没有提供管理、发布或强类型目录 API。

1. 注册与依赖

AddBitzOrcasMasterDataPlatform 使用 TryAddScoped 注册 IDataDictionaryResolver → MasterDataDictionaryResolver。已有自定义注册不会被覆盖。

Resolver 依赖:

  • IEntitySet<SysGeneralCodeCatalogRecord>;
  • IEntitySet<SysGeneralCodeTextCatalogRecord>;
  • ICacheStore;
  • ICacheKeyBuilder;
  • ICurrentTenant(与 BuildForTenant 对齐显式租户键);
  • Logger。

类型标注 [BitzCacheArea(master-data)]。可选启动 / Host 重建由 MasterDataDictionaryCacheWarmupContributor 写入有限键空间(启用字典组 × 默认 culture);见缓存区域目录。

2. 单值解析

是否是否

group + code + culture

tenant cache key

cache hit?

case-insensitive map

查询 active general codes by Group

可选查询 Code+Language texts

code exists?

display text

original code

group/code 空白会抛 ArgumentException,未命中不是 Result failure,而是原 code。

3. 使用真实契约

单值与列表解析
// ① groupKey 对应 SysGeneralCode.Group,不是 Group 表的对象 ID。
var statusText = await resolver.ResolveAsync(
groupKey: "CASESTATUS",
code: caseRecord.StatusCode,
culture: "zh-CN",
cancellationToken);
// ② 列表输入按逗号拆分、Trim、移除空项并保持顺序。
var tags = await resolver.ResolveListAsync(
groupKey: "CASETAG",
commaSeparatedCodes: caseRecord.TagCodes,
culture: "en-US",
cancellationToken);

调用方仍应返回 code,不能只返回解析后的自然语言文本。

4. 批量解析

ResolveBatchAsync 按 request.GroupKey 分组,每个组加载一次映射,再以 DictionaryResolveRequest 为键返回结果。

批量投影
// ① 先收集一页结果所需的字典请求,避免逐字段调用。
var requests = page.Items
.SelectMany(item => item.RequiredDictionaryValues())
.Distinct()
.ToArray();
// ② Resolver 内部按 group 加载;未命中仍返回原 code。
var labels = await resolver.ResolveBatchAsync(
requests, currentCulture.Name, cancellationToken);
return page.Map(item => item.WithLabels(labels));

空 requests 返回空字典;源码没有显式 null 参数防护。

5. ListEntries 的不同路径

ListEntriesAsync 直接查询 codes.ListAsync,按 Sort 内存排序,再查询本地化文本。它没有复用 30 分钟的 group cache。

这意味着 Resolve 与 ListEntries 在高并发下具有不同的数据库负载和一致性窗口。产品若用 ListEntries 渲染每次请求的下拉框,应增加缓存或调用方缓存,并保持租户/文化维度。

6. 缓存策略

当前缓存参数
// ① 正常字典组缓存 30 分钟,并加入 10% jitter。
private static readonly CachePolicy DictionaryPolicy = new(TimeSpan.FromMinutes(30))
{
// ② 缺失组使用 1 分钟 negative TTL,降低穿透。
NegativeTtl = TimeSpan.FromMinutes(1),
JitterRatio = 0.10,
AreaTag = "dictionary"
};

key 通过 CacheScope.Tenant 构造,包含 group 和 culture(null 变成 default)。这依赖正确的当前租户上下文。

7. 本地化覆盖

默认映射来自 GeneralCode 的 DisplayName。culture 非空且不等于小写精确值 default 时,再查询 Text 表,以 Code 对应 DisplayName 覆盖。

没有:

  • culture 大小写/BCP-47 规范化;
  • zh-Hans-CN → zh-Hans → zh 回退;
  • 默认语言目录回退;
  • Class/Group 约束;
  • 翻译状态/有效期。

这套字典本地化与 I18n 的 Translation scope fallback 是两条不同链路。

8. Code/Class 碰撞

GeneralCode 数据库唯一键是 Tenant+Code+Class,但 Resolver 最终映射键只有 Code。查询先按 Group 缩小范围,仍不能保证同组不同 Class 没有相同 Code。

Text 查询只按 entryCodes.Contains(text.Code) && text.Language == culture,忽略 Class、Group 和 tenant 显式字段。若返回同 Code 多行,ToDictionary(text => text.Code) 会抛异常。

Group A / Class X / Code 01

map key 01

Group A / Class Y / Code 01

duplicate-key exception or wrong override

GA 前必须统一数据库、seed 与 resolver 的复合身份。

9. 失效语义

InvalidateCacheAsync(groupKey) 无论是否传 group,都调用 RemoveByTagAsync("dictionary")。group 只用于日志。若 CacheStore tag 是全局范围,一次租户/组变更可能清除所有字典缓存;即使实现局部化,也不是精确 group 失效。

当前 MasterData 没有写入用例或集成事件触发该方法。谁修改数据、何时失效、跨实例如何传播尚未形成闭环。

10. 读取一致性

30 分钟 TTL 意味着数据库变更与显示之间可能有延迟。管理能力未来应采用:

  1. 事务内更新记录和 outbox;
  2. 提交后发布带 Tenant/Group/Version 的失效事件;
  3. 每实例幂等消费并精确失效;
  4. Resolve 返回可选 catalog version;
  5. 监控失效延迟与旧版本命中。

仅缩短 TTL 会增加数据库压力,不能替代一致性协议。

11. 安全与权限

字典读取本身不检查 MasterDataPermissions.DictionaryRead 或 Feature。多数显示解析可能作为内部服务无需逐次授权,但“列出全部字典”可能泄漏内部/受保护值,公开 Endpoint 仍需权限、Feature、IsInternal 过滤和速率限制。

当前 Resolver 过滤 IsActive/IsDeleted,却没有过滤继承的 IsEnabled/IsInternal,也没有检查 IsProtect。

12. 测试矩阵

  • 命中、未命中返回原 code、空参数;
  • 列表顺序、空项、重复 code;
  • batch 跨 group 与重复 request;
  • tenant cache key 隔离;
  • default/null/大小写/父 culture;
  • 同 group 跨 class 重复 code;
  • text 重复与缺失;
  • negative cache 与到期;
  • 精确/全量/跨实例失效;
  • 两个 ORM 的 Contains 表达式等价;
  • IsInternal/IsEnabled/IsProtect 公开策略。

13. 核查命令

Terminal window
# 当前查询键和缓存策略。
rg -n "GetOrCreateAsync|CacheScope.Tenant|RemoveByTagAsync|ToDictionary|entryCodes.Contains" src/Platform/MasterData -g '*.cs'
# 检查重复 Code/Class/Group 数据需要在导入验证器或数据库报告中执行。
rg -n "UX_SysGeneralCode|SysGeneralCodeText" src/Platform/MasterData -g '*.cs'

模块总览 · 测试与 GA

100%

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