有没有办法添加默认排序字段,以便我可以
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”运算符。
这可能会导致不可预测的结果
我看到了一些意想不到的结果
归功于 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));
}
克尔/布洛姆斯特兰德对这个问题的回答对我来说不太有效
以下更改确实有效:
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));
}
这个问题也可以通过在
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;