这篇文档同样属于扩展接入方案:当前端已经不是“固定几个筛选项”,而是需要动态组合字段、操作符、排序和分页时,可以直接提交 LiteOrm 原生 Expr JSON。
推荐做法是:前端按照 JsonSerializer.Serialize<Expr>(...) 的实际输出形状来构造 JSON,而不是另外发明一套只给前端使用的 DSL。
| 场景 | 推荐方式 | 原因 |
|---|---|---|
| 动态多条件查询 | 原生 Expr | 字段、操作符和值都可以在运行时组合 |
| AND / OR 可切换 | 原生 Expr | 更容易表达复合逻辑 |
| 多列排序 | 原生 Expr | OrderBys 直接支持多列 |
| 自定义分页 | 原生 Expr | Skip / Take 原生可用 |
在前端直接提交 Expr 时,建议先统一下面三条约定:
LiteOrm 在序列化 SectionExpr -> OrderByExpr -> WhereExpr 时,输出形状大致如下:
{
"$section": {
"$orderby": {
"$where": null,
"Where": {
"$": "and",
"Items": [
{
"$": "==",
"Left": { "#": "Status" },
"Right": { "@": "Pending" }
},
{
"$": ">=",
"Left": { "#": "TotalAmount" },
"Right": { "@": 300 }
}
]
}
},
"OrderBys": [
{
"Field": { "#": "CreatedTime" },
"Asc": false
}
]
},
"Skip": 0,
"Take": 5
}
关键点:
$section 的值表示它的 SourceSkip / Take 写在同层$orderby 的值表示它的 SourceOrderBys 写在同层$where 的值表示它的 Source;如果没有上游片段,可以是 nullWhere 写在同层const logicExpr = {
"$": "and",
"Items": [
{ "$": "==", "Left": { "#": "Status" }, "Right": { "@": "Pending" } },
{ "$": ">=", "Left": { "#": "TotalAmount" }, "Right": { "@": 300 } }
]
};
WhereExpr 的序列化结果let expr = {
"$where": null,
"Where": logicExpr
};
OrderByExpr 的序列化结果expr = {
"$orderby": expr,
"OrderBys": [
{ "Field": { "#": "CreatedTime" }, "Asc": false },
{ "Field": { "#": "TotalAmount" }, "Asc": true }
]
};
SectionExpr 的序列化结果expr = {
"$section": expr,
"Skip": 0,
"Take": 5
};
const payload = {
"$section": {
"$orderby": {
"$where": null,
"Where": {
"$": "contains",
"Left": { "#": "CustomerName" },
"Right": { "@": "Contoso" }
}
},
"OrderBys": [
{ "Field": { "#": "CreatedTime" }, "Asc": false }
]
},
"Skip": 0,
"Take": 5
};
const result = await demoApp.apiFetch("/api/orders/query/expr", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload)
});
后端通常直接按 Expr 接收这份 JSON,然后解析出:
随后再补充权限过滤。对于非 Admin 用户,后端会自动附加:
using static LiteOrm.Common.Expr;
Prop(nameof(DemoOrder.CreatedByUserId)) == currentUser.Id
原生 Expr 查询同样经常需要返回 total,因此也适合给 Count 增加短时缓存。
一个适合示例项目的做法是:
ExprExpr 作为 count 缓存键LiteOrm 的 Expr 已经实现了结构化 Equals/GetHashCode,因此相同结构的原生 Expr 过滤条件在连续翻页时可以复用同一个 count 结果,而不需要先转成 JSON 再做键。
OrderBy、Skip、Take 不影响总数时,可以只按最终过滤条件缓存 count"$": "section" / Source推荐直接和 LiteOrm 实际序列化结果保持一致,而不是再包装一层自定义结构。
Skip / Take 塞进 $section 对象内部它们应该和 $section 平级,而不是写进 $section 的值里面。
OrderBys 塞进 $orderby 的值里面OrderBys 应该和 $orderby 平级;$orderby 的值只表示它的 Source。