graphql-platform
graphql-platform copied to clipboard
Default sort or required order parameter
Is your feature request related to a problem?
The order
parameter for queries when using .UseSorting
is optional. When running queries with .Paging
the query generated looks similar to:
SELECT `x`.`Id`
FROM `MyObject` AS `x`
LIMIT @__p_0
In EntityFramework this logs the warning:
The query uses a row limiting operator ('Skip'/'Take') without an 'OrderBy' operator. This may lead to unpredictable results.
The solution you'd like
Is there any mechanism to setup a default sort inside the SortInputType
?
I noticed there was a DefaultValue
method but I couldn't find any documentation about what is supposed to go here.
If this is not desired, would you consider allowing us to make the parameter not optional?
class MyObjectSortType : SortInputType<MyObject>
{
protected override void Configure(ISortInputTypeDescriptor<MyObject> descriptor)
{
descriptor.BindFieldsExplicitly();
descriptor.Field(f => f.Id);
// Can this be used to set a default sort?
// .DefaultValue(Utf8GraphQLParser.Syntax.ParseValueLiteral("ASC"));
// Proposed fluent interface
descriptor.DefaultValue(x => x.Id, OrderType.ASC);
}
}
Product
Hot Chocolate
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Still relevant, commenting to un-stale the issue.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Still relevant.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
Actually this is already solved in 12 and more elegantly in 13
Actually this is already solved in 12 and more elegantly in 13
@michaelstaib Do you have examples? 🙂
Yes ... this is for version 12
[UsePaging]
[UseFiltering(typeof(AssetFilterInputType))]
[UseSorting(typeof(AssetSortInputType))]
public IQueryable<Asset> GetAssets(AssetContext context, IResolverContext resolverContext)
=> resolverContext.ArgumentLiteral<IValueNode>("order").Kind is SyntaxKind.NullValue
? context.Assets.OrderBy(t => t.Price!.TradableMarketCapRank)
: context.Assets;
@michaelstaib I'm using 13, is it different?
You can use the same.
You can use the same.
Would you mind elaborating a bit more on the v13 part? You mentioned above that it was solved more elegantly in 13, so what does that mean exactly? :)
@michaelstaib
Would you consider reopening this to look at terser solutions as proposed by the OP?
Defining this in the SortInputType
seems like a cleaner option, and avoids having to repeatedly look at the order
argument in resolvers.
We decided on a different direction, automatically applying a sort by ID to ensure deterministic ordering.
/// <summary>
/// Extends the <see cref="UseSortingAttribute" /> to apply an additional order by ID, to ensure
/// deterministic ordering.
/// </summary>
public sealed class UseDeterministicSortingAttribute<T> : UseSortingAttribute
{
public UseDeterministicSortingAttribute([CallerLineNumber] int order = 0)
: base(typeof(T), order) { }
protected override void OnConfigure(
IDescriptorContext context,
IObjectFieldDescriptor descriptor,
MemberInfo member)
{
descriptor.Use(next => async middlewareContext =>
{
// Execute the regular sorting middleware.
await next(middlewareContext);
if (middlewareContext.Result is IQueryable queryable)
{
// Only apply deterministic sorting when the element type is assignable to IEntity.
if (!queryable.ElementType.IsAssignableTo(typeof(IEntity)))
{
return;
}
// Get the name of the ordering method to invoke, depending on whether or not
// ordering has already been applied.
var methodName = queryable.Expression.Type.IsAssignableTo(typeof(IOrderedQueryable))
? nameof(Queryable.ThenBy)
: nameof(Queryable.OrderBy);
// Find the appropriate method to invoke.
var method = typeof(Queryable)
.GetMethods()
.Single(m => m.Name == methodName && m.GetParameters().Length == 2)
.MakeGenericMethod(queryable.ElementType, typeof(long));
// Create the lambda expression for ordering (e => e.Id).
var parameter = Expression.Parameter(queryable.ElementType, "e");
var body = Expression.Property(parameter, nameof(IEntity.Id));
var keySelector = Expression.Lambda(body, parameter);
// Invoke the ordering method on the queryable.
middlewareContext.Result = method.Invoke(
queryable,
new object[] { queryable, keySelector });
}
});
base.OnConfigure(context, descriptor, member);
}
}
There is also this option if you prefer to set a specific default order.