如何优化C#中foreach循环及递归转换方法以提升性能?
递归菜单DTO转换性能优化方案
原代码核心性能瓶颈
- 递归层级中重复发起数据库查询:每次处理子菜单都单独查询数据库,IO开销随菜单层级指数级增长
- 重复获取当前用户:递归的每一层都调用
manager.FindById查询用户,完全冗余 - 权限校验可能存在重复查询:若
HasPermission内部涉及数据库操作,循环中多次调用会放大开销 - 每层递归都做排序:子菜单排序重复执行,增加不必要的计算
优化方案
1. 预加载所有菜单数据,消除重复DB查询
一次性拉取所有菜单数据到内存,按父ID分组存储,后续所有子菜单获取都在内存中完成,彻底避免递归中的数据库IO:
// 一次性获取所有菜单并按父ID分组 var allMenuItems = db.ApplicationMenuItems.ToList(); var menuGroups = allMenuItems.GroupBy(x => x.ParentMenuItemId) .ToDictionary(g => g.Key, g => g.ToList());
2. 提前获取用户权限,避免重复权限校验开销
在最外层一次性获取用户及其所有权限ID,将权限校验从可能的DB查询转为内存中的HashSet快速查找:
// 外层获取当前用户与权限集合 var currentUserId = User.Identity.GetUserId<int>(); var currentUser = manager.FindById(currentUserId); // 扩展方法:一次性获取用户所有权限ID存入HashSet(O(1)查找性能) var userPermissions = currentUser.GetAllPermissionIds();
3. 重构递归方法,基于内存数据构建DTO
修改递归逻辑,依赖预加载的内存数据和权限集合,不再重复查询DB和用户:
private List<ApplicationMenuItemDTO> ConvertToDTO(List<ApplicationMenuItem> menuItems, Dictionary<int?, List<ApplicationMenuItem>> menuGroups, HashSet<int> userPermissions) { var dtoList = new List<ApplicationMenuItemDTO>(); foreach (var mi in menuItems) { // 内存中快速校验权限 if (mi.PermissionId == null || userPermissions.Contains(mi.PermissionId.Value)) { var dto = new ApplicationMenuItemDTO { MenuItemId = mi.MenuItemId, ParentMenuItemId = mi.ParentMenuItemId, Name = mi.Name, NamePlural = mi.NamePlural, DisplayName = mi.DisplayName, DisplayNamePlural = mi.DisplayNamePlural, DeepLink = mi.DeepLink, ApplicationPageId = mi.ApplicationPageId, Description = mi.Description, PermissionId = mi.PermissionId, IsLocked = mi.IsLocked, Url = mi.Url, Rank = mi.Rank, MenuItemTypeID = mi.MenuItemTypeID, // 从内存分组中获取子菜单,递归转换 MenuItem = menuGroups.TryGetValue(mi.MenuItemId, out var children) ? ConvertToDTO(children, menuGroups, userPermissions) : new List<ApplicationMenuItemDTO>() }; dtoList.Add(dto); } } return dtoList.OrderBy(x => x.Rank).ToList(); }
4. 优化外层调用逻辑
// 对外暴露的入口方法 public List<ApplicationMenuItemDTO> GetMenuDTOs() { // 预加载菜单并按Rank预排序,避免递归中重复排序 var allMenuItems = db.ApplicationMenuItems.OrderBy(x => x.Rank).ToList(); var menuGroups = allMenuItems.GroupBy(x => x.ParentMenuItemId) .ToDictionary(g => g.Key, g => g.ToList()); // 获取用户权限 var currentUserId = User.Identity.GetUserId<int>(); var currentUser = manager.FindById(currentUserId); var userPermissions = currentUser.GetAllPermissionIds(); // 获取根菜单(根据业务调整ParentMenuItemId的判断逻辑) var rootMenus = menuGroups.TryGetValue(null, out var roots) ? roots : new List<ApplicationMenuItem>(); return ConvertToDTO(rootMenus, menuGroups, userPermissions); }
5. 额外优化建议
- 若
HasPermission方法内部存在数据库查询,必须重构为GetAllPermissionIds这类一次性获取权限集合的方法,利用HashSet的O(1)查找性能 - 菜单层级极深时,可将递归改为迭代实现,避免栈溢出风险
- 若菜单数据不频繁变更,可加入内存缓存,进一步减少DB查询次数
内容的提问来源于stack exchange,提问作者user3683619
相关产品推荐
相关产品推荐

