Skip to content
bitzorcas
中EN

Reference

Menu 目录模型、持久化与种子

讲透 SysModule 全局行、编译期元数据、IEntitySet store、种子自然键、九行交付资产和双 ORM 边界。

Last updated

Menu 的持久化核心只有一张全局表 SysModule。理解它的全局性、兼容字段和种子写回边界,是安全扩展菜单的前提。

1. 物理模型

ParentIdParentId

父 SysModule
Id / Code / Name

子 SysModule
Code / LinkUrl / OrderSort

孙 SysModule
IsMenu / Enabled / Scope

UX_SysModule_Code

IX_SysModule_ParentId

ParentId 只由应用约定表达自关联;源码没有数据库外键或级联约束。

2. 字段语义

字段当前语义约束
Id持久化标识EntityBase 提供
ParentId父行 Id;null/0 代表根长度 36,普通索引
CodeAuthorization 关系共享的模块码长度 50,唯一索引,可空
Name管理与导航显示名长度 50,可空
LinkUrl前端或 API 相对入口长度 100,可空
Area/Controller/Action旧 MVC 发现兼容字段各 2000,可空
Icon图标代码长度 100,可空
OrderSort同级显示顺序必填整数
Description管理说明长度 100,可空
IsMenu是否进入 navigation 投影必填布尔
Enabled是否进入所有树查询必填布尔
ScopeWeb=0、Host=1 的遗留平台范围必填整数

Scope 当前只存储和透传,MenuTreeBuilder 没有按登录平台过滤它。

3. 编译期 ORM 元数据

真实模型形状的关键部分
// ① 这是全局目录:没有 IsTenant 元数据,也不继承 BizEntityBase。
[BitzTable("SysModule", Description = "API/控制器/动作注册表")]
[BitzIndex("UX_SysModule_Code", "Code", IsUnique = true)]
[BitzIndex("IX_SysModule_ParentId", "ParentId")]
public sealed class MenuModuleCatalogRecord : EntityBase
{
// ② 父子关系只存 Id;数据库没有外键约束。
[BitzColumn(Length = 36)]
public string? ParentId { get; set; }
// ③ Code 与 Authorization 关系共享,应视为稳定业务键。
[BitzColumn(Length = 50)]
public string? Code { get; set; }
}

Generator 在编译期输出模型清单,SqlSugar 与 EF Core adapter 消费同一元数据;Menu Infrastructure 不引用具体 ORM 包。

4. Store 端口

IMenuStore 暴露三组能力:读取启用目录/详情/直接子节点、读取可见模块码,以及新增/更新/软删。它不暴露 IQueryable,也不让 Menu 直接消费 Authorization 的关系行。

Application handlers

IMenuStore

MenuStore

IEntitySet

IAuthorizationAssignmentReader

SqlSugar adapter

EF Core adapter

5. 查询行为

GetAllEnabledAsync 过滤 !IsDeleted && Enabled,再在内存按 OrderSort 排序。GetChildrenAsync 不要求 Enabled,删除用例因此仍能递归软删已禁用子节点。

通过窄端口读取全局目录
// ① 不从业务代码注入具体 DbContext 或 SqlSugarClient。
var enabled = await menuStore.GetAllEnabledAsync(cancellationToken);
// ② 目录是全局的;租户可见性在另一阶段用模块码过滤。
var visibleCodes = await menuStore.GetVisibleModuleCodesAsync(
currentUser.User.Roles,
cancellationToken);
// ③ Code 是跨 owner 连接键,Id 只用于目录父子关系。
var visible = enabled.Where(row => visibleCodes.Contains(row.Code!));

6. 更新与删除语义

Update 使用 UpdateWhereAsync 一次写入 ParentId、Name、Code、LinkUrl、Icon、OrderSort、IsMenu、Enabled 与 Scope。它不更新旧 MVC 字段和 Description。

Delete 不是物理删除,只把 IsDeleted=true。两个方法都没有检查影响行数,因此“读后被并发删除”的更新可能仍返回成功。

7. Seed step

MenuModuleSeedStep:

  • Order:220;
  • SeedId:sys_module;
  • CSV:220-sys_module.csv;
  • WhereColumns:Code;
  • Match:row.Code == source.Code;
  • 基类:ORM 中立 EntitySetCsvSeedStepBase<T>。
以稳定 Code 做 upsert
// ① Code 同时是数据库唯一键和 seed 自然键。
protected override string[] WhereColumns => [nameof(MenuModuleCatalogRecord.Code)];
// ② 适配器必须能翻译该表达式。
protected override Expression<Func<MenuModuleCatalogRecord, bool>> Match(
MenuModuleCatalogRecord source)
=> row => row.Code == source.Code;
// ③ Copy 不修改 Code,避免导入时更换跨 owner 身份。
protected override void Copy(MenuModuleCatalogRecord source, MenuModuleCatalogRecord target)
=> target.Name = source.Name;

最后一段是教学化裁剪,完整 Copy 还更新父级、路由、图标、排序、说明、状态、Scope 与软删标记。

8. 当前九行资产

顺序CodeLinkUrl
0operations/api/operations
1platformbilling/api/platform-billing
2files/api/files
3notifications/api/notifications
4webhooks/api/webhooks
5catalog/api/catalog
6tickets/api/tickets
7chat/api/chat
8sandbox/api/notes

它们都是根节点、IsMenu=true、Enabled=true、Scope=0。资产不代表这些链接一定有同名页面,也不证明对应 API 已具备相同权限。

9. 修改 Code 的风险

Code 被 Authorization 的角色—模块—权限关系当作 ModuleId 使用。直接改 Code 会让既有授权关系找不到目录项;seed 还会把新 Code 当成新行,而不是重命名旧行。

商业迁移必须同时处理目录唯一键、授权关系、缓存失效、回滚与兼容别名。单独改 CSV 不构成安全迁移。

10. ParentId 完整性

当前没有外键、父存在校验、环校验或最大深度。可以写入孤儿、自引用与 A→B→A。孤儿不会从根树输出;环可能导致递归溢出。

计划中的修复应在应用校验和数据库约束能力之间分层:父存在/自引用/祖先环在事务内校验,读侧仍保留深度和 visited 防御。

11. 测试证据与缺口

架构测试固定 Menu 程序集 ORM 中立、查询 Handler 只能走 Store、fail-closed 默认端口、owner-local 模型与生成清单。集成 parity 覆盖双 ORM 的新增、读取、可见码与软删。

当前没有专门固定 Code 重命名、父环、孤儿、唯一冲突、影响行数或 seed 与授权关系协同迁移的测试。

12. 核查命令

Terminal window
# 模型、索引与 Store 更新字段。
rg -n "BitzTable|BitzIndex|UpdateWhereAsync" src/Platform/Menu -g '*.cs'
# 资产应为表头加九行。
wc -l src/Platform/Menu/*Infrastructure/Seeders/Assets/220-sys_module.csv
# 模块不应引用具体 ORM adapter。
rg -n "SqlSugar|EntityFrameworkCore" src/Platform/Menu -g '*.cs' -g '*.csproj'

模块总览 · 管理与缓存

100%

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