『贴代码』
项目介绍
项目精选
优选问答
【本期话题】更多
三人寄语更多
逻辑注解清晰的代码优于那些一眼看不明白的语法糖
点赞:0
时间与空间总是在换来换去,鱼和熊掌往往不可同得!
点赞:0
你连F12都不关注,你好意思说你是前端?
点赞:0
慎用redis的同步我的意见是redis都走异步!!!
点赞:0
需求就是系统的千年杀,相爱相杀那种!
点赞:0
抛开需求谈架构是无意义的事情!
点赞:0
能通过内网IP访问的,尽量不要使用域名访问!
点赞:1
设置后,UI不会变更,要不给他一个SetTimeout试试,原因自己想
点赞:0
对于答案来说,更重要的是找到答案的这个过程而不是答案本身!
点赞:0
测试没问题的不一定没问题,测试有问题的那肯定有问题!
点赞:2
多层级的排序和更新问题,看这篇就够了!
知乎也 2025-07-04 256 0 0
在多层级的表中,很多人第一反应就是递归查询,而我的第一反应就是不能用递归,会说用递归的肯定是没用过递归的,那查询效率!如果总数据不是很大的,估计你全部查询出来,然后自己排都比递归查询块!那有没有更好的办法呢?

更多特性PasteForm的介绍,请前往PasteForm操作文档查看 PasteForm操作文档与介绍


需求

有小伙伴说PasteForm示例代码中的权限层级设计得很好,希望用于其他地方,问下有没有更简单得办法
每次都复制修改,有点麻烦,还容易出错!

思考

小伙伴说得应该就是权限得多层级,一层套一层得那个功能,在多层级上,确实比递归好,而且在表格中做到了按序排序!!!
这个需求可不太好实现,如果用常规做法得话!
同一层得排序,子集排序。。。
之前我们不是写了一个归属查询么,就是用一个interface,所以我们这一次也这么干

提取接口

提取要用得数据,作为多层级得Entity得接口

    /// <summary>
    /// 支持 非递归多级查询,支持多级排序
    /// </summary>
    public interface ILevelEntity
    {
        /// <summary>
        /// 标识
        /// </summary>
        int Id { get; set; }

        /// <summary>
        /// 排序串
        /// </summary>
        string SortStr { get; set; }

        /// <summary>
        /// 层级
        /// </summary>
        int Level { get; set; }

        /// <summary>
        /// 排序 层级内排序
        /// </summary>
        int Sort { get; set; }

        /// <summary>
        /// 父级
        /// </summary>
        int FatherId { get; set; }

        /// <summary>
        /// 父级串 替换递归查询用
        /// </summary>
        string FatherStr { get; set; }

        /// <summary>
        /// 根级 大块
        /// </summary>
        int RootId { get; set; }
    }

上面得字段,我感觉只有RootId可有可无,其他得还是很重要得,非常重要!!!

提取逻辑

按照上面得,相当于把表得结构给确定了,如果你要写RoleInfo,则有

public class RoleInfo:ILevelEntity{
//具体实现 添加其他字段等
}

