oData v4 规范不支持这种类型的嵌套排序。但这并不意味着您无法使用 oData 获得类似的结果集。
问题是我们想要排序子集合内一行中a属性的顶级响应的结果。
这不受支持,您只能根据该集合中行的属性对每个集合进行排序。 $orderby 只能引用单例属性路径来对结果进行排序。您可以在 $expand 结果中指定记录的排序顺序,但 OOTB 不能使用集合中任意行的属性来指定顶级排序顺序。
您检索此类数据的选项分为 3 类:
-
在控制器上创建自定义端点以执行结果集
- 这应该是最容易实现的,但只有在您控制 API 时才可行。
- 对上述内容的 Linq 查询非常简单,示例控制器函数可能如下所示:
[HttpGet]
public IHttpActionResult GetRecentTaskRuns()
{
var query = from task in _db.Tasks
let lastRun = task.TaskRuns.OrderByDescending(t => t.RunAt).FirstOrDefault()
select new
{
task.Id,
task.Name,
task.State,
// Consider not returning this as a collection at all
//TaskRuns = task.TaskRuns.OrderByDescending(t => t.RunAt).Take(1).ToArray(),
LastChangedAt,
LastChangedBy,
// Just return LastRun, instead of TaskRuns.
LastRun = lastRun
};
return Json(query.OrderBy(t => t.LastRun.RunBy));
}
不要害怕在你的控制器上创建自定义端点来实现特定的业务需求,你的 API 的目的是方便数据访问,是的 OData 为调用者公开了美妙的语法来完全控制要下载的数据,但是你可以帮助他们。
-
将您的查询改为从 TaskRuns 控制器中检索
- API 端不需要进行任何更改
- 这会改变结果集和数据的结构
- 您可以使用
$apply 返回一个group by 结果,这将类似于原始要求
- 有关
$apply 的更多详细信息,请参见下文
-
覆盖
EnableQueryAttribute 或以其他方式实现您自己的 $orderby 解释器
- 这远远超出了本次讨论的范围,但是,您可以为
$orderby 实现自己的解释器以及如何将其应用于底层数据库查询,如果您有能力这样做的话,您应该考虑选项 1...
如果您不想或无法修改 API 本身,只有上述选项 2 会为您提供帮助。
让我们探究一下原因,例如,如果每个Task 有多个TaskRuns,那么应该使用其中的哪一个TaskRuns 来获取RunBy 的值?
您可能想尝试使用 MIN 或 MAX 函数,但它们不起作用,OData v4 规范中没有以下规定:
{{hostUrl}}/odata/TasksOData?$orderby=Max(TaskRuns/RunBy)
or
{{hostUrl}}/odata/TasksOData?$orderby=TaskRuns/Max(RunBy)
使用 $Apply
>
当您想根据子集合限制结果时,我们可以使用$apply 函数,但是类似的规则类型适用于只能对根集合进行操作。因此,切换您的查询,以便子集合 IS 成为根!
即使使用$apply,我也无法获得与原始查询所暗示的完全相同的结构或顺序,但此查询显示了另一种选择
注意:如果您的日期字段不是 DateTimeOffset
,您将无法使聚合工作
{{hostUrl}}/odata/TaskRunsOData?
$apply=groupby((Task/Id,Task/Name,Task/State,Task/LastChangedAt,Task/LastChangedBy), aggregate(RunAt with max as RunAt,RunBy with max as RunBy)&$top=16&$orderby=RunBy DESC&$count=true
应该返回 16 个项目(或更少),其结构类似于:
{
"@odata.context": …,
"@odata.count": 16,
"value": [
{
"@odata.id": null,
"RunAt": …,
"RunBy": …,
"Task": {
"@odata.id": null,
"Id": 7,
"Name": 'TaskName',
"State": 'Pending',
"LastChangedAt": …,
"LastChangedBy": …,
}
},
…
]
}