使用EF Core5和Microsoft.EntityFrameworkCore.Proxies
,当涉及到嵌套对象时,我看到了很多N+1查询。
edit要明确的是,正如公认的答案所说,这个问题甚至与N+1查询无关。而且,正如我在评论中提到的,GraphQL阻止开发人员使用Include()
来塑造查询,因为查询的形状留给了用户。传统的GraphQL优化(如Apollo实现的(使用查询聚合和延迟加载来解决我在这个问题中描述的问题
例如,考虑对GET /machines/
的API调用,这意味着返回一个机器列表。每台机器有Parts
,每台Part
有Settings
。API调用旨在在同一响应中返回所有这些嵌套对象。
为了简单起见,这里是控制器端点:
public async Task<IActionResult> List()
{
MachineProfile[] machineProfiles = await Db<MachineProfile>().Select(x => x).ToArrayAsync();
Dictionary<string, object>[] res = machineProfiles.Select(ToJson).ToArray();
return ApiResult(res);
}
public Dictionary<string, object> ToJson(MachineProfile m)
{
return new Dictionary<string, object>() {
[nameof(m.Id)] = m.Id,
[nameof(m.Parts)] = m.Parts.Select(ToJson).ToArray(),
};
}
public Dictionary<string, object> ToJson(MachinePart m)
{
return new Dictionary<string, object>() {
[nameof(m.Id)] = m.Id,
[nameof(m.Settings)] = m.Settings.Select(ToJson).ToArray(),
};
}
public Dictionary<string, object> ToJson(MachineSetting m)
{
return new Dictionary<string, object>() {
[nameof(m.Id)] = m.Id,
};
}
下面是JSON结果的样子。
执行的SQL查询数为:
MachineProfiles
1x- 2个用于
MachineParts
(每台机器一个( - 11x用于
MachineSettings
(每个零件一个,即使同一零件出现两次(
这是一个非常优化的N+1查询的经典示例。
是否存在任何包或众所周知的解决方案来避免这种情况?
对于这个问题,存在GraphQL解决方案,它们将线程块注入查询树遍历中。这与Microsoft.EntityFrameworkCore.Proxies
的工作方式类似,只是它延迟并批处理查询以解决N+1问题。我想知道是否值得将这些代码移植到EF Core,或者是否有现有的解决方案。
--
Edit1我尝试使用GraphQL
包,因为我计划无论如何都将服务器构建为GraphQL。我确实看到它实际上提高了查询计数。
例如,如果我使用这个查询:
{ getMachineProfiles { id parts { id settings { id } } } }
它产生了相同的JSON结果,但通过缓存调用内部的查询,略微提高了性能。换句话说,同一setting
在同一响应中出现两次将不会引起第二次数据库调用。
但是11个数据库查询仍然远远多于"1";应该";使用(此API调用的优化版本只需要3个数据库查询(。
看起来Facebook的DataLoader有一个.NET端口,它实现了我在OP(查询聚合(中描述的解决方案。
该解决方案依赖于GraphQL
包,但可以通过在后台使用GraphQL和预编译查询来优化标准RESTful端点。