然后我们把之前RoleInfo的提取出来,用ILevenEntity来作为参数

  /// <summary>
  /// 多层级 非递归多层级新增和更新
  /// </summary>
  public class LevelHandler
  {
      /// <summary>
      /// 新增后 注意外部要保存下
      /// </summary>
      /// <param name="_dbContext"></param>
      /// <param name="TableName">应该是PropertyName</param>
      /// <param name="info"></param>
      public static async Task AfterAdd(IOneCarDbContext _dbContext, string TableName, ILevelEntity info)
      {
          info.SortStr = info.Sort.ToString().PadLeft(_persize, '0') + "0".PadLeft((_maxlevel - 1) * _persize, '0');
          if (info.FatherId <= 0)
          {
              info.FatherStr = $"{info.Id},";
              info.RootId = info.Id;
              return;
          }

          var dbType = typeof(IOneCarDbContext);
          var dbProperty = dbType.GetProperty(TableName);
          if (dbProperty != null)
          {
              var dbSet = dbProperty.GetValue(_dbContext) as IQueryable<ILevelEntity>;
              if (dbSet != null)
              {

                  var father = await dbSet.Where(x => x.Id == info.FatherId).AsNoTracking().FirstOrDefaultAsync();
                  if (father != null && father != default)
                  {
                      info.FatherStr = $"{father.FatherStr}{info.Id},";
                      info.Level = (father.Level + 1);
                      info.FatherId = father.Id;
                      info.RootId = father.RootId != 0 ? father.RootId : father.Id;
                      BuildSortStr(info, father);
                  }
                  else
                  {
                      info.FatherId = 0;
                  }

              }
          }
      }

      /// <summary>
      /// 更新后 注意外部要保存下
      /// </summary>
      /// <param name="_dbContext"></param>
      /// <param name="TableName">应该是PropertyName</param>
      /// <param name="info"></param>
      /// <returns></returns>
      public static async Task AfterUpdate(IOneCarDbContext _dbContext, string TableName, ILevelEntity info)
      {
          IQueryable<ILevelEntity> dbSet = null;
          var dbType = typeof(IOneCarDbContext);
          var dbProperty = dbType.GetProperty(TableName);
          if (dbProperty != null)
          {
              dbSet = dbProperty.GetValue(_dbContext) as IQueryable<ILevelEntity>;
          }
          if (dbSet == null)
          {
              return;
          }
          var old_father_str = info.FatherStr;
          var _chat_level = info.Level;

          if (info.FatherId != 0)
          {
              var father = await dbSet.Where(x => x.Id == info.FatherId).AsNoTracking().FirstOrDefaultAsync();
              if (father != null && father != default)
              {
                  info.Level = father.Level + 1;
                  info.FatherStr = $"{father.FatherStr}{info.Id},";
                  info.RootId = father.RootId;
                  BuildSortStr(info, father);
              }
              else
              {
                  info.FatherId = 0;
                  info.FatherStr = $"{info.Id},";
                  info.Level = 0;
                  info.RootId = info.Id;
                  BuildSortStr(info, null);
              }
          }
          else
          {
              info.FatherId = 0;
              info.FatherStr = $"{info.Id},";
              info.Level = 0;
              info.RootId = info.Id;
              BuildSortStr(info, null);
          }
          _chat_level = _chat_level - (info.Level);
          if (!String.IsNullOrEmpty(old_father_str))
          {
              var suns = await dbSet.Where(x => x.Id != info.Id && x.FatherStr.StartsWith(old_father_str)).ToListAsync();
              if (suns != null && suns.Count > 0)
              {
                  foreach (var item in suns)
                  {
                      item.FatherStr = item.FatherStr.Replace(old_father_str, info.FatherStr);
                      item.Level = item.Level + _chat_level;
                      item.RootId = info.RootId;
                      BuildFootSortStr(item, info);
                  }
              }
          }
      }

      /// <summary>
      /// 自动排序 基于同层级排序到后面,只有当当前是0的时候生效
      /// </summary>
      /// <param name="_dbContext"></param>
      /// <param name="TableName">应该是PropertyName</param>
      /// <param name="input"></param>
      /// <param name="step"></param>
      /// <returns></returns>
      public static async Task AutoSort(IOneCarDbContext _dbContext, string TableName, ILevelEntity input,int step=10)
      {
          if (input.Sort == 0)
          {
              var dbType = typeof(IOneCarDbContext);
              var dbProperty = dbType.GetProperty(TableName);
              if (dbProperty != null)
              {
                  var dbSet = dbProperty.GetValue(_dbContext) as IQueryable<ILevelEntity>;
                  if (dbSet != null)
                  {

                      var _max = await dbSet.Where(x => x.FatherId == input.FatherId)
                          .OrderByDescending(x => x.Sort)
                          .Select(x => x.Sort)
                          .FirstOrDefaultAsync();
                      input.Sort = _max + 10;
                  }
              }
          }
      }

      /// <summary>
      /// 每层最大支持的排序位数 5表示00000
      /// </summary>
      private const int _persize = 5;
      /// <summary>
      /// 最大层级个数,从0开始 比如4就是 0 1 2 3
      /// </summary>
      private const int _maxlevel = 4;

      /// <summary>
      /// 基于父级和当前自己的排序信息,构建对应的排序字符串
      /// </summary>
      /// <param name="_info"></param>
      /// <param name="_father"></param>
      private static void BuildSortStr(ILevelEntity _info, ILevelEntity _father)
      {
          if (_info.Level < _maxlevel)
          {
              if (_father == null)
              {
                  _info.SortStr = _info.Sort.ToString().PadLeft(_persize, '0') + "0".PadLeft(_persize * (_maxlevel - 1), '0');
              }
              else
              {
                  if (!string.IsNullOrEmpty(_father.SortStr))
                  {
                      _info.SortStr = _father.SortStr.Substring(0, (_father.Level + 1) * _persize) + _info.Sort.ToString().PadLeft(_persize, '0');
                      //补充尾巴
                      if (_info.Level < (_maxlevel - 1))
                      {
                          _info.SortStr += "0".PadLeft(_persize * (_maxlevel - 1 - _info.Level), '0');
                      }
                  }
              }
          }
      }

      /// <summary>
      /// 上级或者祖籍变更的替换,自己之后的不变,中间层级也不变 
      /// </summary>
      /// <param name="_info"></param>
      /// <param name="_father">是自己的上级或者祖级</param>
      private static void BuildFootSortStr(ILevelEntity _info, ILevelEntity _father)
      {
          var _old_sort_str = _info.SortStr;
          _info.SortStr = _father.SortStr.Substring(0, (_father.Level + 1) * _persize);
          _info.SortStr += _old_sort_str.Substring((_father.Level + 1) * _persize);
      }

  }

