Skip to content

Exposing custom GQL Queries

Sometimes we want to expose queries that receives arguments, or that returns a single element or that performs some advanced filtering or grouping before returning the elements.

In that case, the ExposeInPublicApi will not suffice. We need to create a class ,very similar to an App Service, in the sense that it is application level logic.

These classes, called Queries, will usually apply specifications and return an IQueryable.

For example, this query returns the current position assigned to a user:

C#
public class PositionQuery : IQuery
{
    [UseSuiteSingleOrDefault]
    public Task<IQueryable<PositionData>> GetPositionForUser(
        Guid userCorrelationId,
        [Service] IQueryableFactory factory,
        IResolverContext context)
    {
        var aggregate = AggregateSpecification.For<Position>()
            .AndFilter(new PositionForAssigneeFilterSpecification(userCorrelationId))
            .AddProjection(new AutoMapperHotChocolateProjectionSpecification<Position, PositionData>(context));
        return factory.Execute(aggregate);
    }
}

Unfortunately, attributes are required, at least for now. When returning a single element, you have to add [UseSuiteSingleOrDefault].

This is very important, since it is what makes the SQL SELECT only what we are selecting in the GQL query.

You'll notice that the method returns a Task<IQueryable<PositionData>>, you can obtain it using the IQueryableFactory in combination with an specification, as shown in the example.

Note

We recommend using specifications and the IQueryableFactory instead of using the DbContext directly, as the IQueryableFactory will take care of obtaining the proper DbSet from the correct DbContext.

With this simple trick we can generate all sorts of queries.

This is a second example that returns a list of elements instead of a single one. The only difference, besides the logic, is that we use [UseSuiteList(typeof(PositionData))] instead of [UseSuiteSingleOrDefault]

C#
public class PositionQuery : IQuery
{
    [UseSuiteList(typeof(PositionData))]
    public Task<IQueryable<PositionData>> GetPositionsOf(
        Guid? parentPositionId,
        [Service] IQueryableFactory factory,
        IResolverContext context)
    {
        var aggregate = AggregateSpecification.For<Position>()
            .AndFilter(new ChildPositionFilterSpecification(parentPositionId))
            .AddProjection(new AutoMapperHotChocolateProjectionSpecification<Position, PositionData>(context));
        return factory.Execute(aggregate);
    }
}

Another case is when the entity you are exposing in the GraphQL schema is nullable, we need to use the ExecuteNullable method. Also, return the entity as nullable in the method signature.

C#
public class PositionQuery : IQuery
{
    [UseSuiteSingleOrDefault]
    public Task<IQueryable<PositionData?>> GetPositionForUser(
        Guid userCorrelationId,
        [Service] IQueryableFactory factory,
        IResolverContext context)
    {
        var aggregate = AggregateSpecification.For<Position>()
            .AndFilter(new PositionForAssigneeFilterSpecification(userCorrelationId))
            .AddProjection(new AutoMapperHotChocolateProjectionSpecification<Position, PositionData>(context));

        return factory.ExecuteNullable(aggregate);
    }
}