graphql-platform icon indicating copy to clipboard operation
graphql-platform copied to clipboard

Simplify generated LINQ expressions

Open jabellard opened this issue 3 years ago • 10 comments

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

jabellard avatar Aug 08 '22 12:08 jabellard

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.

michaelstaib avatar Aug 08 '22 13:08 michaelstaib

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?

jabellard avatar Aug 08 '22 19:08 jabellard

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?

michaelstaib avatar Aug 08 '22 20:08 michaelstaib

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.

michaelstaib avatar Aug 08 '22 20:08 michaelstaib

I'll take a look. How about for sorting?

jabellard avatar Aug 08 '22 21:08 jabellard

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

michaelstaib avatar Aug 08 '22 21:08 michaelstaib

Cool. I'll take a look and implement the driver for Marten.

jabellard avatar Aug 08 '22 21:08 jabellard

If you get stack give a ping to @PascalSenn on slack.

michaelstaib avatar Aug 09 '22 08:08 michaelstaib

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?

jabellard avatar Aug 09 '22 15:08 jabellard

Never mind. Figured things out. Everything is working like a charm. I'll contribute the code soon so others can benefit.

jabellard avatar Aug 09 '22 23:08 jabellard

Awesome!

michaelstaib avatar Aug 12 '22 15:08 michaelstaib