如何优化查询 - 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 查询优化:我可以在 List<string> 上使用 Contains 吗?