上面代码,其实其他都不用看,注意2个参数

        /// <summary>
        /// 每层最大支持的排序位数 5表示00000
        /// </summary>
        private const int _persize = 5;
        /// <summary>
        /// 最大层级个数,从0开始 比如4就是 0 1 2 3
        /// </summary>
        private const int _maxlevel = 4;

第一个参数_persize表示当层的最大排序,所以设置排序的时候要注意这个问题,目前上面的代码没有判断,是不完善的!!!
其实我感觉我们平时用的话3位数就够了!
你想啊,同一级有999个对象,多层级,已经是很大的数据了!
和_maxlevel组合后,就是sortstr字段的最大长度了!

如果使用

那么问题来了,如何调用?

  /// <summary>
      /// 这是一个新增案例
      ///</summary>
      /// <param name="input"></param>
      /// <returns></returns>
      public static async Task<RoleInfoDto> RoleAdd(IOneCarDbContext _dbContext, IObjectMapper ObjectMapper, RoleInfoAddDto input)
      {
          if (input.Sort == 0)
          {
              var _max = await _dbContext.RoleInfo.Where(x => x.FatherId == input.FatherId)
                  .OrderByDescending(x => x.Sort)
                  .Select(x => x.Sort)
                  .FirstOrDefaultAsync();
              input.Sort = _max + 10;
          }

          if (input.RoleType == 0)
          {
              if (String.IsNullOrEmpty(input.Model) || String.IsNullOrEmpty(input.Role))
              {
                  throw new PasteCodeException("新增权限的时候,模块和权限必填,请重新填写!");
              }
              var find = await _dbContext.RoleInfo.Where(x => x.RoleType == 0 && x.Model == input.Model && x.Role == input.Role).Select(x => x.Id).FirstOrDefaultAsync();
              if (find > 0)
              {
                  throw new PasteCodeException("已经有这个菜单项了,请勿重复添加!");
              }
          }
          if (input.RoleType == enummodels.EnumRoleType.Menu)
          {
              if (!string.IsNullOrEmpty(input.Path))
              {
                  if (input.FatherId == 0)
                  {
                      throw new PasteCodeException("非一级菜单,父级必选,请重新选择!");
                  }
                  var find = await _dbContext.RoleInfo.Where(x => x.RoleType == enummodels.EnumRoleType.Menu && x.Path == input.Path).Select(x => x.Id).FirstOrDefaultAsync();
                  if (find > 0)
                  {
                      throw new PasteCodeException("已经有这个菜单项了,请勿重复添加!");
                  }
              }
          }

          //这里最好用事务包裹下
          var info = ObjectMapper.Map<RoleInfoAddDto, RoleInfo>(input);
          info.IsEnable = true;//添加自定义
          _dbContext.Add(info);
          await _dbContext.SaveChangesAsync();
          await AfterAdd(_dbContext, nameof(RoleInfo), info);
          await _dbContext.SaveChangesAsync();

          var backinfo = ObjectMapper.Map<RoleInfo, RoleInfoDto>(info);
          return backinfo;
      }

      /// <summary>
      /// 这是一个更新案例
      ///</summary>
      /// <param name="input"></param>
      /// <returns></returns>
      public static async Task<RoleInfoDto> RoleUpdate(IOneCarDbContext _dbContext, IObjectMapper ObjectMapper, RoleInfoUpdateDto input)
      {
          if (input.Sort == 0)
          {
              var _max = await _dbContext.RoleInfo.Where(x => x.FatherId == input.FatherId)
                  .OrderByDescending(x => x.Sort)
                  .Select(x => x.Sort)
                  .FirstOrDefaultAsync();
              input.Sort = _max + 10;
          }

          if (input.RoleType == 0)
          {
              if (String.IsNullOrEmpty(input.Model) || String.IsNullOrEmpty(input.Role))
              {
                  throw new PasteCodeException("新增权限的时候,模块和权限必填,请重新填写!");
              }
              var find = await _dbContext.RoleInfo.Where(x => x.RoleType == 0 && x.Model == input.Model && x.Role == input.Role && x.Id != input.Id).Select(x => x.Id).FirstOrDefaultAsync();
              if (find > 0)
              {
                  throw new PasteCodeException("已经有这个菜单项了,请勿重复添加!");
              }
          }
          if (input.RoleType == enummodels.EnumRoleType.Menu)
          {
              if (!string.IsNullOrEmpty(input.Path))
              {
                  if (input.FatherId == 0)
                  {
                      throw new PasteCodeException("非一级菜单,父级必选,请重新选择!");
                  }
                  var find = await _dbContext.RoleInfo.Where(x => x.RoleType == enummodels.EnumRoleType.Menu && x.Path == input.Path && x.Id != input.Id).Select(x => x.Id).FirstOrDefaultAsync();
                  if (find > 0)
                  {
                      throw new PasteCodeException("已经有这个菜单项了,请勿重复添加!");
                  }
              }
          }

          var info = await _dbContext.RoleInfo.Where(x => x.Id == input.Id).FirstOrDefaultAsync();
          if (info == null || info == default)
          {
              throw new PasteCodeException("需要查询的信息不存在");
          }
          ObjectMapper.Map<RoleInfoUpdateDto, RoleInfo>(input, info);
          await AfterUpdate(_dbContext, nameof(RoleInfo), info);
          var backinfo = ObjectMapper.Map<RoleInfo, RoleInfoDto>(info);
          await _dbContext.SaveChangesAsync();
          return backinfo;
      }

