LiteOrm 支持使用 SelectExpr.With(name) 构建公共表表达式(CTE,WITH 子句)。这一章单独说明 CTE 的适用场景、构建方式,以及与 ExprString 的边界。
CTE 适合以下场景:
Expr / SelectExpr 的结构化构建能力,而不是完全手写 SQL如果只是一次性子查询、简单过滤或简单分页,通常直接使用普通 Expr / SelectExpr 就够了。
先定义一个 SelectExpr,再用 .With(name) 包装为 CTE:
using static LiteOrm.Common.Expr;
var cteDef = new SelectExpr(
From(typeof(User)),
Prop("Id").As("Id"),
Prop("UserName").As("Name"),
Prop("Age").As("Age")
);
var query = cteDef.With("ActiveUsers")
.Where(Prop("Age") >= 18)
.OrderBy(Prop("Name").Asc())
.Select(Prop("Name"), Prop("Age"));
生成的 SQL 形态:
WITH [ActiveUsers] AS (
SELECT [Id] AS [Id], [UserName] AS [Name], [Age] AS [Age]
FROM [Users]
)
SELECT [Name], [Age]
FROM [ActiveUsers]
WHERE [Age] >= 18
ORDER BY [Name]
CTE 很适合先聚合、再过滤:
using static LiteOrm.Common.Expr;
var cteDef = From<User>()
.Where(Prop("Age") >= 25)
.GroupBy(Prop("DeptId"))
.Select(
Prop("DeptId"),
Prop("Id").Count().As("UserCount"),
Prop("Age").Avg().As("AvgAge")
);
var query = cteDef.With("DeptAdultStats")
.Where(Prop("UserCount") >= 2)
.OrderBy(Prop("UserCount").Desc())
.Select(Prop("DeptId"), Prop("UserCount"), Prop("AvgAge"));
除了在单个主查询里引用一次,CTE 也可以在 UNION / UNION ALL 两侧复用:
using static LiteOrm.Common.Expr;
var adultUsers = From<User>()
.Where(Prop("Age") >= 18)
.Select(
Prop("UserName").As("Name"),
Prop("Age").As("Age"))
.With("AdultUsers");
var query = adultUsers
.Where(Prop("Age") < 30)
.Select(Prop("Name"), Prop("Age"), Const("18-29").As("AgeGroup"))
.UnionAll(
adultUsers
.Where(Prop("Age") >= 30)
.Select(Prop("Name"), Prop("Age"), Const("30+").As("AgeGroup")));
这种写法的重点是:
With("AdultUsers") 的结果保存下来CommonTableExpr 构建查询WITH AdultUsers AS (...) 定义LiteOrm 现在会先收集整棵表达式树里的 CTE,再按别名做校验:
WITHInvalidOperationException这意味着你可以在复杂查询里复用同一个 CTE 表达式;或者使用同一个 CTE 别名,但必须保证其定义一致。
当 Expr / SelectExpr 被序列化为 JSON 时:
例如后续引用会被压缩成:
{"$cte":"ActiveUsers"}
反序列化时,LiteOrm 会自动把它还原回首个定义对应的 CTE。
ExprString 与 CTE 的边界ExprString 不支持把 CTE 结构当作 Expr 片段自动展开。也就是说:
SelectExpr.With(name) / CommonTableExpr 是 Expr / SelectExpr 查询模型ExprString 只适合插入普通 Expr 条件片段或手写 SQL 片段ExprString 使用 WITH,必须手动写 WITH 部分下面这种思路并不成立:先构造 CTE Expr,再希望它作为 ExprString 片段自动嵌入。
var cteQuery = cteDef.With("ActiveUsers");
// 不支持把 cteQuery 当成 ExprString 片段自动展开成 WITH SQL
如果场景必须走 ExprString / DAO 原生 SQL,请手动写 WITH 部分:
int minAge = 18;
var result = await dataViewDAO.Search(
$"""
WITH ActiveUsers AS (
SELECT Id, UserName, Age
FROM Users
WHERE Age >= {minAge}
)
SELECT Id, UserName, Age
FROM ActiveUsers
""",
isFull: true
).GetResultAsync();
这里的 WITH ... 是你手写的 SQL,LiteOrm 只负责继续处理插值参数。
也可通过插入 SelectExpr 方式构造:
using static LiteOrm.Expr;
Expr cteDef = From(typeof(User))
.Select(
Prop("Id"),
Prop("UserName"),
Prop("Age")
).Where(Prop("Age") >= 18);
var result = await dataViewDAO.Search(
$"""
WITH ActiveUsers AS (
{cteDef}
)
SELECT Id, UserName, Age
FROM ActiveUsers
""",
isFull: true
).GetResultAsync();