ASP.NET Razor页面大数据加载缓慢,求推荐优化方案
针对你遇到的Razor页面加载慢的问题,结合你的代码情况,给你几个实用的解决方向:
一、先优化服务器端数据处理逻辑(最立竿见影)
你的嵌套循环里最耗时的是ActivePallets.FirstOrDefault(a => a.GetLocation() == s.key),因为每次查找都是遍历整个数组,数据量大的时候这个操作会被执行成千上万次,直接拖慢速度。
- 把
ActivePallets转换成字典,用GetLocation()的结果作为键,将查找操作从O(n)降为O(1):
后续Select里替换FirstOrDefault:// 替换原来的Pallet[] ActivePallets = ViewBag.ActivePalletArr; var activePalletLookup = ((Pallet[])ViewBag.ActivePalletArr).ToDictionary(a => a.GetLocation());PalletItem = activePalletLookup.TryGetValue(s.key, out var pallet) ? pallet : null, - 提前计算Zone的统计数据,避免在视图里实时计算:
在bindata的投影里直接加入统计字段,视图里直接取用即可:var bindata = Model.GroupBy(x => x.description) .Select(x => new { Zone = x.Key.description, PalletCount = x.Count(s => activePalletLookup.ContainsKey(s.key)), TotalCount = x.Count(), Location = x.Select(s => { activePalletLookup.TryGetValue(s.key, out var pallet); var aging = pallet != null ? (DateTime.Now - pallet.CreatedOn).Days : 0; return new { PalletItem = pallet, Aging = aging, BIN = s.key, LOCATION = s.value }; }) }) .OrderByDescending(x => x.Zone) .ToArray(); - 简化嵌套Select,把两次投影合并成一次,减少中间匿名对象的创建开销。
二、实现点击Zone再加载对应数据(彻底减少首次加载量)
你之前想的点击加载思路是对的,核心是用AJAX从服务器获取对应Zone的HTML片段:
- 在Razor Page中添加请求处理Handler:
在页面对应的.cs文件中添加:public IActionResult OnGetLoadZoneDetails(string zone) { var model = (ValueDataSet[])Model; var activePallets = ((Pallet[])ViewBag.ActivePalletArr).ToDictionary(a => a.GetLocation()); var zoneData = model.Where(x => x.description == zone) .Select(s => { activePallets.TryGetValue(s.key, out var pallet); var aging = pallet != null ? (DateTime.Now - pallet.CreatedOn).Days : 0; return new { PalletItem = pallet, Aging = aging, BIN = s.key, LOCATION = s.value }; }); return Partial("_ZoneDetails", zoneData); } - 创建Partial View(_ZoneDetails.cshtml):
把原来渲染BIN的循环代码单独抽出来:@model IEnumerable<dynamic> @foreach (var bin in Model) { var order = bin.PalletItem?.ContainerItem?.OrderItem; <div class="binlocation @(bin.Aging > 12 ? "aging2" : bin.Aging > 0 ? "aging1" : "")"> @bin.BIN (@bin.Aging) <div> <small>@(bin.PalletItem?.LicensePlate)</small> <small>@(order != null ? $"<a href=\"/order/edit/{order.ItemKey}\" target=\"_blank\">{order.OrderNumber}</a>" : "")</small> </div> </div> } - 修改主页面的JavaScript逻辑:
替换原来的toggledetails函数,实现点击加载:
同时修改h5的点击参数,传入Zone名称:<script> function toggledetails(zoneIdx, zoneName) { const targetDiv = document.querySelector(`.toggledetail_${zoneIdx}`); if (targetDiv.style.display === 'none') { // 避免重复加载 if (targetDiv.innerHTML.trim() === '') { fetch(`?handler=LoadZoneDetails&zone=${encodeURIComponent(zoneName)}`) .then(response => response.text()) .then(html => { targetDiv.innerHTML = html; targetDiv.style.display = 'block'; }); } else { targetDiv.style.display = 'block'; } } else { targetDiv.style.display = 'none'; } } </script>
这样首次加载只渲染Zone标题,点击后才加载对应BIN的内容,大幅降低首次加载耗时。<h5><b onclick="toggledetails(@(zoneidx), '@item.Zone')" style="cursor:pointer;">@item.Zone (@item.PalletCount / @item.TotalCount)</b></h5>
三、其他辅助优化
- 避免在视图循环内创建新对象:比如把
new OrderItem()换成null,视图里用@(order?.OrderNumber ?? ""),减少不必要的对象创建。 - 缓存系统时间:在服务器端开头定义
var now = DateTime.Now;,后续计算Aging都用这个变量,避免多次获取系统时间。 - 服务器端缓存:如果数据不是实时更新的,可以用MemoryCache缓存bindata或Zone数据,减少重复计算。
内容的提问来源于stack exchange,提问作者MAREY
相关产品推荐
相关产品推荐

