With EF Core5 and Microsoft.EntityFrameworkCore.Proxies
, I'm seeing lots of N+1 queries when it comes to nested objects.
edit To be clear, this question isn't even about N+1 queries, as the accepted answer says. And, as I mention in the comments, GraphQL precludes the developer from using Include()
to shape the query because the query shape is left to the user. Traditional GraphQL optimizations, such as implemented by Apollo, use query aggregation in combination with lazy loading to solve the problem I'm describing in this question.
For example, consider an API call to GET /machines/
, which is meant to return a list of machines. Each machine has Parts
, and and each Part
has Settings
. The API call is intended to return all of these nested objects in the same response.
To keep it simple, here's the controller endpoint:
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,
};
}
And here's what the JSON result looks like.
The number of SQL queries executed are:
- 1x for
MachineProfiles
- 2x for
MachineParts
(one for each machine) - 11x for
MachineSettings
(one for each part, even if the same part appears twice)
This is a classic example of an horribly-optimized N+1 query.
Does there exist any package or well-known solution to avoid this?
There exist GraphQL solutions to this problem, which inject thread blocks into the query tree traversal. This is similar to how Microsoft.EntityFrameworkCore.Proxies
works, except that it defers and batches the queries to solve the N+1 problem. I'm wondering if it's worth porting this code to EF Core, or if there's an existing solution.
--
Edit1 I tried using the GraphQL
package because I plan on building my server as GraphQL anyways. I do see that it actually improves the query count.
For example, if I use this query:
{ getMachineProfiles { id parts { id settings { id } } } }
It produces the same JSON result, but improves performance slightly by appearing to cache the queries internal to the call. In other words, the same setting
appearing twice in the same response will not cause a second database call.
But 11 database queries is still WAY more than "should" be used (an optimized version of this API call would only require 3 database queries).