-
-
Notifications
You must be signed in to change notification settings - Fork 781
Cannot use UseProjection with UseConnection and PageConnection<T> #8278
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
The The When using the new style of projections, lists will not be projected by default (see this issue for the reasoning). It is recommended that you define a resolver for each related collection (f.e. cities), and that these resolvers (in fact almost all object resolvers) use a DataLoader. So in your public static async Task<PageConnection<City>> GetCitiesAsync(
[Parent(requires: nameof(Country.Id))] Country country,
WorldContext context,
PagingArguments pagingArguments,
QueryContext<City> query,
CancellationToken cancellationToken = default)
{
var page = await context.Cities.AsNoTracking()
.Where(c => c.CountryId == country.Id)
.With(query, c => c.IfEmpty(i => i.AddAscending(e => e.Id)))
.ToPageAsync(pagingArguments, cancellationToken);
return new PageConnection<City>(page);
} ... but using DataLoader instead, to avoid the N+1 problem (i.e. to send a single query for all of the cities). PS. You can also run the query using Nitro at http://localhost:5000/graphql/. 😉 |
Thanks a lot for your detailed explanation — it makes much more sense to me now. Initially, it didn’t feel intuitive to separate the query into one for the countries and another one per relation (like cities), especially when I wanted to fetch the full list in one go. But now I understand the reasoning and the advantage of this pattern, particularly in the context of nested pagination and avoiding overfetching. I had tried using a DataLoader before but didn’t implement it correctly — I was retrieving all fields of the child entity instead of selecting only what was needed. Now I’ve successfully set up a DataLoader with a internal static class CityDataLoader
{
[DataLoader]
public static async Task<IReadOnlyDictionary<int, City[]>> CitiesByCountryIdAsync(
IReadOnlyList<int> countryIds,
WorldContext dbContext,
ISelectorBuilder selector,
CancellationToken cancellationToken)
{
return await dbContext.Countries
.AsNoTracking()
.Where(t => countryIds.Contains(t.Id))
.Select(t => t.Id, t => t.Cities, selector)
.ToDictionaryAsync(r => r.Key, r => r.Value.ToArray(), cancellationToken);
}
} And in my [ObjectType<Country>]
public static partial class CountryType
{
public static async Task<IEnumerable<City>> GetCitiesAsync(
[Parent] Country track,
ICitiesByCountryIdDataLoader citiesByCountryId,
ISelection selection,
CancellationToken cancellationToken)
{
return await citiesByCountryId
.Select(selection)
.LoadAsync(track.Id, cancellationToken) ?? [];
}
} Now everything works smoothly — and efficiently. Thanks again for pointing me in the right direction! 🙏 |
Product
Hot Chocolate
Version
15.1.3
Link to minimal reproduction
https://github.com/mathieu-radyo/HotChocolate-IssueReproductions
Steps to reproduce
[UseConnection]
,[UseFiltering]
,[UseSorting]
and[UseProjection]
on a resolver that returnsPageConnection<T>
.countries
and theircities
).Example code:
GraphQL query used:
What is expected?
I expect to be able to combine
[UseConnection]
with[UseProjection]
to:forwardCursors
,backwardCursors
)cities
without overfetchingWhat is actually happening?
The query fails at startup or runtime with the following error:
If I remove
[UseProjection]
, the query runs — but child entities likecities
are no longer included, even if defined in the GraphQL schema.Relevant log output
Additional context
The issue is reproducible in this minimal GitHub repo:
🔗 https://github.com/mathieu-radyo/HotChocolate-IssueReproductions
Questions:
UseProjection
eventually supportPageConnection<T>
?Thanks in advance for your help and your work on this great library!
The text was updated successfully, but these errors were encountered: