You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在GraphQL(Hot Chocolate)中实现非null值优先的ASC排序?

Hot Chocolate GraphQL 实现非Null日期字段前置的排序需求

我用Hot Chocolate构建GraphQL服务,启用了排序功能,但按日期字段earliestPaymentDateTime排序时,null值会夹杂在结果里。需要实现非null值按ASC排序在前,null值排在最后的效果。

当前查询与输出

查询语句

query {
  getRecords(
    skip: 0
    take: 10
    where: {
      and: [
        { location: { contains: "MyLocation" } }
      ]
    }
    order: { earliestPaymentDateTime: DESC }
  ) {
    items {
      earliestPaymentDateTime
    }
  }
}

当前输出

{
  "data": {
    "getRecords": {
      "items": [
        {
          "earliestPaymentDateTime": "2024-03-25T16:00:51.830Z"
        },
        {
          "earliestPaymentDateTime": "2024-03-20T12:02:42.000-04:00"
        },
        {
          "earliestPaymentDateTime": "2024-03-18T20:48:12.000Z"
        },
        {
          "earliestPaymentDateTime": "2024-02-08T23:05:28.598Z"
        },
        {
          "earliestPaymentDateTime": "2024-02-01T07:00:00.000Z"
        },
        {
          "earliestPaymentDateTime": "2024-01-30T07:00:00.000Z"
        },
        {
          "earliestPaymentDateTime": "2024-01-25T17:12:49.221Z"
        },
        {
          "earliestPaymentDateTime": "2023-12-08T22:22:46.000Z"
        },
        {
          "earliestPaymentDateTime": null
        },
        {
          "earliestPaymentDateTime": null
        }
      ]
    }
  }
}

期望输出

{
  "data": {
    "getRecords": {
    "items": [
        {
          "earliestPaymentDateTime": "2023-12-08T22:22:46.000Z"
        },
        {
          "earliestPaymentDateTime": "2024-01-25T17:12:49.221Z"
        },
        {
          "earliestPaymentDateTime": "2024-01-30T07:00:00.000Z"
        },
        {
          "earliestPaymentDateTime": "2024-02-01T07:00:00.000Z"
        },
        {
          "earliestPaymentDateTime": "2024-02-08T23:05:28.598Z"
        },
        {
          "earliestPaymentDateTime": "2024-03-18T20:48:12.000Z"
        },
        {
          "earliestPaymentDateTime": "2024-03-20T12:02:42.000-04:00"
        },
        {
          "earliestPaymentDateTime": "2024-03-25T16:00:51.830Z"
        },
        {
          "earliestPaymentDateTime": null
        },
        {
          "earliestPaymentDateTime": null
        }
      ]
    }
  }
}

尝试过的方案及问题

理想的排序逻辑应该是:

.OrderByDescending(x => x.EarliestPaymentDateTime.HasValue)
.ThenBy(x => x.EarliestPaymentDateTime)
.ThenBy(x => SOME_OTHER_FIELD)

但之前自定义的排序代码没有达到预期,反而让null值夹杂更混乱:

public class ExceptionSortType: SortInputType<ExceptionResponseDto>
{
    protected override void Configure(ISortInputTypeDescriptor<ExceptionResponseDto> descriptor)
    {
        descriptor.BindFieldsExplicitly();
        descriptor.Field(x => x.EarliestPaymentDateTime).Type<AscOnlySortEnumType>();
    }
}

public class AscOnlySortEnumType : DefaultSortEnumType
{
    protected override void Configure(ISortEnumTypeDescriptor descriptor)
    {
        descriptor.Operation(DefaultSortOperations.Descending);
    }
}

执行后错误结果(部分):

"items": [
        {
          "earliestPaymentDateTime": "2023-12-08T22:22:46.000Z"
        },
        {
          "earliestPaymentDateTime": null
        },
        {
          "earliestPaymentDateTime": null
        },
        {
          "earliestPaymentDateTime": "2024-01-25T17:12:49.221Z"
        },
        {
          "earliestPaymentDateTime": null
        },
        {
          "earliestPaymentDateTime": "2024-01-30T07:00:00.000Z"
        },
        ...
      ]

正确实现方案

要实现非Null值前置的排序,需要自定义排序字段的表达式转换器,把排序逻辑转换成需要的OrderByDescending + ThenBy组合:

步骤1:自定义排序操作的表达式处理

创建自定义排序输入类型,重写字段的排序逻辑:

public class ExceptionSortType : SortInputType<ExceptionResponseDto>
{
    protected override void Configure(ISortInputTypeDescriptor<ExceptionResponseDto> descriptor)
    {
        descriptor.BindFieldsExplicitly();
        
        // 为earliestPaymentDateTime字段自定义排序逻辑
        descriptor.Field(x => x.EarliestPaymentDateTime)
            .Sort((context, descending) => 
            {
                // 构建HasValue的表达式
                var hasValueExpr = Expression.Property(
                    context.GetInstance(), 
                    nameof(ExceptionResponseDto.EarliestPaymentDateTime)
                ).Property(nameof(Nullable<DateTime>.HasValue));
                
                // 构建日期值的表达式
                var dateValueExpr = Expression.Property(
                    context.GetInstance(), 
                    nameof(ExceptionResponseDto.EarliestPaymentDateTime)
                ).Property(nameof(Nullable<DateTime>.Value));

                // 先按是否有值排序(非Null在前)
                var firstOrder = context.OrderByDescending(hasValueExpr);
                
                // 再按日期值升序排列,满足需求中的ASC排序
                return firstOrder.ThenBy(dateValueExpr);
            });
    }
}

步骤2:注册自定义排序类型

在GraphQL服务配置中注册该类型:

services.AddGraphQLServer()
    .AddQueryType<Query>()
    .AddSorting()
    .AddSortInputType<ExceptionSortType>();

步骤3:执行查询

查询时指定按earliestPaymentDateTime升序排列即可:

query {
  getRecords(
    skip: 0
    take: 10
    where: { location: { contains: "MyLocation" } }
    order: { earliestPaymentDateTime: ASC }
  ) {
    items {
      earliestPaymentDateTime
    }
  }
}

逻辑说明

  • 通过Sort()方法自定义排序逻辑,先按EarliestPaymentDateTime.HasValue降序排列,确保非Null值全部排在前面;
  • 再按日期值升序排列,满足需求中的排序顺序;
  • 该逻辑会被转换成LINQ表达式,支持EF Core等数据库查询提供器,不会出现内存排序的性能问题。

如果需要支持日期降序且非Null在前,只需将ThenBy替换为ThenByDescending即可。


内容的提问来源于stack exchange,提问作者GThree

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 19:05:21