How to specify a default Sort with Hotchocolate and EF Core?

Viewed 228

Is there a way to add a Default Sort field, so that I can UsePaging and UserSorting, but if no order is specified I add a field, such as Id. But if the user does specify an order, then don't add the default.

For example, I can add the default sort to the query method, but then no other sorting works

[UseContext]
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Property> GetProperties([ScopedService] PropContext dbContext)
{
    return dbContext.Properties
                    .OrderBy(p => p.Id); // Default sort by Prop Id
} 

If don't have a sort, then Entity Framework shows a warning:

The query uses a row limiting operator ('Skip'/'Take') without an 'OrderBy' operator.
This may lead to unpredictable results

And I've seen some unexpected results

2 Answers

Credit to S Blomstrand

Using these extension methods

public static bool HasOrderByArgument(this IResolverContext context,
                                       string argumentName = "order")
{
    try
    {
        var orderByArgument = context.ArgumentLiteral<IValueNode>(argumentName);
        if (orderByArgument != NullValueNode.Default && orderByArgument != null)
        {
            return true;
        }
    }
    catch
    {
        return false;
    }

    return false;
}

public static IQueryable<T> OrderByArgumentOrDefault<T>(this IQueryable<T> query, IResolverContext context, 
                                                        Func<IQueryable<T>> func, string argumentName = "order")
{
    if (context.HasOrderByArgument(argumentName))
    {
        return query;
    }

    return func.Invoke();
}

It can then be call as follows:

[UseContext]
[UsePaging]
[UseProjection]
[UseFiltering]
[UseSorting]
public IQueryable<Property> GetProperties([ScopedService] PropContext dbContext)
{
    return dbContext.Properties
                    .OrderByArgumentOrDefault(context, () => properties.OrderBy(p => p.Id));
} 

the Kerr/Blomstrand answer to this question didn't quite work for me as written

The following changes did work:

  • you need an IResolverContext argument in the query method (the runtime will fill it, no attribute needed)
  • change the Func to an Expression
   public static IQueryable<T> OrderByArgumentOrDefault<T>(
        this IQueryable<T> query,
        IResolverContext context,
        Expression<Func<IQueryable<T>>> expression,
        string argumentName = "order")
    {
        return context.HasOrderByArgument(argumentName) ? query : expression.Compile().Invoke();
    }

public IQueryable<Property> GetProperties(
  [ScopedService] PropContext dbContext,
  IResolverContext context)
{
    return dbContext.Properties
                    .OrderByArgumentOrDefault(context, () => dbContext.Properties.OrderBy(p => p.Id));
} 
Related