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

C#查询性能优化求助:7k+数据查询耗时超30秒

优化C# EF查询性能方案

核心问题分析

你的代码性能瓶颈主要来自三个方面:

  1. 提前将7000+条全量数据加载到内存后再过滤,完全浪费了数据库的查询优化能力
  2. DTO转换时存在大量N+1查询(每个产品单独调用数据库获取子数据)
  3. 部分过滤逻辑在内存执行,且存在逻辑错误

具体优化步骤

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 00:28:09