如何优化查询 - Ef core

Posted

技术标签:

【中文标题】如何优化查询 - Ef core【英文标题】:How to optimize the query - Ef core 【发布时间】:2022-01-14 09:54:32 【问题描述】:

我正在使用带有 EF 核心的 asp .net 核心 Web API。 我写了这个查询。但这需要 20-30 秒才能执行。

任何人都想改进这个查询。

var hotels = await _context.Hotels
                    .Where(i => (i.DestinationCode == request.Destination))
                    .Select(i => new HotelListHotelVm
                    
                        Item1 = i.Item1,
                        Item2 = i.Item2,
                        Item3 = i.Item3,
                        
                        Item4Code = i.Item4Code,
                        Item4Description = i.Item4.TypeDescription,
                        Item5 = i.Item5.Select(x => new HotelListHotelVm.HotelListItem5Vm
                        
                            Code = x.Item5Code,
                            Description = x.Item5.Description,
                        ).Where(x =>(incomingItem5s.Length > 0 ) ? (incomingItem5s.Contains(x.Code)) : (x.Code != "")),
                        Item6 = i.Item6.Select(x => new HotelListHotelVm.HotelListHotelItem6Vm
                        
                            Id = x.Id,
                            Item6TypeCode = x.Item6TypeCode,
                            Order = x.Order,
                            Path = x.Path,
                            VisualOrder = x.VisualOrder,
                        ).Take(3),
                        HotelFacilities =  i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm 
                            Id = x.Id,
                            FacilityGroupCode = x.FacilityGroupCode,
                            HotelFacilityGroupDescription = x.FacilityGroup.Description,
                            FacilityCode = x.FacilityCode
                        ),
                    )
                    .Where( i => ((incomingItem4.Length > 0 ) ? (incomingItem4.Contains(i.Item4Code)) : (i.Item4Code != ""))   )
                    .OrderByDescending(i => i.Code)
                    .PaginatedListAsync(request.PageNumber, request.PageSize);



                    foreach( var item in hotels.Items)
                        foreach(var facility in item.HotelFacilities)
                            foreach( var fac in  _context.Facilities)
                        
                                if(facility.FacilityCode == fac.Code)
                                    facility.HotelFacilityDescription = fac.Description;
                                
                            
                        
                    

如果我删除那些 foreach 代码,查询需要 8-10 秒才能执行。 但我需要那些 foreach 代码。因为我需要HotelFacilityDescription

对优化查询有什么建议吗?

编辑i.Facilities - 模型

public class HotelFacility 
    
        // removed some
        public int FacilityCode  get; set; 

        public int FacilityGroupCode  get; set; 
        public FacilityGroup FacilityGroup  get; set; 

        public int HotelCode  get; set; 
        public Hotel Hotel  get; set; 
    

【问题讨论】:

很遗憾我们使用的是 5.0 版 (1) 请在问题中包含i.Facilities集合导航属性指向的模型类。 (2) 还要解释你想用.ToList().Distinct() 实现什么,因为它没有做任何有用的事情 - Id 已经是唯一的,所以 distinct 运算符不会更改结果集,但可能会影响数据库中的查询性能查询优化器不够聪明,无法忽略它。 (1) 我已经更新了。 (2) 因为有一些重复的FacilityCode (2) 您从模型中删除了一些可能必不可少的属性。查询显示必须有一个名为Id 的属性,它很可能是PK,即唯一,所以distinct 没有效果。不同的是使用 all 属性,而不仅仅是 FacilityCode。这就是为什么我问您要实现什么目标 - 根据您的评论,它确实不会达到您的预期。 (1) 另外,你没有public Facility Facility get; set; 导航属性(就像你有FacilityGroup 一样)?如果没有,为什么不呢?确保您拥有它,然后只需在 LINQ to Entities 查询中使用它,例如FacilityCode = x.FacilityCode, HotelFacilityDescription = x.Facility.Description 并删除循环。 【参考方案1】:

_context.Facilities 将为先前循环的每次迭代枚举(即,将调用数据库)。快速解决方法是调用它并将结果存储在变量中:

var facilities = _context.Facilities.ToList();
foreach( var item in hotels.Items)
    foreach(var facility in item.HotelFacilities)
        foreach(var fac in facilities)
                        
            if(facility.FacilityCode == fac.Code)
                facility.HotelFacilityDescription = fac.Description;
            
        
    

下一个改进是将facilities 转换为Dictionary 以进行搜索。

更好的方法是在数据库端使用_context.Facilities 编写查询连接(但这里需要更多信息)。

【讨论】:

谢谢@Guru。有什么建议可以改进这个查询var hotels = await _context.Hotels.......。因为如果我注释了 foreach 代码,剩下的查询也需要 2s-8s。 @hanushic 很难说。检查生成的SQL,检查执行计划。也许一些索引会有所帮助。或者从.OrderByDescending(i => i.Code) 开始使用skip-take 更改分页可能会非常昂贵。 好的,我会检查的。再次感谢兄弟【参考方案2】:

我已经读过几次了,但看起来 Hotel.Facilities 的关系是一个 Facility,所以你能不能不这样做:

  HotelFacilities =  i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm 
                        Id = x.Id,
                        FacilityGroupCode = x.FacilityGroupCode,
                        HotelFacilityGroupDescription = x.FacilityGroup.Description,
                        FacilityCode = x.FacilityCode,
                        HotelFacilityDescription = x.Description
                    ),

