如何使用 Hotchocolate 和 EF Core 指定默认排序?

问题描述 投票:0回答:3

有没有办法添加默认排序字段,以便我可以

UsePaging
UserSorting
,但如果没有指定顺序,我会添加一个字段,例如 Id。但如果用户确实指定了顺序,则不要添加默认值。

例如,我可以将默认排序添加到查询方法中,但其他排序就不起作用了

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

如果没有排序,则实体框架会显示警告:

查询使用行限制运算符(“Skip”/“Take”),而不使用“OrderBy”运算符。
这可能会导致不可预测的结果

我看到了一些意想不到的结果

entity-framework-core hotchocolate
3个回答
5
投票

归功于 S Blomstrand

使用这些扩展方法

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();
}

然后可以按如下方式调用:

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

1
投票

克尔/布洛姆斯特兰德对这个问题的回答对我来说不太有效

以下更改确实有效:

  • 您在查询方法中需要一个
    IResolverContext
    参数(运行时将填充它,不需要属性)
  • Func
    更改为
    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));
} 

0
投票

这个问题也可以通过在

UseSorting
UsePaging
之间添加自定义中间件来解决,以在任何明确指定的排序顺序之后附加唯一的“平局打破”排序顺序。

与 Kerr/Blomstrand 解决方案相比,我个人更喜欢此解决方案的原因是,即使已指定显式排序顺序,它也能保证每一行都有不同的排序顺序。

假设您有一个所有实体类型都实现的

IEntityId
接口:

public interface IEntityId
{
    public long Id { get; }
}

然后可以如下定义中间件:

public class UseTieBreakSortByIdAttribute<T> : ObjectFieldDescriptorAttribute where T : IEntityId
{
    public UseTieBreakSortByIdAttribute([CallerLineNumber] int order = 0) => this.Order = order;

    protected override void OnConfigure(
        IDescriptorContext context, IObjectFieldDescriptor descriptor, MemberInfo member) =>
        descriptor.Use(next => async middlewareContext =>
        {
            await next(middlewareContext);

            if (middlewareContext.Result is IOrderedQueryable<T> queryable)
            {
                var visitor = new IsOrderedVisitor();
                visitor.Visit(queryable.Expression);

                middlewareContext.Result = visitor.HasOrderBy ?
                    queryable.ThenBy(e => e.Id) :
                    queryable.OrderBy(e => e.Id);
            }
        });

    private sealed class IsOrderedVisitor : ExpressionVisitor
    {
        public bool HasOrderBy { get; private set; }

        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            if (!this.HasOrderBy && node.Method.Name switch
            {
                nameof(Enumerable.OrderBy) => true,
                nameof(Enumerable.OrderByDescending) => true,
                _ => false,
            })
            {
                this.HasOrderBy = true;
            }

            return base.VisitMethodCall(node);
        }
    }
}

并应用在排序和分页中间件之间:

[UsePaging]
[UseTieBreakSortById<Comment>]
[UseSorting]
public IQueryable<Comment> Comments(AppDbContext dbContext) =>
    dbContext.Comments;
© www.soinside.com 2019 - 2024. All rights reserved.