比如我再另外一个表使用的

        /// <summary>
        /// 添加一个
        ///</summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "add" })]
        public async Task<string> CreateItemAsync(CateInfoAddDto input)
        {
            var _userid = base.ReadCurrentUserId();
            var info = ObjectMapper.Map<CateInfoAddDto, CateInfo>(input);
            await LevelHandler.AutoSort(_dbContext, nameof(CateInfo), info);
            _dbContext.Add(info);
            await _dbContext.SaveChangesAsync();
            await LevelHandler.AfterAdd(_dbContext, nameof(CateInfo), info);
            await _dbContext.SaveChangesAsync();
            //这里最好用事务包裹下,因为会保存2次
            return "新增成功";
        }

        /// <summary>
        /// 更新一个
        ///</summary>
        /// <param name="input"></param>
        /// <returns></returns>
        [HttpPost]
        [TypeFilter(typeof(RoleAttribute), Arguments = new object[] { "data", "edit" })]
        public async Task<string> UpdateItemAsync(CateInfoUpdateDto input)
        {
            var info = await _dbContext.CateInfo.Where(x => x.Id == input.Id).FirstOrDefaultAsync();
            if (info == null || info == default)
            {
                throw new PasteCodeException("需要查询的信息不存在", 404);
            }
            ObjectMapper.Map<CateInfoUpdateDto, CateInfo>(input, info);
            await LevelHandler.AutoSort(_dbContext, nameof(CateInfo), info);
            await LevelHandler.AfterUpdate(_dbContext, nameof(CateInfo), info);
            await _dbContext.SaveChangesAsync();
            return "修改成功";
        }

是不是很方便了?

赶紧试试吧!

评论列表
知乎也
0 256 0
快捷注册
热门推荐更多
PasteSpider
;
最新动态
  • 104.****.143 正在查看 PasteSpider之--路由列表-私有仓库-环境配置-的介绍 !
  • 126.****.131 正在查看 PasteSpider的测试环境之在Docker中安装centos7并设定SSH的密码 !
  • 126.****.131 正在查看 PasteSpider的测试环境之在Docker中安装centos7并设定SSH的密码 !
  • 125.****.37 正在查看 PasteBuilder的进阶用法 !
  • 125.****.37 正在查看 PasteBuilder的进阶用法 !
  • 58.****.136 正在查看 在Centos7中安装Nginx !
  • 58.****.136 正在查看 在Centos7中安装Nginx !
  • 47.****.132 正在查看 PasteSpider更新摘要,持续更新 !
  • 47.****.132 正在查看 PasteSpider更新摘要,持续更新 !
  • 104.****.102 正在查看 PasteSpider支持Ubuntu(podman)啦! !
  • 104.****.102 正在查看 PasteSpider支持Ubuntu(podman)啦! !
欢迎加入QQ讨论群 296245685 [PasteSpider]介绍 [PasteForm]介绍 @2022-2023 PasteCode.cn 版权所有 ICP证 闽ICP备2021013869号-2