ChilliCream / graphql-platform

Welcome to the home of the Hot Chocolate GraphQL server for .NET, the Strawberry Shake GraphQL client for .NET and Banana Cake Pop the awesome Monaco based GraphQL IDE.
https://chillicream.com
MIT License
5.07k stars 723 forks source link

Primitive types such as UINT do not work with [UseFiltering] and [UseSorting] attributes #7200

Open naymore opened 1 week ago

naymore commented 1 week ago

Product

Hot Chocolate

Is your feature request related to a problem?

The type of the member CountUsed of the declaring type Shape is unknown (HotChocolate.Data.Filters.FilterInputType<MinimalGraphql.Entities.Shape>)

It does not work. Not even with the extra Nuget package for additional scalar types. And explicit Type Mapping.

// part of an EF entity
[GraphQLType(typeof(UnsignedIntType))]
public uint CountUsed { get; private set; }

Also adding an explicit type mapping doesn't help. Same error message.

builder.Services.AddGraphQLServer()
                                  .BindRuntimeType<uint, UnsignedIntType>()
                                  .InitializeOnStartup();

also doesn't help:

builder.Services.AddGraphQLServer()
                                  .AddType<UnsignedIntType>()
                                  .BindRuntimeType<uint, UnsignedIntType>()
                                  .InitializeOnStartup();

I'm absolutely puzzled. Can you shed some light on this and maybe update the docs?

The solution you'd like

Clear documentation on how to actually use these supported types.

michaelstaib commented 1 week ago

This type is not supported in filtering probably. @PascalSenn can this be mapped? should be just another comparable right?

glen-84 commented 1 week ago

See:

naymore commented 2 days ago

Program.cs

using MinimalGraphql.EntityFramework;
using Microsoft.EntityFrameworkCore;
using MinimalGraphql.Queries;

var builder = WebApplication.CreateBuilder(args);

string? connectionString = builder.Configuration.GetConnectionString("shapesdb");
builder.Services.AddDbContextFactory<ShapesDbContext>(options => options.UseSqlServer(connectionString));

builder.Services.AddGraphQLServer()
                    .BindRuntimeType<uint, UnsignedIntType>() // in some linked issue I read this should work
                    //.AddType<UnsignedIntType>() // not so sure about this one (see [#4305][(url](https://github.com/ChilliCream/graphql-platform/issues/4305))
                    .AddQueryType<Query>()
                    .AddFiltering()
                    .AddSorting()
                    .InitializeOnStartup();

WebApplication app = builder.Build();

using (IServiceScope scope = app.Services.CreateAsyncScope())
{
    await using ShapesDbContext dbContext = scope.ServiceProvider.GetRequiredService<ShapesDbContext>();
    await dbContext.Database.EnsureCreatedAsync();
    await dbContext.Database.MigrateAsync();
}

app.UseHttpsRedirection();
app.MapGraphQL();
app.Run();

EF Entity

namespace MinimalGraphql.Entities
{
    public class Shape
    {
        private Shape()
        {
            // NOP
        }

        protected Shape(...)
        {
             // left out for brevity
        }

        // [...]
        [GraphQLType(typeof(UnsignedIntType))]
        public uint CountUsed { get; set; }
        // [...]

        public static Shape Create(...)
        {
            return new Shape(...);
        }
    }
}

GraphQL Query

namespace MinimalGraphql.Queries
{
    public class Query
    {
        [UseDbContext(typeof(ShapesDbContext))]
        [UsePaging]
        [UseFiltering]
        [UseSorting]
        public IQueryable<Shape> GetShapes([Service(ServiceKind.Resolver)] ShapesDbContext dbContext)
        {
            return dbContext.Shapes.AsNoTracking();
        }
    }

...the exception

fail: Microsoft.Extensions.Hosting.Internal.Host[11]
      Hosting failed to start
      HotChocolate.SchemaException: For more details look at the `Errors` property.

      1. For more details look at the `Errors` property.

      1. The type of the member CountUsed of the declaring type Shape is unknown
       (HotChocolate.Data.Filters.FilterInputType<MinimalGraphql.Entities.Shape>)

         at HotChocolate.Configuration.TypeInitializer.DiscoverTypes()
         at HotChocolate.Configuration.TypeInitializer.Initialize()
         at HotChocolate.SchemaBuilder.Setup.InitializeTypes(SchemaBuilder builder, IDescriptorContext context, IReadOnlyList`1 types)
         at HotChocolate.SchemaBuilder.Setup.Create(SchemaBuilder builder, LazySchema lazySchema, IDescriptorContext context)
         at HotChocolate.SchemaBuilder.Create(IDescriptorContext context)
         at HotChocolate.SchemaBuilder.HotChocolate.ISchemaBuilder.Create(IDescriptorContext context)
         at HotChocolate.Execution.RequestExecutorResolver.CreateSchemaAsync(ConfigurationContext context, RequestExecutorSetup setup, RequestExecutorOptions executorOptions, IServiceProvider schemaServices, TypeModuleChangeMonitor typeModuleChangeMonitor, CancellationToken cancellationToken)
         at HotChocolate.Execution.RequestExecutorResolver.CreateSchemaServicesAsync(ConfigurationContext context, RequestExecutorSetup setup, CancellationToken cancellationToken)
         at HotChocolate.Execution.RequestExecutorResolver.GetRequestExecutorNoLockAsync(String schemaName, CancellationToken cancellationToken)
         at HotChocolate.Execution.RequestExecutorResolver.GetRequestExecutorAsync(String schemaName, CancellationToken cancellationToken)
         at HotChocolate.AspNetCore.Warmup.ExecutorWarmupService.ExecuteAsync(CancellationToken stoppingToken)
         at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__15_1(IHostedService service, CancellationToken token)
         at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 operation)

I can confirm it DOES WORK as soon as I ommit the attributes for Filtering and Sorting. But that's exactly why I want to use GraphQL in the first place...

If I understand #5759 correctly you basically have to do the plumbing yourself now and write custom FilterInputTypes?

naymore commented 2 days ago

Working code...

builder.Services.AddGraphQLServer()
                    .BindRuntimeType<uint, UnsignedIntType>()
                    .AddQueryType<Query>()
                    .AddFiltering(x => x.AddDefaults().BindRuntimeType<uint, UnsignedIntOperationFilterInputType>()) // <---
                    .AddSorting()
                    .InitializeOnStartup();

custom ComparableOperationFilterInputType

using HotChocolate.Data.Filters;

namespace MinimalGraphql.GraphQLFilters
{
    public class UnsignedIntOperationFilterInputType
     : ComparableOperationFilterInputType<UnsignedIntType>
    {
        protected override void Configure(IFilterInputTypeDescriptor descriptor)
        {
            descriptor.Name("UnsignedIntOperationFilterInputType");
            base.Configure(descriptor);
        }
    }
}

So this doesn't do much except setting a descriptor name. Refering to #5759 I don't really see why I should need to set the descriptor name myself. I couldn't really understand your concerns about it... at which point will I ever see this type name anyway?

It's actually in the docs. I just expected this to work automatically (hence the extra Scalars package).

When you add custom scalars, you will have to create custom filter types. Scalars are mapped to a FilterInputType that defines the operations that are possible for this scalar. The built-in scalars are already mapped to matching filter types. For custom scalars, or scalars from HotChocolate.Types.Scalars, you have to create and bind types.