C#查询性能优化求助:7k+数据查询耗时超30秒
优化C# EF查询性能方案
核心问题分析
你的代码性能瓶颈主要来自三个方面:
- 提前将7000+条全量数据加载到内存后再过滤,完全浪费了数据库的查询优化能力
- DTO转换时存在大量N+1查询(每个产品单独调用数据库获取子数据)
- 部分过滤逻辑在内存执行,且存在逻辑错误
具体优化步骤
1. 将所有过滤条件推送到数据库执行
不要一开始就用ToListAsync()加载全量数据,保持查询为IQueryable<T>类型,让EF自动生成带过滤条件的SQL,在数据库端完成数据筛选,减少内存加载的数据量。
修改初始查询及过滤逻辑:
// 保持为IQueryable,不提前加载到内存 var query = Context.ViewQueryProductoAlls.Where(a => !a.Delet).AsNoTracking(); if (model.Producto != null) { var productoFilter = model.Producto.ToString().ToLower(); query = query.Where(a => a.IdProducto.ToString().ToLower().Contains(productoFilter)); } if (model.NombreProducto != null) { var nombreFilter = model.NombreProducto.ToString().ToLower(); query = query.Where(a => a.Producto.ToString().ToLower().Contains(nombreFilter)); } if (model.Id != null) { query = query.Where(a => a.Id == model.Id); } if (model.categoriaId != null) { query = query.Where(a => a.IdCategoria == model.categoriaId); } if (model.PrecioDesde != null) { query = query.Where(a => a.Precio >= model.PrecioDesde); } // 修正逻辑错误:PrecioHasta应该是<=而不是>= if (model.PrecioHasta != null) { query = query.Where(a => a.Precio <= model.PrecioHasta); }
2. 消除N+1查询,批量获取关联数据
原来的Select中每个产品都会单独调用数据库获取子数据(如Context.MaestraCategoria、GetImagesProductoByColor等),这会产生数千次额外查询。改为批量查询所有需要的关联数据,再在内存中匹配。
示例:
// 先获取所有符合条件的产品ID,用于批量查询关联数据 var productIds = await query.Select(x => x.IdProducto).ToListAsync(); var productMainIds = await query.Select(x => x.Id).ToListAsync(); // 批量查询所有关联数据,转为字典方便快速取值 var subCategorias = await Context.MaestraCategoria .Where(b => productIds.Contains(b.IdProducto) && !b.Delet) .GroupBy(b => b.IdProducto) .ToDictionary(g => g.Key, g => g.Select(a => a.IdSubcategoria).FirstOrDefault()); // 替换原单个查询方法为批量查询实现 var imagesByProduct = await GetAllImagesByProductIds(productIds); var distinctImagesByProduct = await GetAllDistinctImagesByProductIds(productIds); var descuentosByProduct = await GetAllDescuentosByProductIds(productIds); var caracteristicasByProductId = await GetAllCaracteristicasByProductMainIds(productMainIds); var meGustaMap = await GetAllMeGustaByProductIds(productMainIds, model.CodigoCliente);
然后在DTO转换时直接从字典中取值:
var dtoQuery = query.Select(x => new ProductoDTOList.DetalleProducto { Id = x.Id, IdSucursal = x.IdSucursal, Sucursal = x.Sucursal, IdProducto = x.IdProducto, IdCategoria = x.IdCategoria, Categoria = x.Categoria, IdSubCategorias = subCategorias.TryGetValue(x.IdProducto, out var subCat) ? subCat : default, IdUnidad = x.IdUnidad, Unidad = x.Unidad, IdEstado = x.IdEstado, Estado = x.Estado, CantidadProductoDisponible = x.CantidadProductoDisponible, IdModena = 1, Moneda = "RD$", Precio = x.Precio, Costo = (decimal)x.Costo, Producto = x.Producto, ProductoComentario = x.ProductoComentario, ListaImagenes = imagesByProduct.TryGetValue(x.IdProducto, out var imgs) ? imgs : new List<ImageDTO>(), ConsultarImagenesDistinct = distinctImagesByProduct.TryGetValue(x.IdProducto, out var distImgs) ? distImgs : new List<ImageDTO>(), Descuentos = descuentosByProduct.TryGetValue(x.IdProducto, out var descs) ? descs : new List<DescuentoDTO>(), CarasteristicaColorSize = caracteristicasByProductId.TryGetValue(x.Id, out var chars) ? chars : new List<CaracteristicaDTO>(), MeGusta = meGustaMap.TryGetValue(x.Id, out var like) ? like : false });
3. 调整过滤与分页顺序
将基于DTO的过滤逻辑尽量提前,或者针对分页后的少量数据执行,减少内存处理的数据量:
// 数据库端计算总记录数,避免内存遍历统计 Result.TotalRecord = await query.CountAsync(); // 先分页再转换DTO,仅处理当前页数据 var paginatedProducts = await query .Skip((model.NumeroPagina - 1) * model.CantidadRows) .Take(model.CantidadRows) .ToListAsync(); // 转换当前页数据为DTO var dtoList = paginatedProducts.Select(x => new ProductoDTOList.DetalleProducto { // 赋值逻辑同上,使用批量查询的字典数据 }).ToList(); // 对当前页数据应用剩余过滤条件 if (model.DescuentoDesde != null) { dtoList = dtoList.Where(a => a.Descuentos.Any(b => b.Descuento >= model.DescuentoDesde)).ToList(); } if (model.DescuentoHasta != null) { dtoList = dtoList.Where(a => a.Descuentos.Any(b => b.Descuento <= model.DescuentoHasta)).ToList(); } if (model.carasteristicas != null) { dtoList = dtoList.Where(a => a.CarasteristicaColorSize.Any(b => model.carasteristicas.Contains(b.Size))).ToList(); } Result.Skip = model.NumeroPagina; Result.Take = model.CantidadRows; Result.DataList = dtoList;
4. 其他优化建议
- 为视图
ViewQueryProductoAlls的常用过滤字段(如Delet、IdCategoria、Precio、IdProducto)创建索引,提升数据库查询速度 - 避免频繁调用
ToString().ToLower(),可以在数据库端设置不区分大小写的排序规则,或者使用EF的EF.Functions.Like方法生成SQL的LIKE查询 - 确认
AsNoTracking()已正确使用,避免EF变更跟踪带来的额外开销
内容的提问来源于stack exchange,提问作者ASBEL SANTANA JIMENEZ
相关产品推荐
相关产品推荐

