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.27k stars 748 forks source link

MongoDB integration not filtering child collections #5228

Open jkears opened 2 years ago

jkears commented 2 years ago

Is there an existing issue for this?

Describe the bug

I have a simple document that has a collection (modelReferences), and within it that I would like to filter the modelReferences by their Id property, however filtering at these child collections does not appear to work.

Note: When the exact same model is configured with the EFCore integration it works as expected, but not using MongoDb integratation.

Here is a simple example, of an unfiltered query on modelReferences child collection:

{
   gatewayServicesGatewayDefinitions {
     items{
        name name
        modelReferences {
           id
        }
     }
   }
}

Resultant data :

{
  "data": {
    "gatewayServicesGatewayDefinitions": {
      "items": [
        {
          "name": "Test",
          "modelReferences": [
            {
              "id": "f00554bc-fcef-417c-baee-9234534cb6bd"
            }
          ]
        }
      ]
    }
  }
}

And with the filter in which the id does not exist:"

{
   gatewayServicesGatewayDefinitions {
     items{
        name name
        modelReferences (where: {id:{eq:"f00554bc-fcef-417c-baee-9234534cb6be"}}) {
           id
        }
     }
   }
}

Should return no data modelReferences collection items, as that ID does not exist, however it returns everything as if is unfiltered:

{
  "data": {
    "gatewayServicesGatewayDefinitions": {
      "items": [
        {
          "name": "Test",
          "modelReferences": [
            {
              "id": "f00554bc-fcef-417c-baee-9234534cb6bd"
            }
          ]
        }
      ]
    }
  }
}

Here is the GraphQL configuration:

builder.Services.AddGraphQLServer()
.AddAuthorization()
.AddType<ErrorResponse>()
.AddType<AggregateEvent>()
.AddType<GatewayServicesGatewayDefinitionSM.GatewayDefinition>()
.AddQueryType()
.AddTypeExtension<GatewayDefinitionGraphQLQuery>()
.AddMutationType()
.AddTypeExtension<GatewayDefinitionGraphQLMutation>()
.AddMongoDbFiltering()
.AddMongoDbSorting()
.AddMongoDbProjections()  
.AddMongoDbPagingProviders(); 

Here is the Query Definition:

    public partial class GatewayDefinitionGraphQLQuery
    {

        [UseFirstOrDefault]
        public IExecutable<GatewayServicesGatewayDefinitionSM.GatewayDefinition> GetGatewayServicesGatewayDefinition([Service] IMongoCollection<GatewayServicesGatewayDefinitionSM.GatewayDefinition> collection, Guid id)
        {
            return collection.Find(x => x.Id == id).AsExecutable();
        }
        [UseOffsetPaging(IncludeTotalCount = false, DefaultPageSize = 10)]
        [UseProjection]
        [UseSorting]
        [UseFiltering]
        public IExecutable<GatewayServicesGatewayDefinitionSM.GatewayDefinition> GetGatewayServicesGatewayDefinitions([Service] IMongoCollection<GatewayServicesGatewayDefinitionSM.GatewayDefinition> collection)
        {
            return collection.AsExecutable();
        }
    }

Here is the model definition:

  [HotChocolate.AspNetCore.Authorization.Authorize]
    [GraphQLName("gatewayServices_gatewayDefinition")]
    [Serializable]
    [BsonIgnoreExtraElements]
    public class GatewayDefinition
    {

        public string Name { get; set; } = string.Empty;

        [HotChocolate.Data.UseFiltering]
        [HotChocolate.Data.UseSorting]
        public List<GatewayServicesGatewayDefinitionSM.ModelReference> ModelReferences { get; set; } = new();

       [BsonId(IdGenerator = typeof(GuidGenerator)), BsonRepresentation(BsonType.String)]
        [DataMember(Name = "id")]
        public System.Guid? Id { get; set; } = Guid.Empty;
    }

Here is the Node Resolver:

public class GatewayDefinitionNodeResolver
    {
        public Task<GatewayServicesGatewayDefinitionSM.GatewayDefinition> ResolveAsync([Service] IMongoCollection<GatewayServicesGatewayDefinitionSM.GatewayDefinition> collection, Guid id)
        {
            return collection.Find(x => x.Id == id).FirstOrDefaultAsync();
        }
    }

Here is the related child type, "ModelRerference" that I am trying to filter:

 [HotChocolate.AspNetCore.Authorization.Authorize]

    [GraphQLName("gatewayServices_modelReference")]
    [Serializable]
    [BsonIgnoreExtraElements]
    public class ModelReference
    {

        public System.Guid DomainModelId { get; set; } = Guid.Empty;

        public int Depth { get; set; } = 2;

        [BsonId(IdGenerator = typeof(GuidGenerator)), BsonRepresentation(BsonType.String)]
        public System.Guid? Id { get; set; } = Guid.Empty;
    }

Steps to reproduce

  1. Apply same code as above

Relevant log output

No response

Additional Context?

No response

Product

Hot Chocolate

Version

12.8.2

jkears commented 2 years ago

