Expr 是 LiteOrm 的核心表达式对象模型,本文主要讲解如何构造、组合、复用和理解它的语义。
如果你关心 Lambda / Expr / ExprString 的选型,请继续查看查询指南。
using static LiteOrm.Common.Expr;
var age = Prop("Age");
var userName = Prop("U", "UserName");
var paramValue = Value(18); // 参数化
var constValue = Const("Enabled"); // 直接内嵌
Prop(name):创建属性表达式Prop(alias, name):创建带表别名的属性表达式Value(obj):按参数传递,适合运行时值Const(obj):直接内嵌到 SQL,适合真正的常量using static LiteOrm.Common.Expr;
var expr1 = Prop("Age") >= 18;
var expr2 = Prop("DeptId").In(1, 2, 3);
var expr3 = Prop("Age").Between(18, 30);
var expr4 = Prop("UserName").Contains("admin");
var expr5 = Prop("UserName").Like("%root%");
这些写法都返回 LogicExpr,可以继续组合。
using static LiteOrm.Common.Expr;
var absAge = Func("ABS", Prop("Age"));
var countExpr = Aggregate("COUNT", Prop("Id"), isDistinct: true);
var currentUserFilter = Sql("CurrentUserFilter");
Func(name, args):普通函数Aggregate(name, expr, isDistinct):聚合函数包装Sql(key, arg):注册式动态 SQL 片段,适合运行时上下文过滤ExistsLambda 写法:
var users = await userService.SearchAsync(
u => Exists<Department>(d => d.Id == u.DeptId && d.Name == "研发中心")
);
Expr 写法:
using static LiteOrm.Common.Expr;
var expr = Exists<Department>(
Prop("Id") == Prop("T0", "DeptId")
& Prop("Name") == "研发中心"
);
这类写法适合你想自己明确写出关联条件的场景。
ExistsRelatedLambda 写法:
var users = await userService.SearchAsync(
u => ExistsRelated<DepartmentView>(d => d.Name == "研发中心")
);
Expr 写法:
using static LiteOrm.Common.Expr;
var expr = ExistsRelated<DepartmentView>(
Prop("Name") == "研发中心"
);
ExistsRelated 会根据 ForeignType / TableJoin 等元数据自动补关联条件。
详细匹配逻辑请看关联查询。
using static LiteOrm.Common.Expr;
LogicExpr condition = null;
if (minAge.HasValue)
condition &= Prop("Age") >= minAge.Value;
if (deptId.HasValue)
condition &= Prop("DeptId") == deptId.Value;
if (!string.IsNullOrWhiteSpace(keyword))
condition &= Prop("UserName").Contains(keyword);
& / | 对 null 友好,非常适合做后台筛选器。
using static LiteOrm.Common.Expr;
public static LogicExpr BuildUserSearch(IReadOnlyDictionary<string, string?> query)
{
LogicExpr condition = null;
if (query.TryGetValue("minAge", out var minAgeText) && int.TryParse(minAgeText, out var minAge))
condition &= Prop("Age") >= minAge;
if (query.TryGetValue("keyword", out var keyword) && !string.IsNullOrWhiteSpace(keyword))
condition &= Prop("UserName").Contains(keyword);
return condition;
}
这类写法适合开放查询接口、网关转发和前端条件构造器。
using static LiteOrm.Common.Expr;
LogicExpr extra = null;
extra &= Prop("UserName").Contains("John");
var users = await userService.SearchAsync(
u => u.IsActive == true && extra.To<bool>()
);
如果你想保持 Lambda 的业务可读性,同时又想复用动态 Expr,请继续阅读:Lambda 与 Expr 组合使用。
Expr.From<T>() 链式构建查询using static LiteOrm.Common.Expr;
var query = From<User>()
.Where(Prop("Age") > 18)
.GroupBy(Prop("DeptId"))
.Having(Prop("Id").Count() > 5)
.Select(
Prop("DeptId"),
Prop("Id").Count().As("UserCount")
)
.OrderBy(Prop("UserCount").Desc())
.Section(0, 20);
这是 Expr 最完整的用法:从 FROM 起点一路构造 WHERE / GROUP BY / HAVING / SELECT / ORDER BY / 分页。
可以把 LiteOrm 里的 Expr 大致分成四层:
| 层级 | 代表类型 | 说明 |
|---|---|---|
| 根类型 | Expr |
所有表达式对象的共同基类 |
| 值表达式 | ValueTypeExpr |
能出现在列、函数、比较两侧、SELECT 项里的值 |
| 逻辑表达式 | LogicExpr |
能出现在 WHERE / HAVING / EXISTS 条件里的布尔表达式 |
| SQL 片段 | SqlSegment |
FROM / SELECT / WHERE / ORDER BY 这一类链式 SQL 节点 |
ValueExpr:值或参数PropertyExpr:列引用FunctionExpr:函数调用ValueBinaryExpr:值运算,如 a + bUnaryExpr:一元运算,如 -a、DISTINCT aValueSet:值集合,如 IN (...)、拼接参数集SelectItemExpr:SELECT xxx AS AliasOrderByItemExpr:ORDER BY xxx ASC/DESCLogicBinaryExpr:比较表达式,如 Age >= 18AndExpr:AND 组合OrExpr:OR 组合NotExpr:NOT 组合ForeignExpr:Exists / ExistsRelated 对应的 EXISTS 子查询表达式LambdaExpr:Lambda 转换过程中的包装表达式,通常不需要手写SourceExpr:可作为数据源的 SQL 片段抽象基类TableExpr:表CommonTableExpr:CTETableJoinExpr:JOINFromExpr:FROMSelectExpr:SELECTWhereExpr:WHEREGroupByExpr:GROUP BYHavingExpr:HAVINGOrderByExpr:ORDER BYSectionExpr:分页UpdateExpr:UPDATEDeleteExpr:DELETE如果你只是写业务查询,通常最常接触的是:
PropertyExpr / ValueExprLogicBinaryExpr / AndExpr / OrExprForeignExprSelectExpr / WhereExpr / OrderByExpr| 方法 | 说明 | 示例 |
|---|---|---|
Expr.Prop(name) |
创建属性表达式 | Expr.Prop("Age") |
Expr.Prop(alias, name) |
创建带别名的属性表达式 | Expr.Prop("U", "UserName") |
Expr.Value(value) |
创建参数化值 | Expr.Value(18) |
Expr.Const(value) |
创建常量值 | Expr.Const("Enabled") |
Expr.Null |
SQL NULL | Expr.Null |
Expr.From<T>() |
创建链式查询起点 | Expr.From<User>() |
Expr.Update<T>() |
创建 UPDATE 表达式 | Expr.Update<User>() |
Expr.Delete<T>() |
创建 DELETE 表达式 | Expr.Delete<User>() |
Expr.Exists<T>(innerExpr) |
创建 EXISTS 子查询 | Expr.Exists<Department>(...) |
Expr.ExistsRelated<T>(innerExpr) |
创建自动关联 EXISTS 子查询 | Expr.ExistsRelated<DepartmentView>(...) |
Expr.Lambda<T>(expr) |
将 Lambda 转成 LogicExpr |
Expr.Lambda<User>(u => u.Age > 18) |
Expr.Func(name, args) |
创建函数表达式 | Expr.Func("COUNT", Expr.Prop("Id")) |
Expr.Aggregate(name, expr, isDistinct) |
创建聚合函数表达式 | Expr.Aggregate("COUNT", Expr.Prop("Id"), true) |
Expr.If(condition, then, else) |
IF / CASE WHEN 形式 | Expr.If(... ) |
Expr.Case(cases, elseExpr) |
CASE 表达式 | Expr.Case(... ) |
Expr.Now() |
当前时间戳 | Expr.Now() |
Expr.Today() |
当前日期 | Expr.Today() |
Expr.Sql(key, arg) |
动态 SQL 片段 | Expr.Sql("CurrentUserFilter") |
Expr.Query<T>(expression) |
IQueryable Lambda 转 Expr | Expr.Query<User>(...) |
Expr.Query<T, TResult>(expression) |
带返回值的 IQueryable Lambda 转 Expr | Expr.Query<User, int>(...) |
LiteOrm 为 ValueTypeExpr 和 LogicExpr 提供了常用 C# 运算符重载,因此很多写法看起来和普通表达式非常接近:
| 运算符 | 适用类型 | 返回类型 | 示例 |
|---|---|---|---|
== != > < >= <= |
ValueTypeExpr |
LogicExpr |
Prop("Age") >= 18 |
+ - * / % |
ValueTypeExpr |
ValueTypeExpr |
Prop("Amount") * 0.9m |
一元 - / ~ |
ValueTypeExpr |
ValueTypeExpr |
-Prop("Balance")、~Prop("Flags") |
& \| |
LogicExpr |
LogicExpr |
(Prop("Age") >= 18) & (Prop("Status") == 1) |
! |
LogicExpr |
LogicExpr |
!(Prop("IsDeleted") == true) |
例如:
using static LiteOrm.Common.Expr;
var scoreExpr = (Prop("MathScore") + Prop("ExtraScore")) / 2;
var filter = (Prop("Age") >= 18 & Prop("Status") == 1)
| Prop("UserName").Contains("admin");
+,用 .Concat(...)在手写 Expr 时,ValueTypeExpr 的 + 是“加法”语义,最终 SQL 可能生成 +,对字符串拼接并不跨数据库可靠。
推荐显式使用 concat:
using static LiteOrm.Common.Expr;
var fullName = Prop("FirstName")
.Concat(" ")
.Concat(Prop("LastName"));
Concat(...) 会走底层 SqlBuilder.BuildConcatSql,由不同数据库方言输出 CONCAT(a,b,...) 或 a || b。
注意:在 Lambda 场景下(例如
SearchAsync(u => u.FirstName + " " + u.LastName == "..." )),C# 的字符串+通常会在解析阶段被转换为 concat;但手写 Expr 时请显式使用.Concat(...)。
LogicExpr 的空值友好组合LogicExpr 的 & / | 特别适合做动态筛选器,因为它们对 null 友好:
using static LiteOrm.Common.Expr;
LogicExpr condition = null;
condition &= Prop("Age") >= 18;
condition &= Prop("Status") == 1;
condition |= Prop("IsVip") == true;
规则是:
null & expr => exprexpr & null => exprnull | expr => exprexpr | null => expr这样可以避免每次拼接条件时手动判断“当前条件是否为空”。
ValueTypeExpr / ValueExpr 支持以下标量自动转换:
stringintlongboolDateTimedoubledecimal因此你可以直接写:
using static LiteOrm.Common.Expr;
var expr1 = Prop("Age") >= 18;
var expr2 = Prop("CreateTime") >= DateTime.Today;
var expr3 = Prop("IsEnabled") == true;
var expr4 = Prop("Amount") + 12.5m;
这些字面量会自动变成 ValueExpr,通常等价于显式写法:
Prop("Age") >= Value(18)
Prop("CreateTime") >= Value(DateTime.Today)
运算符重载里出现的普通值默认会走参数化,也就是 Expr.Value(...) 语义,而不是 Expr.Const(...):
using static LiteOrm.Common.Expr;
var expr = Prop("Status") == 1; // 参数化
var constExpr = Prop("Status") == Const(1); // 常量内嵌
选择建议:
Value(...)Const(...)null 没有对应的隐式转换null 不会自动转换为 ValueTypeExpr,因此涉及空判断时应显式使用:
using static LiteOrm.Common.Expr;
var expr1 = Prop("DeletedTime").IsNull();
var expr2 = Prop("DeletedTime") == Expr.Null;
对于数据库语义,通常更推荐 .IsNull() / .IsNotNull(),因为意图更清晰。
除了标量值,LiteOrm 还提供了一些“为了链式 API 更顺手”的隐式转换:
using static LiteOrm.Common.Expr;
var query = From<User>()
.OrderBy(("Age", false)); // (string property, bool ascending) -> OrderByItemExpr
var update = Update<User>()
.Set((Prop("Age"), Prop("Age") + 1)); // (PropertyExpr, ValueTypeExpr) -> SetItem
这类转换的价值在于减少样板代码,让 OrderBy(...)、Set(...) 等 API 更接近自然写法。
建议:混合比较、算术和逻辑运算时,尽量加上括号,避免依赖 C# 运算符优先级去猜测最终表达式结构。
| 方法 | 说明 | 示例 |
|---|---|---|
& / .And(right) |
AND | Prop("Age") > 18 & Prop("DeptId") == 2 |
| / .Or(right) |
OR | condition1 | condition2 |
! / .Not() |
NOT | !Prop("IsDeleted").Equal(true) |
| 方法 | 说明 |
|---|---|
.Equal(v) .NotEqual(v) |
等于 / 不等于 |
.GreaterThan(v) .LessThan(v) |
大于 / 小于 |
.GreaterThanOrEqual(v) .LessThanOrEqual(v) |
大于等于 / 小于等于 |
.In(params items) .In(IEnumerable) .In(Expr) |
IN 集合 / 子查询 |
.Between(low, high) |
BETWEEN |
| 方法 | 说明 |
|---|---|
.Like(pattern) |
LIKE |
.Contains(text) .StartsWith(text) .EndsWith(text) |
常见字符串匹配 |
.RegexpLike(pattern) |
正则匹配 |
.IsNull() .IsNotNull() |
NULL 检查 |
.IfNull(defaultValue) |
空值替换 |
| 方法 | 说明 |
|---|---|
.As(name) |
生成 SelectItemExpr |
.Distinct() |
DISTINCT |
.Count() .Sum() .Avg() .Max() .Min() |
聚合 |
.Asc() .Desc() |
排序 |
.Over(partitionBy) |
窗口函数 |
| 方法 | 说明 |
|---|---|
.Where(condition) |
WHERE |
.GroupBy(props) |
GROUP BY |
.Having(condition) |
HAVING |
.Select(props) |
SELECT |
.OrderBy(props) |
ORDER BY |
.Section(skip, take) |
分页 |
.Set(assignments) |
UPDATE SET |
PropertyExpr、TableExpr、ForeignExpr、FunctionExpr、SelectExpr、SelectItemExpr、CommonTableExpr、GenericSqlExpr 等表达式,在做 Equals / GetHashCode 时,名称与别名按忽略大小写处理。
例如:
Expr.Prop("User", "Name")
Expr.Prop("user", "name")
会被视为相等表达式。
AndExpr / OrExpr 采用 Set 语义AndExpr.Items 与 OrExpr.Items 现在按 Set 语义处理:
Equals / GetHashCode 不再依赖重复分布所以:
new AndExpr(a, a, b)
new AndExpr(a, b)
在组合语义上等价。