graphql-platform
graphql-platform copied to clipboard
Simplify generated LINQ expressions
Is your feature request related to a problem?
Yes. I'll explain below.
Let's say I have the following object:
public class Book
{
public Guid Id { get; set; }
public string Content { get; set; }
public bool IsPopular { get; set; }
public int Rating { get; set; }
}
And that I want to execute the following query:
{
books (where: {rating: {gt: 50}}) {
id
content
isPopular
rating
}
}
HotChocolate will generate a LINQ expression for that where clause to apply to the IQueryable for my data source that looks something like:
where([x] != null AndAlso [x].Rating > 50)
as opposed to the more simplified:
where([x].Rating > 50)
Also, let's say I want to sort by executing the following query:
{
books (order: {rating: ASC}) {
id
content
isPopular
rating
}
}
HotChocolate will generate a LINQ expression for that order clause to apply to the IQueryable for my data source that looks something like:
orderby IFF([x] == null, 0, [x].Rating) asc
as opposed to the more simplified:
orderby ([x].Rating) asc
The problem with these generated expressions is that some LINQ providers like the one from MartenDb (https://martendb.io/) would understand the more simplified form of the expressions above, but not the ones that are currently generated.
The solution you'd like
The solution to this problem would be to generate more simplified LINQ expressions, so that it's possible to integrate HotChocolate with more data sources.
Product
Hot Chocolate
Thanks for your investigation into this. A lot of these are optimizations for getting better SQL with the current database drivers. We can however create a new driver that produces simpler expressions.
Cool. A new driver would be very helpful to resolve this. However, in the meantime, I actually know how to simplify the expressions, but am not sure where exactly in the framework I can hook in and add some custom logic to do that. For instance, once an orderby expression is generated, is there a place where I can hook in, get the generated expression and simplify it before it is actually applied to the IQueryable?
This is where the standard handler for the filter expressions are registered: https://github.com/ChilliCream/hotchocolate/blob/main/src/HotChocolate/Data/src/Data/Filters/Expressions/Extensions/FilterConventionDescriptorQueryableExtensions.cs
Each operation has its own handler like the following: https://github.com/ChilliCream/hotchocolate/blob/main/src/HotChocolate/Data/src/Data/Filters/Expressions/Handlers/Boolean/QueryableBooleanEqualsHandler.cs
At the moment the handler use a helper class to build the expression.
So you would need to create a separate set of handler, you could copy the handlers. that we got and then modify them to your needs.
the create a new registration method called AddMartenFieldHandlers and at the end of it you will have essentially a new driver :)
You want to do a PR? We would create a new library for Marten and you could put all of this in there?
You can also do all of this in your project if you do not want to do a PR ... the HotChocolate.Data is very modular.
I'll take a look. How about for sorting?
same thing. here is where we register: https://github.com/ChilliCream/hotchocolate/blob/main/src/HotChocolate/Data/src/Data/Sorting/Expressions/Extensions/SortConventionDescriptorQueryableExtensions.cs
and here is one of the handlers: https://github.com/ChilliCream/hotchocolate/blob/main/src/HotChocolate/Data/src/Data/Sorting/Expressions/Handlers/QueryableAscendingSortOperationHandler.cs
Cool. I'll take a look and implement the driver for Marten.
If you get stack give a ping to @PascalSenn on slack.
I implemented the driver, and added extension methods that's analogous to the ones defined in https://github.com/ChilliCream/hotchocolate/blob/main/src/HotChocolate/Data/src/Data/Filters/Expressions/Extensions/FilterConventionDescriptorQueryableExtensions.cs and https://github.com/ChilliCream/hotchocolate/blob/main/src/HotChocolate/Data/src/Data/Sorting/Expressions/Extensions/SortConventionDescriptorQueryableExtensions.cs such as AddMartenFieldHandlers and UseMartenQueryableProvider. Then I try to configure on startup like so:
builder.Services
.AddGraphQLServer()
.AddType<BookType>()
.AddQueryType<Query>()
.AddSorting(c => c.UseMartenQueryableProvider())
.AddFiltering(c => c.UseMartenQueryableProvider());
But that does not work. I don't think that's due to of an issue in my driver because if I try to explicitly configure with the defaults like so:
builder.Services
.AddGraphQLServer()
.AddType<BookType>()
.AddQueryType<Query>()
.AddSorting(c => c.UseQueryableProvider())
.AddFiltering(c => c.UseQueryableProvider());
That also does not work, which leads me to believe that maybe that's not where I should be hooking in the driver. Where should I do that?
Never mind. Figured things out. Everything is working like a charm. I'll contribute the code soon so others can benefit.
Awesome!