news 2026/10/8 19:32:19

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Blazor Admin 关联表怎么处理?Navigate、Include、Join 实战

后台开发里关联表几乎是必选项:订单要显示客户名、文章要选专栏、用户要分配角色、菜单要挂父子。这篇讲 EasyAdminBlazor 里关联数据从建模到查询、展示、编辑的完整做法。


一、四种关联,四种 Navigate 写法

FreeSql 用[Navigate]描述关联,框架的实体已经给出了四类范例。

1. 多对一(最常用)

文章属于一个专栏:

[Table(Name="blog_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]publicList<SysRoleUser>RoleUsers{get;set;}=[];}

3. 多对多

publicclassSysRole:Entity{[JsonIgnore][Navigate(ManyToMany=typeof(SysRoleUser))]publicList<SysUser>Users{get;set;}=[];[JsonIgnore][Navigate(ManyToMany=typeof(SysRoleMenu))]publicList<SysMenu>Menus{get;set;}=[];}

中间表是显式定义的实体:

publicclassSysRoleMenu{publiclongRoleId{get;set;}publiclongMenuId{get;set;}publicSysRoleRole{get;set;}=default!;publicSysMenuMenu{get;set;}=default!;}

4. 自关联(树形)

publicpartialclassSysMenu:EntityCreated,IHasParentId<long>{[Navigate(nameof(ParentId))][JsonIgnore]publicSysMenu?Parent{get;set;}[Navigate(nameof(ParentId))][JsonIgnore]publicList<SysMenu>Childs{get;set;}=[];publiclongParentId{get;set;}}

注意导航属性上的[JsonIgnore]:这些是 ORM 层面的对象图,序列化给前端时容易产生循环引用或体积膨胀,所以框架统一加了忽略。


二、查询:Include 与 IncludeMany

1. 多对一用 Include

privatevoidOnBeforeQuery(AdminQueryEventArgs<Article>e){e.Select.Include(a=>a.Classify);}

2. 多对多用 IncludeMany

角色页需要把"这个角色有哪些菜单"一起查出来(因为点击行时要回显勾选状态):

privatevoidOnBeforeQuery(AdminQueryEventArgs<SysRole>e){e.Select.WhereIf(!admin.IsAdmin,x=>x.IsAdministrator==false);if(!e.IsExport){e.Select.IncludeMany(x=>x.Menus);}}

两行代码包含两个经验:

  • WhereIf做条件过滤:非管理员看不到超级管理员角色;
  • 导出时跳过IncludeMany:IsExport判断避免了"导出上千行时把每行的菜单集合都拉一遍"的灾难性性能问题(AdminQueryEventArgs的注释里明确写了这一点)。

3. 不带导航的查询默认更轻

如果只是要显示"分类 Id"而不是"分类名称",就不要Include。导航属性的加载是有成本的,尤其是在列表页。


三、表格展示关联名称

关联名称不能直接绑ClassifyId,要用Template取导航属性:

<TableColumn @bind-Field="context.ClassifyId" Filterable="true"> <Template Context="v">@v.Row.Classify?.ClassifyName</Template> <FilterTemplate> <FilterProvider> <AdminSelectEntityFilter TItem="Classify" GetText="x => x.ClassifyName" /> </FilterProvider> </FilterTemplate> </TableColumn>

三件事同时完成:

  1. 列绑定的是外键字段ClassifyId(这样排序、筛选都作用在数据库列上);
  2. 显示的是导航属性Classify?.ClassifyName;
  3. 列头筛选用AdminSelectEntityFilter选一个专栏。

?.不是多余的:如果某行的外键指向一个已被删除的分类,导航属性就是 null,加了?.才不会抛异常。

按关联字段模糊搜索

想按"分类名称"模糊搜索,用AdminSelectEntityFilterGeneric:

<TableColumn @bind-Field="context.Title" Filterable="true" Searchable="true"> <FilterTemplate> <FilterProvider> <AdminSelectEntityFilterGeneric TItem="Classify" TKey="string" FilterAction="FilterAction.Contains" GetValue="a=>a.ClassifyName" GetText="x => x.ClassifyName" /> </FilterProvider> </FilterTemplate> </TableColumn>

GetValue决定筛选值取什么(这里取分类名),FilterAction.Contains决定是模糊匹配。


四、表单里选关联:三个组件

1. AdminSelectEntity:只绑 Id

<AdminSelectEntity TItem="Classify" TKey="long?" @bind-Value="Model.ClassifyId" GetText="e => e.ClassifyName" ShowSearch />

参数(源码):

[Parameter]publicExpression<Func<TItem,bool>>?Where{get;set;}[Parameter]publicFunc<TItem,string>GetText{get;set;}=x=>x?.ToString()??string.Empty;[Parameter]publicFunc<TItem,string>?GetValue{get;set;}[Parameter]publicTimeSpanCacheDuration{get;set;}=TimeSpan.FromSeconds(30);

注意CacheDuration默认是 30 秒。

2. AdminSelectTable:绑整个实体

需要把选中的实体对象也带回来(比如要读它的多个字段)时用这个:

<AdminSelectTable TItem="SysUser" @bind-ValueId="@selectedUserId" GetText="@(u => u.Nickname)"> <TableColumns> <TableColumn @bind-Field="context.Username" Text="用户名" /> <TableColumn @bind-Field="context.Nickname" Text="昵称" /> </TableColumns> </AdminSelectTable>

它同时支持@bind-Value(完整实体)和@bind-ValueId(主键),弹窗里的列可以自定义。

3. 是否需要数据权限

AdminSelectEntity/AdminMultiSelect/AdminSelectTable都支持UseDataPermission,用法与AdminTable一致:

<AdminSelectEntity TItem="SysUser" TKey="long" @bind-ValueId="Model.AuditorId" GetText="u => u.Nickname" UseDataPermission="true" />

打开之后,"能选谁"就受当前用户的数据权限约束,避免选到一个自己根本无权查看的人。


五、多对多编辑:角色分配菜单的真实实现

Pages/Role.razor是一个完整的多对多编辑范例:左边角色表格,右边菜单树。

1. 点击行,回显已选

privateasyncTaskOnClickRowCallback(SysRolerow){select=row;varroleMenuIds=row.Menus.Select(m=>m.Id).ToList();menuSelectionTree?.UpdateTreeSelection(roleMenuIds);awaitInvokeAsync(StateHasChanged);}

row.Menus之所以有值,是因为查询时IncludeMany(x => x.Menus)已经加载了。

2. 保存,更新关联表

[AdminButton("alloc_menus")][OperationLog("修改角色菜单权限")]privateasyncTaskOnSaveMenu(){if(select!=null){if(select.Menus==null||select.Menus.Count==0){awaitSwalService.Warning(CommonLocalizer["请至少选择一个菜单权限"]);return;}await_repo.UpdateAsync(select);awaitadmin.InvalidatePermissionCacheAsync();awaitToastService.Success(CommonLocalizer["保存数据"],CommonLocalizer["权限保存成功"]);}}

三个关键点:

  • [AdminButton("alloc_menus")]:方法级权限,没有这个按钮权限直接拦截;
  • [OperationLog("修改角色菜单权限")]:审计留痕;
  • InvalidatePermissionCacheAsync():角色-菜单关系变了,权限缓存必须立即失效,否则用户要等 30 分钟才生效。

3. 删除前的引用检查

删除角色前要确认没人用它:

varroleIds=e.Items.Select(x=>x.Id).ToList();varusedCount=await_repo.Orm.Select<SysRoleUser>().Where(x=>roleIds.Contains(x.RoleId)).CountAsync();if(usedCount>0){awaitSwalService.Error(CommonLocalizer["该角色已分配给用户,不能删除"]);e.Cancel=true;}

这是关联表最典型的"引用完整性"处理:先查中间表,有人用就拦住删除。


六、IgnoreSearchColumns:导航属性不能当筛选列

这是关联表最容易踩的坑。页面里只要有导航集合,就要在AdminTable上声明忽略:

<AdminTable TItem="SysUser" TKey="long" IgnoreSearchColumns="x => new { x.Roles, x.Messages }" ... />

原因在第 05 篇讲过:框架会把表格的筛选条件翻译成数据库条件,导航属性不是数据库列,传下去会报"无法匹配 xxx"。

IgnoreSearchColumns在OnParametersSetAsync的最前面就会被解析(GetIgnoredPropertyNames()),保证首次渲染抢跑查询时列表已经就绪。


七、性能:关联查询的几个取舍

做法代价建议
列表页Include多对一每条多一次 JOIN需要显示关联名称时才加
列表页IncludeMany多对多数据量随关联数放大只在确实需要时用(如角色页回显菜单)
导出时Include行数 × 关联数,非常慢用IsExport跳过
关联筛选(AdminSelectEntityFilterGeneric)子查询/EXISTS关联表的外键列建索引
导航属性被当作筛选字段直接报错加入IgnoreSearchColumns

一条经验:列表页尽量只查需要的列,关联名称用投影或按需加载;编辑页拿到完整对象后,导航加载可以更宽松。


八、主子表(明细)怎么办

先说实话:框架没有专门的"主从表/明细表"组件。EditTemplate只是一个普通的 Blazor 组件插槽。

如果需要"一张订单下面有多条明细",可行的做法是:

<!-- 订单编辑模板里嵌一个绑定当前订单的明细表格 --> <AdminTable TItem="OrderItem" TKey="long" OnBeforeQuery="OnBeforeQueryItems" ... /> @code { private void OnBeforeQueryItems(AdminQueryEventArgs<OrderItem> e) { e.Select.Where(x => x.OrderId == Model.Id); // 只查当前单据的明细 } }

要注意两点:

  • 主单据尚未保存(Id = 0)时没有明细可挂,通常做法是"先保存主表,再编辑明细";
  • 明细的保存/删除要自己处理与主表的从属关系(级联删除、必填校验等)。

这属于业务编排,框架提供的是组件能力而不是固定模式。


九、常见问题

现象原因处理
关联名称为空导航属性没加载在OnBeforeQuery里Include
报"无法匹配 xxx"导航属性参与了筛选加入IgnoreSearchColumns
多对多保存没生效只改了主表,没更新中间表参考Role.razor:IncludeMany+UpdateAsync
改了角色/菜单权限不生效权限缓存未失效调用InvalidatePermissionCacheAsync()
导出慢到超时导出时加载了导航集合用IsExport判断后跳过
删除时报外键错误关联数据未清理删除前查中间表/子表,或做级联

十、小结

关联表在 EasyAdminBlazor 里的处理套路可以总结成四句话:

  1. 建模:外键用xxxId,导航用[Navigate](约定优于配置,命名不符时显式标注);
  2. 查询:需要展示才Include/IncludeMany,导出时跳过;
  3. 展示:列绑外键、显示导航值、筛选用AdminSelectEntityFilter(Generic);
  4. 编辑:选单个关联用AdminSelectEntity,多对多用中间表 +IncludeMany+UpdateAsync,并记得让缓存失效。

再加一条纪律:所有导航属性都放进IgnoreSearchColumns。这一条能省掉大量"无法匹配 xxx"的排查时间。


如果你正在用 .NET 10 + Blazor 做后台,关联表是绕不开的场景。EasyAdminBlazor 在实体导航、查询加载、关联选择器、多对多编辑上都给了现成做法,可以照着改。

  • 文档:https://easyadmin.wang-zhan.com.cn/doc
  • 源码:https://gitee.com/gudufy/EasyAdminBlazor
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/8 19:27:46

theme-factory - desert-rose

沙漠玫瑰&#xff08;Desert Rose&#xff09; 一个柔和精致、带有灰粉色调的主题&#xff0c;非常适合优雅的演示。 调色板 灰玫瑰粉&#xff08;Dusty Rose&#xff09;&#xff1a;#d4a5a5 - 柔和的主色陶土色&#xff08;Clay&#xff09;&#xff1a;#b87d6d - 质朴的点缀…

作者头像 李华
网站建设 2026/10/8 19:26:47

基于springboot宠物领养系统-计算机毕设 附源码【课程设计】94217

摘要城市流浪动物数量持续增长&#xff0c;传统线下领养模式面临信息不对称、流程不透明、匹配效率低下等多重困境。宠物领养需求与救助资源之间缺乏有效连接渠道&#xff0c;导致大量宠物无法找到合适家庭。互联网技术发展为解决这一问题提供了新路径。基于Spring Boot框架构建…

作者头像 李华
网站建设 2026/10/8 19:25:56

impeccable:基于PRODUCT.md的声明式交付契约验证工具

1. 项目概述&#xff1a;从一个词出发&#xff0c;理解“impeccable”在开发者工具链中的真实定位 “impeccable”这个词本身是英文形容词&#xff0c;意为“无可挑剔的、完美无瑕的”&#xff0c;常用于描述工艺、服务或执行质量。但放在当前技术语境下——尤其是与 npx 、…

作者头像 李华
网站建设 2026/10/8 19:24:39

具身智能中的协同机理研究(43):TVA-World云边端协同部署详解

前沿技术探索&#xff1a;TVA智能体&#xff08;简称TVA&#xff09;TVA智能体&#xff08;亦称“AI智能体视觉”&#xff09;是依托Transformer架构与“因式智能体”理论构建的新型工业视觉系统&#xff0c;也是当前最具代表性的具身视觉技术之一。它有机融合深度强化学习&…

作者头像 李华
网站建设 2026/10/8 19:23:53

趣博思AI官网在做一个“反直觉”的决定:不让你一键生成

一个让产品经理皱眉的设计 如果你是一个产品经理&#xff0c;你会觉得趣博思AI官网的设计“不合理”。 它没有把“一键生成”放在最显眼的位置。它没有用“三秒出稿”这样的文案。它甚至在使用建议里明确写着&#xff1a;不建议一次性生成全文&#xff0c;推荐分章节推进。 在A…

作者头像 李华