资讯详情

Blazor Admin 关联表怎么处理?Navigate、Include、Join 实战

📅 2026/10/8 19:32:23 | 华诺云谱 👁 阅读
Blazor Admin 关联表怎么处理?Navigate、Include、Join 实战
后台开发里关联表几乎是必选项订单要显示客户名、文章要选专栏、用户要分配角色、菜单要挂父子。这篇讲 EasyAdminBlazor 里关联数据从建模到查询、展示、编辑的完整做法。一、四种关联四种 Navigate 写法FreeSql 用[Navigate]描述关联框架的实体已经给出了四类范例。1. 多对一最常用文章属于一个专栏[Table(Nameblog_article)]publicpartialclassArticle:ApprovalEntityFull{/// summary随笔专栏/summary[DisplayName(随笔专栏)][Required]publiclong?ClassifyId{get;set;}publicClassifyClassify{get;set;}default!;}这里ClassifyId是外键列Classify是导航属性。注意仓库里的示例实体并没有在Classify上写[Navigate(nameof(ClassifyId))]——FreeSql 会按命名约定自动识别ClassifyId与Classify的关系。如果你的命名不符合约定就显式标注[Navigate(nameof(ClassifyId))]publicClassifyClassify{get;set;}default!;2. 一对多反向导航partialclassSysUser{[Navigate(nameof(SysRoleUser.UserId))][JsonIgnore]publicListSysRoleUserRoleUsers{get;set;}[];}3. 多对多publicclassSysRole:Entity{[JsonIgnore][Navigate(ManyToManytypeof(SysRoleUser))]publicListSysUserUsers{get;set;}[];[JsonIgnore][Navigate(ManyToManytypeof(SysRoleMenu))]publicListSysMenuMenus{get;set;}[];}中间表是显式定义的实体publicclassSysRoleMenu{publiclongRoleId{get;set;}publiclongMenuId{get;set;}publicSysRoleRole{get;set;}default!;publicSysMenuMenu{get;set;}default!;}4. 自关联树形publicpartialclassSysMenu:EntityCreated,IHasParentIdlong{[Navigate(nameof(ParentId))][JsonIgnore]publicSysMenu?Parent{get;set;}[Navigate(nameof(ParentId))][JsonIgnore]publicListSysMenuChilds{get;set;}[];publiclongParentId{get;set;}}注意导航属性上的[JsonIgnore]这些是 ORM 层面的对象图序列化给前端时容易产生循环引用或体积膨胀所以框架统一加了忽略。二、查询Include 与 IncludeMany1. 多对一用 IncludeprivatevoidOnBeforeQuery(AdminQueryEventArgsArticlee){e.Select.Include(aa.Classify);}2. 多对多用 IncludeMany角色页需要把这个角色有哪些菜单一起查出来因为点击行时要回显勾选状态privatevoidOnBeforeQuery(AdminQueryEventArgsSysRolee){e.Select.WhereIf(!admin.IsAdmin,xx.IsAdministratorfalse);if(!e.IsExport){e.Select.IncludeMany(xx.Menus);}}两行代码包含两个经验WhereIf做条件过滤非管理员看不到超级管理员角色导出时跳过IncludeManyIsExport判断避免了导出上千行时把每行的菜单集合都拉一遍的灾难性性能问题AdminQueryEventArgs的注释里明确写了这一点。3. 不带导航的查询默认更轻如果只是要显示分类 Id而不是分类名称就不要Include。导航属性的加载是有成本的尤其是在列表页。三、表格展示关联名称关联名称不能直接绑ClassifyId要用Template取导航属性TableColumn bind-Fieldcontext.ClassifyId Filterabletrue Template Contextvv.Row.Classify?.ClassifyName/Template FilterTemplate FilterProvider AdminSelectEntityFilter TItemClassify GetTextx x.ClassifyName / /FilterProvider /FilterTemplate /TableColumn三件事同时完成列绑定的是外键字段ClassifyId这样排序、筛选都作用在数据库列上显示的是导航属性Classify?.ClassifyName列头筛选用AdminSelectEntityFilter选一个专栏。?.不是多余的如果某行的外键指向一个已被删除的分类导航属性就是 null加了?.才不会抛异常。按关联字段模糊搜索想按分类名称模糊搜索用AdminSelectEntityFilterGenericTableColumn bind-Fieldcontext.Title Filterabletrue Searchabletrue FilterTemplate FilterProvider AdminSelectEntityFilterGeneric TItemClassify TKeystring FilterActionFilterAction.Contains GetValueaa.ClassifyName GetTextx x.ClassifyName / /FilterProvider /FilterTemplate /TableColumnGetValue决定筛选值取什么这里取分类名FilterAction.Contains决定是模糊匹配。四、表单里选关联三个组件1. AdminSelectEntity只绑 IdAdminSelectEntity TItemClassify TKeylong? bind-ValueModel.ClassifyId GetTexte e.ClassifyName ShowSearch /参数源码[Parameter]publicExpressionFuncTItem,bool?Where{get;set;}[Parameter]publicFuncTItem,stringGetText{get;set;}xx?.ToString()??string.Empty;[Parameter]publicFuncTItem,string?GetValue{get;set;}[Parameter]publicTimeSpanCacheDuration{get;set;}TimeSpan.FromSeconds(30);注意CacheDuration默认是 30 秒。2. AdminSelectTable绑整个实体需要把选中的实体对象也带回来比如要读它的多个字段时用这个AdminSelectTable TItemSysUser bind-ValueIdselectedUserId GetText(u u.Nickname) TableColumns TableColumn bind-Fieldcontext.Username Text用户名 / TableColumn bind-Fieldcontext.Nickname Text昵称 / /TableColumns /AdminSelectTable它同时支持bind-Value完整实体和bind-ValueId主键弹窗里的列可以自定义。3. 是否需要数据权限AdminSelectEntity/AdminMultiSelect/AdminSelectTable都支持UseDataPermission用法与AdminTable一致AdminSelectEntity TItemSysUser TKeylong bind-ValueIdModel.AuditorId GetTextu u.Nickname UseDataPermissiontrue /打开之后能选谁就受当前用户的数据权限约束避免选到一个自己根本无权查看的人。五、多对多编辑角色分配菜单的真实实现Pages/Role.razor是一个完整的多对多编辑范例左边角色表格右边菜单树。1. 点击行回显已选privateasyncTaskOnClickRowCallback(SysRolerow){selectrow;varroleMenuIdsrow.Menus.Select(mm.Id).ToList();menuSelectionTree?.UpdateTreeSelection(roleMenuIds);awaitInvokeAsync(StateHasChanged);}row.Menus之所以有值是因为查询时IncludeMany(x x.Menus)已经加载了。2. 保存更新关联表[AdminButton(alloc_menus)][OperationLog(修改角色菜单权限)]privateasyncTaskOnSaveMenu(){if(select!null){if(select.Menusnull||select.Menus.Count0){awaitSwalService.Warning(CommonLocalizer[请至少选择一个菜单权限]);return;}await_repo.UpdateAsync(select);awaitadmin.InvalidatePermissionCacheAsync();awaitToastService.Success(CommonLocalizer[保存数据],CommonLocalizer[权限保存成功]);}}三个关键点[AdminButton(alloc_menus)]方法级权限没有这个按钮权限直接拦截[OperationLog(修改角色菜单权限)]审计留痕InvalidatePermissionCacheAsync()角色-菜单关系变了权限缓存必须立即失效否则用户要等 30 分钟才生效。3. 删除前的引用检查删除角色前要确认没人用它varroleIdse.Items.Select(xx.Id).ToList();varusedCountawait_repo.Orm.SelectSysRoleUser().Where(xroleIds.Contains(x.RoleId)).CountAsync();if(usedCount0){awaitSwalService.Error(CommonLocalizer[该角色已分配给用户不能删除]);e.Canceltrue;}这是关联表最典型的引用完整性处理先查中间表有人用就拦住删除。六、IgnoreSearchColumns导航属性不能当筛选列这是关联表最容易踩的坑。页面里只要有导航集合就要在AdminTable上声明忽略AdminTable TItemSysUser TKeylong IgnoreSearchColumnsx new { x.Roles, x.Messages } ... /原因在第 05 篇讲过框架会把表格的筛选条件翻译成数据库条件导航属性不是数据库列传下去会报无法匹配 xxx。IgnoreSearchColumns在OnParametersSetAsync的最前面就会被解析GetIgnoredPropertyNames()保证首次渲染抢跑查询时列表已经就绪。七、性能关联查询的几个取舍做法代价建议列表页Include多对一每条多一次 JOIN需要显示关联名称时才加列表页IncludeMany多对多数据量随关联数放大只在确实需要时用如角色页回显菜单导出时Include行数 × 关联数非常慢用IsExport跳过关联筛选AdminSelectEntityFilterGeneric子查询/EXISTS关联表的外键列建索引导航属性被当作筛选字段直接报错加入IgnoreSearchColumns一条经验列表页尽量只查需要的列关联名称用投影或按需加载编辑页拿到完整对象后导航加载可以更宽松。八、主子表明细怎么办先说实话框架没有专门的主从表/明细表组件。EditTemplate只是一个普通的 Blazor 组件插槽。如果需要一张订单下面有多条明细可行的做法是!-- 订单编辑模板里嵌一个绑定当前订单的明细表格 -- AdminTable TItemOrderItem TKeylong OnBeforeQueryOnBeforeQueryItems ... / code { private void OnBeforeQueryItems(AdminQueryEventArgsOrderItem e) { e.Select.Where(x x.OrderId Model.Id); // 只查当前单据的明细 } }要注意两点主单据尚未保存Id 0时没有明细可挂通常做法是先保存主表再编辑明细明细的保存/删除要自己处理与主表的从属关系级联删除、必填校验等。这属于业务编排框架提供的是组件能力而不是固定模式。九、常见问题现象原因处理关联名称为空导航属性没加载在OnBeforeQuery里Include报无法匹配 xxx导航属性参与了筛选加入IgnoreSearchColumns多对多保存没生效只改了主表没更新中间表参考Role.razorIncludeManyUpdateAsync改了角色/菜单权限不生效权限缓存未失效调用InvalidatePermissionCacheAsync()导出慢到超时导出时加载了导航集合用IsExport判断后跳过删除时报外键错误关联数据未清理删除前查中间表/子表或做级联十、小结关联表在 EasyAdminBlazor 里的处理套路可以总结成四句话建模外键用xxxId导航用[Navigate]约定优于配置命名不符时显式标注查询需要展示才Include/IncludeMany导出时跳过展示列绑外键、显示导航值、筛选用AdminSelectEntityFilter(Generic)编辑选单个关联用AdminSelectEntity多对多用中间表 IncludeManyUpdateAsync并记得让缓存失效。再加一条纪律所有导航属性都放进IgnoreSearchColumns。这一条能省掉大量无法匹配 xxx的排查时间。如果你正在用 .NET 10 Blazor 做后台关联表是绕不开的场景。EasyAdminBlazor 在实体导航、查询加载、关联选择器、多对多编辑上都给了现成做法可以照着改。文档https://easyadmin.wang-zhan.com.cn/doc源码https://gitee.com/gudufy/EasyAdminBlazor
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