简体   繁体   English

如何优化查询 - Ef core

[英]How to optimize the query - Ef core

I am working a asp .net core web API with EF core.我正在使用带有 EF 内核的 asp .net 内核 web API。 I wrote this query.我写了这个查询。 But this take 20-30seconds to execute.但这需要 20-30 秒才能执行。

Anyone have idea to improve this query.任何人都有改进此查询的想法。

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;
                                }
                            }
                        }
                    }

I f I remove those foreach code, The query takes 8-10s to execute.如果我删除了那些 foreach 代码,则查询需要 8-10 秒才能执行。 But I need those foreach codes.但我需要那些 foreach 代码。 Because I need the HotelFacilityDescription因为我需要HotelFacilityDescription

Any suggestion for optimize the query?有什么优化查询的建议吗?

Edit The i.Facilities - model编辑i.Facilities - model

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; }
    }
}

_context.Facilities will be enumerated (ie database will be called) for every iteration of previous loops. _context.Facilities将为先前循环的每次迭代枚举(即,将调用数据库)。 The quick fix is to call it ones and store results in variable:快速解决方法是将其命名为 one 并将结果存储在变量中:

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;
            }
        }
    }
}

Next improvement can be converting facilities into Dictionary for searching purposes.下一个改进可以是将facilities转换为Dictionary以进行搜索。

Even better approach can be writing query joining with _context.Facilities on database side (but here more info needed).更好的方法是在数据库端使用_context.Facilities编写查询连接(但这里需要更多信息)。

I've read this a couple times, but it looks like the relationship for Hotel.Facilities is a Facility, so could you not just do:我已经读过几次了,但看起来 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
                    }),

If for some reason Hotel.Facilities is not pointing at a Facility, but is a Many-to-Many HotelFacilityGroup entity to a FacilityGroup, that also contains a FacilityCode, if the associated FacilityGroup has access to a set of Facilities beneath it you could leverage that:如果由于某种原因 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).Select(f => f.Description).SingleOrDefault()
                    }),

That would avoid the need to go load all of the facilities to resolve that code.这将避免需要 go 加载所有工具来解析该代码。 Otherwise, if you do need to fetch across all facilities, pre-loading them would be the way to go, but rather than fetching the entire Facility entity I would recommend just the values you need, the Code and the Description.否则,如果您确实需要跨所有设施获取,则预加载它们将是 go 的方式,但我建议您只获取您需要的值、代码和描述,而不是获取整个设施实体。 This cuts down on the amount of memory needed and potentially be a faster query:这减少了所需的 memory 数量,并且可能是一个更快的查询:

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

The loop can be eliminated by projecting the value in the LINQ to Entities query.可以通过将 LINQ 中的值投影到实体查询来消除循环。

It would have been quite easy if you had relationship and navigation property like other *Code fields.如果您拥有像其他*Code字段一样的关系和导航属性,那将非常容易。 But as clarified in the comments, there is no such relationship, so you have to resort to old good manual left other join to emulate what navigation property provide automatically, eg但正如评论中澄清的那样,没有这种关系,所以你必须求助于旧的好手册 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 // <--
    }),

Here x_Facility emulates optional reference navigation property x.Facility if existed.这里x_Facility模拟可选的参考导航属性x.Facility如果存在)。

In case you need just single property from the related table, instead of a left join you could also use the original query with single value returning correlated subquery inside the projection, eg如果您只需要相关表中的单个属性,而不是左连接,您还可以使用原始查询和单个值返回投影内的相关子查询,例如

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() // <--
    }),

or even甚至

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

All these will eliminate the need of the post loop executiong additional database queries.所有这些都将消除后循环执行额外数据库查询的需要。 You can test them and take the one with best performance (#2 and #3 produce one and the same SQL, so it's a matter of taste - the choice is between #1 and #2/3).您可以测试它们并选择性能最佳的一个(#2 和 #3 产生一个相同的 SQL,所以这是一个品味问题 - 选择在 #1 和 #2/3 之间)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM