Skip to content

Latest commit

 

History

History
123 lines (107 loc) · 3 KB

18.conditional-field-projection.md

File metadata and controls

123 lines (107 loc) · 3 KB

Conditional Field Projection

the Problem

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?

the Solution

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:

Convention Setup

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;
}

Usage

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));