4

I see that it's possible to use Data Loaders for root queries, but is it also possible to use Data Loaders for nested connections? In the example below, I want use a Data Loader for the rooms property. In the example request at the bottom, there will be three database queries made. One by the data loader to fetch both buildings, one to fetch the rooms for building 1, and another to fetch the rooms for building 2. Instead, I'm trying to use a data loader for the rooms, so only two database queries are made.

// Building DB table
ID | Name
1  | Main Campus
2  | Satellite Campus
// Rooms DB table
ID | BuildingId | Name
1  | 1          | Lab
2  | 1          | Dorm
3  | 2          | Theatre
4  | 2          | Gym
// Schema
type Building {
  id: Int!
  name: String!
  rooms(after: String before: String first: PaginationAmount last: PaginationAmount): RoomsConnection
}

type Room {
  id: Int!
  name: String!
  building: Building!
}
// Hot Chocolate
public class BuildingType: ObjectType<Building> {
  protected override void Configure(IObjectTypeDescriptor<Building> descriptor)
  {
    // ... omitted other fields for brevity

    // Instead of using a resolver, can a data loader be used instead?
    descriptor.Field(b => b.rooms).UsePaging<RoomType>().Resolver(ctx => {
      var building = ctx.Parent<Building>();
      var roomsRepository = ctx.Service<IRoomsRepository>();
      return roomsRepository.GetRoomsByBuildingId(building.Id);
    });
  }
}
// Example request
query {
  a: building(id: 1){
    id,
    name,
    rooms {
      nodes {
        id,
        name
      }
    }
  },
  b: building(id: 2){
    id,
    name,
    rooms {
      nodes {
        id,
        name
      }
    }
  }
}
Keith
  • 41
  • 1
  • 3

1 Answers1

2

Not only is that possible but I would say it's the basic use case for DataLoaders to avoid "N+1" requests made across the object tree. For your purpose you could use GroupDataLoader. The group data loader is a batch data loader, i.e. it collects requests for similar entities made in one graphql request roundtrip and sends them as a single request to the data source. After that, it caches the result - i.e. returning entities from the cache while requested from any place of the object tree. Group data loader gets the list of keys as an input parameter (IReadOnlyList<TKey>) and returns the list of cached entities as a lookup (ILookup<TKey, TValue>). Lookup is a data type which holds multiple values per key (rather than dictionary which holds a single value per one key).

According to your specific case, rooms have to be grouped by building id - it is the key. So, you could use the group data loader to cache your connection in the following way (take into account that it's worthwhile to change the interface of your RoomsRepository to support batch requests to it, i.e. it has to accept not a single building id but a batch of them, also I assume that Room contains back reference to its building id):

public class BuildingType : ObjectType<Building>
{
    protected override void Configure(IObjectTypeDescriptor<Building> descriptor)
    {
        descriptor.Field(b => h.rooms).UsePaging<RoomType>
.Resolver(async (ctx, t) => await ctx.GroupDataLoader<int, Room>("roomsByBuildingGroup",
                    async keys => ctx.Service<IRoomsRepository>().GetRoomsByBuildingIds(keys).ToLookup(r => r.BuildingId))
                .LoadAsync(ctx.Parent<Building>().Id, t));
}

"roomsByBuildingGroup" is the name of the loader. In order to share a cache and to combine all requests from different objects tree places into a single one you have to use the same loader (i.e. use the same loader name) in all other places where you load rooms by building id.

Joundill
  • 6,828
  • 12
  • 36
  • 50
ademchenko
  • 585
  • 5
  • 18
  • But what if i have a lot of rooms per building? Let's say i have ten millions rooms in those buildings. Your example loads all of them and that is a little problem in my case. How can i pass limit/first argument to the dataloader? It is even possible? – Milan Obrtlík Jan 28 '23 at 10:02
  • The dataloader will load precisely those rooms by id that are requested by your query. Once it loads ten million rooms it means you have requested all that rooms. So, the paging should be done not on the level of the dataloader but on the level of your overall query. – ademchenko Jan 30 '23 at 14:22