You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.28 08:47:14