often we store only ids in database and we use them to fetch full entity types in graphQL. In other words, we want to do graphQL query like this:
query myOrders {
userOrders(where: {id: { eq: "bbedc085-68b0-4234-8c19-13c3b67db43d"}, }) {
nodes{
book {
title
abstract
categories
authors {
firstName
surnName
}
}
boughtOn
quantity
amount
}
}
}
having this kind of documents in the db:
{
"_id" : "bbedc085-68b0-4234-8c19-13c3b67db43d",
"BookId" : "1fa24d03-7029-47a7-a13d-f5adedc73502",
"UserName" : "william.verdolini",
"BoughtOn" : ISODate("2000-03-01T23:00:00.000Z"),
"Quantity" : 3,
"Amount" : NumberLong(88)
}
Here, the BookId
should be used in mongodb query, when we ask for order's book.
But, with the conventions used by the MongoDb Hotchocolate provider, the query that we'll execute will be something like the following:
{
"find" : "order",
"filter" : { "UserName" : "william.verdolini", "_id" : { "$eq" : "bbedc085-68b0-4234-8c19-13c3b67db43d" } },
"projection" : {
"Amount" : 1,
"Quantity" : 1,
"BoughtOn" : 1,
"BookId.Authors.SurnName" : 1,
"BookId.Authors.FirstName" : 1,
"BookId.Categories" : 1,
"BookId.Abstract" : 1,
"BookId.Title" : 1 },
}
That cannot be resolved...so what?
In this case we have to force the projection of the db field scalar id and stop the way the graphQL mongodb provider works. To do that we have use custom Convention, in this way:
public class ConditionFieldProjectionHandler : MongoDbProjectionScalarHandler
{
public override bool CanHandle(ISelection selection)
{
if (selection?.Field is not null)
{
if (selection.Field.ContextData.TryGetValue("UseScalarProjectionKey", out var val) && val != null)
{
return (bool)val;
}
}
return false;
}
}
builder.Services
...
.AddGraphQLServer()
...
.AddType<BookType>()
.AddType<OrderType>()
.AddConvention<IProjectionConvention>(
new ProjectionConventionExtension(
x => x.AddProviderExtension(
new ProjectionProviderExtension(y => y.RegisterFieldHandler<ConditionFieldProjectionHandler>()))))
with custom extension method to apply the behaviour:
public static IObjectFieldDescriptor UseScalarProjection(this IObjectFieldDescriptor descriptor)
{
descriptor.Extend().OnBeforeCreate(x =>
{
x.ContextData["UseScalarProjectionKey"] = true;
});
return descriptor;
}
Now, it's very simple:
descriptor
.Field(f => f.BookId)
.Name("book")
.UseScalarProjection() // custom extentsion method
.Type<BookType>()
.Resolve((ctx, ct)
=> ctx.DataLoader<BookBatchDataLoader>()
.LoadAsync(ctx.Parent<Order>().BookId!, ct));