I included the definition of the ModelReference (child collection).

jkears commented 2 years ago

@PascalSenn , I was told you are the maintainer of the MongoDB integration library. Please see the above issue and let me know if you require further information.

Is filtering of children not possible in the MongoDB integration?

If yes and you can't provide me with an answer, can you at least provide me with hints as to where to look within the MongoDB integration source code so that I can try to figure this out. This is a major issue for us at the present time.

Thank you in advance, John

PascalSenn commented 2 years ago

@jkears at the moment this is not possible. How would the mongo query look like if you would issue one? Also, do you mind filtering in memory? this would be possbile.

jkears commented 2 years ago

@PascalSenn , thanks for your prompt reply. I thought we were doing something wrong.

Yes, filtering in memory would be fine for most of our scenarios, how would I integrate this via a GraphQL query?

jkears commented 2 years ago

@PascalSenn, this seems to be the way that you'd filter it.

PascalSenn commented 2 years ago

@jkears okok. so opting for the aggregation pipeline.. i mean, could work. Has other issues of course ;) like scalability of queries. But depending on the document you receive this might not even be necessary.

so try this

services.AddMongoDbFiltering(); //ordermatters
services.AddFiltering("queryable")

and then do

[HotChocolate.Data.UseFiltering("queryable")]
[HotChocolate.Data.UseSorting]
public List<GatewayServicesGatewayDefinitionSM.ModelReference> ModelReferences { get; set; } = new();
jkears commented 2 years ago

@PascalSenn

I swear I was able to add a string previously, but now I get this error ..

image

error CS1503: Argument 1: cannot convert from 'string' to 'System.Type?'

Here are my package references...

image

PascalSenn commented 2 years ago

mybad [UseFiltering(Scope = "queryable")]

jkears commented 2 years ago

@PascalSenn Yes I had also figure that I needed to define scope.

I also have to change this to

.AddMongoDbFiltering()
.AddFiltering("queryable") 

to this ..'

.AddMongoDbFiltering("queryable")
.AddFiltering()

as when it was complaining that it was missing AddFiltering()

However, now it is complaining ...

_1. The name gatewayServices_modelReferenceFilterInput was already registered by another type. (HotChocolate.Data.Filters.FilterInputType)_

jkears commented 2 years ago

@PascalSenn I am still not getting this to work...

As mentioned, when I attempted to configure filtering this way....

.AddMongoDbFiltering()
.AddFiltering("queryable") 

I get this exception at startup ...

[nextwarecoreservicesgatewayservices_825fad40-c]: 1. No default filter convention found. Call `AddFiltering()` on the schema builder.
[nextwarecoreservicesgatewayservices_825fad40-c]: (HotChocolate.Types.ObjectType<NextWare.CoreServices.SharedModels.GatewayServices.GatewayDefinitionAggregate.Models.GatewayDefinition>)

and if I try it this way ...

.AddMongoDbFiltering("queryable")
.AddFiltering()

I get this exception at startup...

1. The name gatewayServices_modelReferenceFilterInput was already registered by another type. (HotChocolate.Data.Filters.FilterInputType<NextWare.CoreServices.SharedModels.GatewayServices.GatewayDefinitionAggregate.Models.ModelReference>)

We are desperately trying to get this working, can you please advise what it is I am doing incorrectly? Many thanks!

jkears commented 2 years ago

@PascalSenn , can you please help me resolve this issue?

parihatch commented 1 year ago

Exact same issue as @jkears . Is there a fix? I couldn't get it working even as much as https://github.com/ChilliCream/graphql-platform/issues/5622 describes.
Following the callout: https://chilly-relay-docs.netlify.app/docs/hotchocolate/v11/integrations/mongodb/#filtering

My ServiceCollection.cs has this:

            .AddMongoDbFiltering("queryable")
            .AddMongoDbSorting()
            .AddMongoDbProjections()
            .UseDefaultPipeline()
            .AddFiltering()
            .AddSorting()

My query has this:

        [UsePaging]
    [UseFiltering(Scope ="queryable")]
    [UseSorting]
    [Authorize(Policy = Policy.Name.LicensedUsersOnly)]
    [GraphQLDescription("Get all sites")]
    public async Task<IEnumerable<Site?>> GetSitesAsync()

Additionally I injected the IResolverContext to the query and attempted to filter like this: var result = sites.Filter(context) where context is the IResolverContext. It throws an exception here: Filtering was not found. Register filtering with [UseFiltering] or descriptor.UseFiltering() . If I remove the Scope from [UseFiltering], there is no error, but obviously the filter is not attempted.

PascalSenn commented 5 months ago

@parihatch

The issue that @jkears had was that the mongodb filtering was picked up when in memory filtering should have been used.

So in your example you should actually register it differently:

            .AddMongoDbFiltering()
            .AddMongoDbSorting()
            .AddMongoDbProjections()
            .UseDefaultPipeline()
            .AddFiltering("queryable")
            .AddSorting()

Then you can annotate queryable on the field that you want to filter in memory.

This only works for queryables btw:

 var result = sites.Filter(context)