Query.list()多次调用后挂掉问题

本文介绍了一个使用jQuery实现的前端分页功能遇到的问题及其解决办法。在多次点击翻页后出现卡顿现象,通过逐步排查定位到Hibernate查询部分存在问题,并给出了具体的代码修改方案。
用jquery 写了一个前台分页 ajax 请求,返回 json 类型,
在点下一页第六七次的时候卡住了
错误很诡异: 1不报异常 2不返回请求 无奈半天先用排除法也就(Sysout方法)逐层排查 ,
最后发现到DAO执行此方法时的return q.list();这个地方出了问题, 这就是copy代码的弊端啊。贴上代码以作提醒

public List<UserGeneral> getLimitUsers(int pageNum, int pageSize)
throws Exception {
String hql = "select * from UserGeneral order by username desc";
Session s = this.getHibernateTemplate().getSessionFactory().openSession();//将这里换成.getCurrentSession();解决
//Session s = this.getHibernateTemplate().getSessionFactory().getCurrentSession();
Query q = s.createQuery("from UserGeneral order by username");
q.setFirstResult(pageNum);
q.setMaxResults(pageSize);
return q.list();
}
}

openSession();每次都创建一个新的会话
getCurrentSession();获取当前存在的
using Mds.Customization.CodeRule; using Mds.Master.Entities; using Mds.Master.Operation; using Mds.Master.Repositories; using Mds.Toolkit; using Microsoft.EntityFrameworkCore; using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Volo.Abp; using Volo.Abp.Application.Dtos; using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Repositories; namespace Mds.Master { public class MasterOperationService( IOperationRepository operationRepo, IRepository<SubOperationEntity, Guid> subOperationRepo ) : MdsCrudAppService< OperationEntity, OperationGetOutputDto, Dictionary<string, object>, Guid, OperationGetListInput, OperationCreateInput, OperationUpdateInput >(operationRepo), IMasterOperationService { public async override Task<OperationGetOutputDto> CreateAsync(OperationCreateInput input) { if (string.IsNullOrWhiteSpace(input.OperationCode)) { string code = await CodeService.GenCodeAsync(CodeRuleConstant.OperationCodeRule); input.OperationCode = code; } await base.EnsureEntityUniqueAsync(GuidGenerator.Create(), input, "OperationName"); return await base.CreateAsync(input); } public async override Task<OperationGetOutputDto> UpdateAsync(Guid id, OperationUpdateInput input) { if (string.IsNullOrWhiteSpace(input.OperationCode)) { string code = await CodeService.GenCodeAsync(CodeRuleConstant.OperationCodeRule); input.OperationCode = code; } await base.EnsureEntityUniqueAsync(id, input, "OperationName"); return await base.UpdateAsync(id, input); } #region 外键关联代码 protected override void MapToEntity(OperationUpdateInput updateInput, OperationEntity entity) { base.MapToEntity(updateInput, entity); SetIdForGuids(entity); } protected override void SetIdForGuids(OperationEntity entity) { base.SetIdForGuids(entity); // 1. 筛选当前主工序下的所有子工序(同一层级,保持原始顺序) var subOperations = entity.SubOperations .Where(sub => sub.OperationId == entity.Id) .ToList().OrderBy(sub => sub.StepSequence); // 2. 处理子工序(不排序,保留原始顺序和StepSequence) foreach (var subOp in subOperations) { // 原有逻辑:生成子工序ID + 绑定外键(保留) if (subOp.Id == Guid.Empty) EntityHelper.TrySetId(subOp, () => GuidGenerator.Create(), true); subOp.OperationId = entity.Id; // 原有校验:子工序父编码不能等于主工序编码(保留): Todo: 这里需要在用户编辑页面时就处理,而且这里是多级的不是单级,递归的项目不应该显示在用户的选择项中。 if (subOp.SubOperationId == entity.Id) { throw new BusinessException(MdsDomainErrorCodes.OperationCheck); } // 移除了StepSequence的重排逻辑,保留原始值 } // 资源和收费项外键处理(保持不变) foreach (var item in entity.OperationResource) { if (item.Id == Guid.Empty) EntityHelper.TrySetId(item, () => GuidGenerator.Create(), true); item.OperationId = entity.Id; } foreach (var item in entity.OperationChargeItems) { if (item.Id == Guid.Empty) EntityHelper.TrySetId(item, () => GuidGenerator.Create(), true); item.OperationId = entity.Id; } } public async Task<TreeResponseDto<OperationNodeDto>> GetOperationById(OperationGetListInput input) { // 1. 入参校验 if (input.OperationCode.IsNullOrEmpty()) { return new TreeResponseDto<OperationNodeDto> { TotalCount = 0, Items = new() }; } // 2. 筛选主工序(按Code前缀)并分页 var opQry = await operationRepo.GetQueryableAsync(); var mainOpQuery = opQry .Where(op => op.OperationCode.StartsWith(input.OperationCode)) .OrderBy(op => op.OperationCode); var totalMainOpCount = await AsyncExecuter.CountAsync(mainOpQuery); var pagedMainOpIds = await mainOpQuery .Skip(input.SkipCount) .Take(input.MaxResultCount) .Select(op => op.Id) .ToListAsync(); if (!pagedMainOpIds.Any()) { return new TreeResponseDto<OperationNodeDto> { TotalCount = 0, Items = new() }; } // 3. 调用原有树形逻辑 var nestedTrees = await GetNestedTreeAsync(pagedMainOpIds); // 4. 关键:递归拉平嵌套节点,所有节点作为顶级节点 var flatNodes = new List<OperationNodeDto>(); var queue = new Queue<OperationNodeDto>(nestedTrees); // 初始队列放根节点 while (queue.Count > 0) { var current = queue.Dequeue(); // 克隆当前节点,清空children(避免保留嵌套关系) var flatNode = new OperationNodeDto { Id = current.Id, OperationType = current.OperationType, OperationCode = current.OperationCode, OperationName = current.OperationName, Description = current.Description, StandardDuration = current.StandardDuration, IndirectlyHours = current.IndirectlyHours, FixedHours = current.FixedHours, RequiredQc = current.RequiredQc, IsInActive = current.IsInActive, RecordTypeId = current.RecordTypeId, ParentCode = current.ParentCode, // 保留父编码信息 Level = current.Level, StepSequence = current.StepSequence, ParentId = current.ParentId, // 保留父ID信息 Children = new List<OperationNodeDto>() // 强制子节点为空 }; flatNodes.Add(flatNode); // 将子节点入队,继续拉平(递归处理所有层级) foreach (var child in current.Children) { queue.Enqueue(child); } } // 5. 去重(按ID唯一) var uniqueFlatNodes = flatNodes .GroupBy(node => node.Id) .Select(group => group.First()) .ToList(); // 6. 返回拉平后的结果 return new TreeResponseDto<OperationNodeDto> { TotalCount = uniqueFlatNodes.Count, // 总节点数(主+所有子节点) Items = uniqueFlatNodes // 所有节点均为顶级,无嵌套 }; } #endregion #region 查询Operation Tree数据方法 public Task<List<OperationNodeFlatDto>> GetFlatTreeAsync(IEnumerable<Guid> rootIds) => GetFlatNodesAsync(rootIds); public Task<List<OperationNodeFlatDto>> GetFlatTreeAsync(Guid rootId) => GetFlatNodesAsync(new[] { rootId }).ContinueWith(t => t.Result.ToList()); public Task<OperationNodeDto?> GetNestedTreeAsync(Guid rootId) => GetNestedTreeAsync(new[] { rootId }).ContinueWith(t => t.Result.FirstOrDefault()); //#region 修改查询方法(返回前端所需格式) //// 1. 修改 GetAllOperationsTreeAsync 方法的返回类型为 TreeResponseDto<OperationNodeDto> //public async Task<TreeResponseDto<OperationNodeDto>> GetAllOperationsTreeAsync() //{ // var opQry = await operationRepo.GetQueryableAsync(); // var allOperationIds = await opQry // .Select(o => o.Id) // .ToListAsync(); // // 获取原始嵌套树形数据 // var nestedTrees = await GetNestedTreeAsync(allOperationIds); // // 2. 根节点去重(按 Id 唯一,避免同一节点多次作为根节点) // var uniqueRoots = nestedTrees // .GroupBy(node => node.Id) // 按节点唯一标识分组 // .Select(group => group.First()) // 每组只保留第一个节点(去重) // .ToList(); // // 3. 包装为前端所需格式 // return new TreeResponseDto<OperationNodeDto> // { // TotalCount = uniqueRoots.Count, // 总根节点数量(可根据需求改为总节点数) // Items = uniqueRoots // 去重后的嵌套根节点数组 // }; //} //#endregion #region 修改查询方法(返回前端所需格式) // 1. 修改 GetAllOperationsTreeAsync 方法的返回类型为 TreeResponseDto<OperationNodeDto> public async Task<TreeResponseDto<OperationNodeDto>> GetAllOperationsTreeAsync() { var opQry = await operationRepo.GetQueryableAsync(); var allOperationIds = await opQry .Select(o => o.Id) .ToListAsync(); // 获取原始嵌套树形数据 var nestedTrees = await GetNestedTreeAsync(allOperationIds); // 2. 根节点去重(按 Id 唯一,避免同一节点多次作为根节点) var uniqueRoots = nestedTrees .GroupBy(node => node.Id) // 按节点唯一标识分组 .Select(group => group.First()) // 每组只保留第一个节点(去重) .ToList(); // 3. 关键:只保留一级根节点中有子工序的节点(Children 不为空) var filteredRoots = uniqueRoots .Where(root => root.Children != null && root.Children.Any()) .ToList(); // 4. 包装为前端所需格式 return new TreeResponseDto<OperationNodeDto> { TotalCount = filteredRoots.Count, // 过滤后的一级根节点数量 Items = filteredRoots // 仅保留有子工序的一级根节点,其子节点正常保留(无论是否有子工序) }; } #endregion public async Task<List<OperationNodeDto>> GetNestedTreeAsync(IEnumerable<Guid> rootIds) { var flat = await GetFlatNodesAsync(rootIds); if (!flat.Any()) return new(); var roots = new List<OperationNodeDto>(); var nodeById = new Dictionary<Guid, List<OperationNodeDto>>(); // 改成 List! foreach (var item in flat) { var node = new OperationNodeDto { Id = item.Id, OperationType = item.OperationType, OperationCode = item.OperationCode, OperationName = item.OperationName, Description = item.Description, StandardDuration = item.StandardDuration, IndirectlyHours = item.IndirectlyHours, FixedHours = item.FixedHours, RequiredQc = item.RequiredQc, IsInActive = item.IsInActive, RecordTypeId = item.RecordTypeId, ParentCode = item.ParentCode, Level = item.Level, StepSequence = item.StepSequence, ParentId = item.ParentId, Children = new List<OperationNodeDto>() }; // 同一个 Id 可以有多个实例(每条路径一个) if (!nodeById.ContainsKey(item.Id)) nodeById[item.Id] = new List<OperationNodeDto>(); nodeById[item.Id].Add(node); if (item.ParentId == null) { roots.Add(node); } else { // 找到这个父节点的所有实例,子节点挂到所有父实例下(可选) if (nodeById.TryGetValue(item.ParentId.Value, out var parentInstances)) { foreach (var parent in parentInstances) { // 可选:避免重复添加 if (!parent.Children.Any(c => c.Id == node.Id && c.ParentId == item.ParentId)) { parent.Children.Add(node); } } } } } return roots; } #endregion #region 私有方法 private async Task<List<OperationNodeFlatDto>> GetFlatNodesAsync(IEnumerable<Guid> rootIds) { var rootIdList = rootIds?.Where(id => id != Guid.Empty).Distinct().ToHashSet(); if (rootIdList == null || rootIdList.Count == 0) return new(); var opQry = await operationRepo.GetQueryableAsync(); var subOpQry = await subOperationRepo.GetQueryableAsync(); // 1. 收集所有后代 Id var allIds = new HashSet<Guid>(rootIdList); var toProcess = new Queue<Guid>(rootIdList); while (toProcess.Count > 0) { var batch = toProcess.Take(500).ToList(); toProcess = new Queue<Guid>(toProcess.Skip(500)); var childIds = await subOpQry .Where(s => batch.Contains(s.OperationId)) .Select(s => s.SubOperationId) .ToListAsync(); foreach (var childId in childIds) if (allIds.Add(childId)) toProcess.Enqueue(childId); } // 2. 加载所有节点和所有边 var operations = await opQry .Where(o => allIds.Contains(o.Id)) .ToListAsync(); var idToCodeMap = operations.ToDictionary(op => op.Id, op => op.OperationCode); var links = await subOpQry .Where(s => allIds.Contains(s.OperationId) && allIds.Contains(s.SubOperationId)) .OrderBy(s => s.OperationId) .ThenBy(s => s.StepSequence) .ToListAsync(); // 3. 关键:不再用 Dictionary 去重!直接生成多条记录 var result = new List<OperationNodeFlatDto>(); // 用于计算 Level 和路径 var nodeInfo = new Dictionary<Guid, (int Level, Guid? ParentId, int StepSequence)>(); var queue = new Queue<(Guid Id, int Level, Guid? ParentId, int StepSequence)>(); // 初始化根节点 foreach (var rootId in rootIdList) { if (operations.Any(o => o.Id == rootId)) { queue.Enqueue((rootId, 0, null, 0)); nodeInfo[rootId] = (0, null, 0); } } while (queue.Count > 0) { var (currentId, level, parentId, seq) = queue.Dequeue(); var op = operations.First(o => o.Id == currentId); var parentCode = parentId.HasValue ? idToCodeMap.TryGetValue(parentId.Value, out var code) ? code : null : null; // 每出现一次,就生成一条记录(支持多父!) result.Add(new OperationNodeFlatDto { Id = op.Id, OperationType = op.OperationType, OperationCode = op.OperationCode, OperationName = op.OperationName, Description = op.Description, StandardDuration = op.StandardDuration, IndirectlyHours = op.IndirectlyHours, FixedHours = op.FixedHours, RequiredQc = op.RequiredQc, IsInActive = op.IsInActive, RecordTypeId = op.RecordTypeId, ParentCode = parentCode, ParentId = parentId, Level = level, StepSequence = seq }); // 查找当前节点的所有子边 var children = links.Where(l => l.OperationId == currentId).ToList(); foreach (var child in children) { var childId = child.SubOperationId; var childLevel = level + 1; // 即使 childId 已出现过,也要再入队!(支持多路径) queue.Enqueue((childId, childLevel, currentId, child.StepSequence)); } } return result; } #endregion // 在 MasterOperationService 中添加 /// <summary> /// 计算指定工序ID集合的层级(根节点0,子节点=父节点+1) /// </summary> private async Task<Dictionary<Guid, int>> CalculateOperationLevelsAsync(IEnumerable<Guid> operationIds) { var levelMap = new Dictionary<Guid, int>(); if (!operationIds.Any()) return levelMap; // 1. 获取所有相关的父子关系(父OperationId -> 子SubOperationId) var subOpQry = await subOperationRepo.GetQueryableAsync(); var parentChildRelations = await subOpQry .Where(s => operationIds.Contains(s.OperationId) || operationIds.Contains(s.SubOperationId)) .Select(s => new { ParentId = s.OperationId, ChildId = s.SubOperationId }) .ToListAsync(); // 2. 找出所有根节点(没有父节点的工序) var allChildIds = parentChildRelations.Select(r => r.ChildId).ToHashSet(); var rootIds = operationIds.Where(id => !allChildIds.Contains(id)).ToList(); // 3. 初始化根节点层级为0 foreach (var rootId in rootIds) { levelMap[rootId] = 0; } // 4. 递归计算子节点层级(BFS避免栈溢出) var queue = new Queue<Guid>(rootIds); // 从根节点开始遍历 while (queue.Count > 0) { var parentId = queue.Dequeue(); var parentLevel = levelMap[parentId]; // 找出当前父节点的所有子节点 var childIds = parentChildRelations .Where(r => r.ParentId == parentId && operationIds.Contains(r.ChildId)) .Select(r => r.ChildId) .ToList(); foreach (var childId in childIds) { if (!levelMap.ContainsKey(childId)) // 避免重复计算 { levelMap[childId] = parentLevel + 1; // 子节点层级=父节点+1 queue.Enqueue(childId); // 继续计算子节点的子节点 } } } // 5. 处理孤立节点(无任何父子关系,默认层级0) foreach (var id in operationIds) { if (!levelMap.ContainsKey(id)) { levelMap[id] = 0; } } return levelMap; } //public async override Task<PagedResultDto<Dictionary<string, object>>> GetListAsync(OperationGetListInput input) //{ // var opQry = await operationRepo.GetQueryableAsync(); // var allOperationIds = await opQry // .Select(o => o.Id) // .ToListAsync(); // // 获取原始嵌套树形数据 // var nestedTrees = await GetFlatNodesAsync(allOperationIds); // nestedTrees = nestedTrees.Where(n => n.Level > 0).ToList(); // //var entites = ObjectMapper.Map<List<OperationEntity>, List<Dictionary<string, object>>>(nestedTrees); // List<Dictionary<string, object>> entites = new List<Dictionary<string, object>>(); // nestedTrees.ForEach(node => // { // var et = Convert(node); // entites.Add(et); // }); // return new PagedResultDto<Dictionary<string, object>>(entites.Count, entites); //} public async Task<PagedResultDto<OperationGetOutputDto>> GetListTopLevelAsync(OperationGetListInput input) { var opQry = await operationRepo.GetQueryableAsync(); var subOpQry = await subOperationRepo.GetQueryableAsync(); List<Guid> subOpIds = await subOpQry .Select(s => s.SubOperationId) .Distinct() .ToListAsync(); opQry = opQry.Where(o => !subOpIds.Contains(o.Id)); opQry = FilterHelper.CreateFilterQuery(input, opQry); if (!string.IsNullOrWhiteSpace(input.FilterOpt)) { var filterOpt = JObject.Parse(input.FilterOpt); var keywords = filterOpt.ContainsKey("keywords") ? filterOpt["keywords"]?.ToString() : null; if (!string.IsNullOrWhiteSpace(keywords)) { opQry = opQry.Where(o => o.OperationCode.Contains(keywords) || o.OperationName.Contains(keywords));,在这里实现下面代码 public async Task<PagedResultDto<OperationGetOutputDto>> GetListWithFilterAsync(OperationGetListInput input) { // 1. 解析前端传入的FilterOpt JSON字符串 OperationFilterOptions? filter = null; if (!string.IsNullOrWhiteSpace(input.FilterOpt)) { try { var settings = new JsonSerializerSettings { ContractResolver = new CamelCasePropertyNamesContractResolver(), NullValueHandling = NullValueHandling.Ignore }; filter = JsonConvert.DeserializeObject<OperationFilterOptions>(input.FilterOpt, settings); } catch (Exception) { throw new BusinessException("筛选条件格式错误,请传入有效的JSON字符串!"); } } var query = await repository.WithDetailsAsync(); Guid? excludedId = null; if (filter != null && !string.IsNullOrWhiteSpace(filter.Filter_Id)) { string parentIdStr = filter.Filter_Id.Trim(); const string prefix = "opt_"; // 检查是否以 "opt_" 开头 if (parentIdStr.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)) { // 提取 "opt_" 后面的部分 string suffix = parentIdStr.Substring(prefix.Length).Trim(); // 按空格分割操作符代码和目标值 string[] parts = suffix.Split(new[] { ' ' }, 2); // 尝试解析操作符 if (parts.Length > 0 && Enum.TryParse<OperatorType>(parts[0], out var operatorType)) { // 只有当操作符是 NotEqual 时,才执行排除逻辑 if (operatorType == OperatorType.NotEqual && parts.Length > 1) { // 解析真正的ID值 string idValue = parts[1].Trim(); if (Guid.TryParse(idValue, out Guid parsedId)) { excludedId = parsedId; } } } } } } 但是这个参考代码逻辑 } } var entities = await opQry.ToListAsync(); var dtos = ObjectMapper.Map<List<OperationEntity>, List<OperationGetOutputDto>>(entities); return new PagedResultDto<OperationGetOutputDto>(dtos.Count, dtos); } /// <summary> /// 将对象转换为 Dictionary<string, object>(键为属性名,值为属性值) /// </summary> public static Dictionary<string, object> Convert(OperationNodeFlatDto obj) { if (obj == null) throw new ArgumentNullException(nameof(obj), "对象不能为null"); var dict = new Dictionary<string, object>(); // 获取对象的所有公共实例属性 PropertyInfo[] properties = obj.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); foreach (var prop in properties) { // 忽略索引器(如this[]) if (prop.GetIndexParameters().Length > 0) continue; // 获取属性值(支持null值) object value = prop.GetValue(obj); dict[prop.Name] = value; } return dict; } public override async Task<OperationGetOutputDto> GetAsync(Guid id) { var operation = await operationRepo.GetAsync(id); // 计算当前工序的层级 var levelMap = await CalculateOperationLevelsAsync(new[] { id }); var dto = ObjectMapper.Map<OperationEntity, OperationGetOutputDto>(operation); dto.Level = levelMap.TryGetValue(id, out var level) ? level : 0; return dto; } } } excludeid为当前子工序的id,想办法返回operation列表不包含excludeid,子工序为excludeid的主工序要是为别人的子工序,那个别人(主工序)不返回,不要在数据库里递归,最好用上面的方法
最新发布
11-26
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值