如果由于某种原因,Hotel.FacilityGroup 未指向设施,而是指向设施组的多对多 HotelFacilityGroup 实体,如果关联的设施组有权访问其下的一组设施,则该实体还包含设施代码你可以利用它:

编辑:听起来多个设施共享相同的代码,其中一些可能具有空描述。前提是与代码匹配的设施位于同一设施组内,并且不考虑不同设施组内的相同代码。如果您需要在所有设施中匹配代码,那么除了加载整套设施代码和描述之外,可能没有太多替代方案。

  HotelFacilities =  i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm 
                        Id = x.Id,
                        FacilityGroupCode = x.FacilityGroupCode,
                        HotelFacilityGroupDescription = x.FacilityGroup.Description,
                        FacilityCode = x.FacilityCode,
                        HotelFacilityDescription = x.FacilityGroup.Facilities.Where(f => f.Code == x.FacilityCode && f.Description != null).Select(f => f.Description).FirstOrDefault()
                    ),

这将避免需要加载所有工具来解析该代码。否则,如果您确实需要跨所有设施进行获取,则可以预加载它们,但与其获取整个设施实体,我建议您只使用您需要的值、代码和描述。这减少了所需的内存量并且可能是一个更快的查询:

var facilities = _context.Facilities
    .Select(f => new 
    
        f.Code,
        f.Description
    ).ToList();

编辑: 从那里,使用以下方法找到匹配项:

foreach( var facility in hotels.Items.SelectMany(x => x.HotelFacilities)

    facility.HotelFaciltyDescription = facilities
        .Where(x => x.Code == facility.FacilityCode 
            && !string.IsNullOrEmpty(x.Description)
        .Select(x => x.Description)
        .FirstOrDefault();

我会推荐一个 OrderBy 子句,以确保设施的选择是可预测的,因为听起来在具有非空描述的代码上可能有多个匹配项。

【讨论】:

是的。 FaciltyGroup 与 Facility 有关系。但有些描述是空的。所以我用了最后一个。谢谢。但仍然需要 8 秒。 我可以像这样使用 ` var facilityList = _context.Facilities.ToList(); `HotelFacilities = hotelFacilitiesList.Where(x => (x.HotelCode == i.Code)) .Select(x => new HotelListHotelVm.HotelListFacilityVm Id = x.Id, FacilityGroupCode = x.FacilityGroupCode, HotelFacilityGroupDescription = x.FacilityGroup.Description, FacilityCode = x.FacilityCode ) 我对示例进行了一些编辑。总体而言,这些查询会很昂贵,例如 Distinct 调用或查询整个表以查找松散耦合或不可靠的值。【参考方案3】:

可以通过投影 LINQ to Entities 查询中的值来消除循环。

如果您拥有像其他 *Code 字段一样的关系和导航属性,那将非常容易。但是正如 cmets 中所阐明的那样,没有这种关系,所以你必须求助于旧的好手册 left other join 来模拟导航属性自动提供的内容,例如

HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
    // left outer join with Facilities
    .SelectMany(x => _context.Facilities
        .Where(f => x.FacilityCode == f.Code).DefaultIfEmpty(),
    (x, x_Facility) => new HotelListHotelVm.HotelListFacilityVm
    
        Id = x.Id,
        FacilityGroupCode = x.FacilityGroupCode,
        HotelFacilityGroupDescription = x.FacilityGroup.Description,
        FacilityCode = x.FacilityCode,
        HotelFacilityDescription = x_Facility.Description // <--
    ),

此处x_Facility 模拟可选的参考导航属性x.Facility(如果存在)。

如果您只需要相关表中的单个属性,而不是左连接,您还可以使用原始查询与单个值返回投影内的相关子查询,例如

HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
    .Select(x => new HotelListHotelVm.HotelListFacilityVm
    
        Id = x.Id,
        FacilityGroupCode = x.FacilityGroupCode,
        HotelFacilityGroupDescription = x.FacilityGroup.Description,
        FacilityCode = x.FacilityCode,
        HotelFacilityDescription = _context.Facilities
            .Where(f => x.FacilityCode == f.Code)
            .Select(f => f.Description)
            .FirstOrDefault() // <--
    ),

甚至

HotelFacilityDescription = _context.Facilities
    .FirstOrDefault(f => x.FacilityCode == f.Code).Description

所有这些都将消除后循环执行额外数据库查询的需要。您可以对它们进行测试并选择性能最佳的一个(#2 和 #3 生成一个相同的 SQL,所以这是个人喜好问题 - 在 #1 和 #2/3 之间进行选择)。

【讨论】:

以上是关于如何优化查询 - Ef core的主要内容,如果未能解决你的问题,请参考以下文章

针对特定 EF Core 查询优化 UNKNOWN

EF Core 标头-详细信息查询优化

EF Core 查询优化:我可以在 List<string> 上使用 Contains 吗?

#yyds干货盘点#愚公系列2023年02月 .NET/C#知识点-EF Core性能优化之显示编译

如何优化 EF 查询?

如何在 EF Core 中查询多对多关系