LiteOrm在线文档

Expr 使用指南

Expr 是 LiteOrm 的核心表达式对象模型,本文主要讲解如何构造、组合、复用和理解它的语义。 如果你关心 Lambda / Expr / ExprString 的选型,请继续查看查询指南

1. 创建基础表达式

1.1 属性、值与常量

using static LiteOrm.Common.Expr;

var age = Prop("Age");
var userName = Prop("U", "UserName");

var paramValue = Value(18);       // 参数化
var constValue = Const("Enabled"); // 直接内嵌

1.2 比较、字符串与集合

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,可以继续组合。

1.3 函数、聚合与动态 SQL

using static LiteOrm.Common.Expr;

var absAge = Func("ABS", Prop("Age"));
var countExpr = Aggregate("COUNT", Prop("Id"), isDistinct: true);
var currentUserFilter = Sql("CurrentUserFilter");

2. 子查询与关联过滤

2.1 显式 Exists

Lambda 写法:

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") == "研发中心"
);

这类写法适合你想自己明确写出关联条件的场景。

2.2 自动关联 ExistsRelated

Lambda 写法:

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 等元数据自动补关联条件。
详细匹配逻辑请看关联查询

3. 动态拼装 Expr

3.1 按参数累加条件

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 友好,非常适合做后台筛选器。

3.2 从 QueryString / Dictionary 构造

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;
}

这类写法适合开放查询接口、网关转发和前端条件构造器。

3.3 和 Lambda 组合使用

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 组合使用

4. 用 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 / 分页

5. Expr 类型总览

可以把 LiteOrm 里的 Expr 大致分成四层:

层级 代表类型 说明
根类型 Expr 所有表达式对象的共同基类
值表达式 ValueTypeExpr 能出现在列、函数、比较两侧、SELECT 项里的值
逻辑表达式 LogicExpr 能出现在 WHERE / HAVING / EXISTS 条件里的布尔表达式
SQL 片段 SqlSegment FROM / SELECT / WHERE / ORDER BY 这一类链式 SQL 节点

5.1 ValueTypeExpr 体系

5.2 LogicExpr 体系

5.3 SqlSegment 体系

5.4 直接挂在 Expr 下的语句表达式

如果你只是写业务查询,通常最常接触的是:

6. Expr 静态方法速查

方法 说明 示例
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>(...)

7. 运算符重载与隐式类型转换

7.1 运算符重载速览

LiteOrm 为 ValueTypeExprLogicExpr 提供了常用 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(...)

7.2 LogicExpr 的空值友好组合

LogicExpr& / | 特别适合做动态筛选器,因为它们对 null 友好:

using static LiteOrm.Common.Expr;

LogicExpr condition = null;
condition &= Prop("Age") >= 18;
condition &= Prop("Status") == 1;
condition |= Prop("IsVip") == true;

规则是:

这样可以避免每次拼接条件时手动判断“当前条件是否为空”。

7.3 标量的隐式类型转换

ValueTypeExpr / ValueExpr 支持以下标量自动转换:

因此你可以直接写:

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)

7.4 这些隐式值默认是参数,不是 SQL 内嵌常量

运算符重载里出现的普通值默认会走参数化,也就是 Expr.Value(...) 语义,而不是 Expr.Const(...)

using static LiteOrm.Common.Expr;

var expr = Prop("Status") == 1;      // 参数化
var constExpr = Prop("Status") == Const(1); // 常量内嵌

选择建议:

7.5 null 没有对应的隐式转换

null 不会自动转换为 ValueTypeExpr,因此涉及空判断时应显式使用:

using static LiteOrm.Common.Expr;

var expr1 = Prop("DeletedTime").IsNull();
var expr2 = Prop("DeletedTime") == Expr.Null;

对于数据库语义,通常更推荐 .IsNull() / .IsNotNull(),因为意图更清晰。

7.6 其它常见隐式转换

除了标量值,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# 运算符优先级去猜测最终表达式结构。

8. ExprExtensions 速查

8.1 逻辑组合

方法 说明 示例
& / .And(right) AND Prop("Age") > 18 & Prop("DeptId") == 2
| / .Or(right) OR condition1 | condition2
! / .Not() NOT !Prop("IsDeleted").Equal(true)

8.2 比较与集合

方法 说明
.Equal(v) .NotEqual(v) 等于 / 不等于
.GreaterThan(v) .LessThan(v) 大于 / 小于
.GreaterThanOrEqual(v) .LessThanOrEqual(v) 大于等于 / 小于等于
.In(params items) .In(IEnumerable) .In(Expr) IN 集合 / 子查询
.Between(low, high) BETWEEN

8.3 字符串与 NULL

方法 说明
.Like(pattern) LIKE
.Contains(text) .StartsWith(text) .EndsWith(text) 常见字符串匹配
.RegexpLike(pattern) 正则匹配
.IsNull() .IsNotNull() NULL 检查
.IfNull(defaultValue) 空值替换

8.4 别名、聚合、排序

方法 说明
.As(name) 生成 SelectItemExpr
.Distinct() DISTINCT
.Count() .Sum() .Avg() .Max() .Min() 聚合
.Asc() .Desc() 排序
.Over(partitionBy) 窗口函数

8.5 链式 SQL 构建

方法 说明
.Where(condition) WHERE
.GroupBy(props) GROUP BY
.Having(condition) HAVING
.Select(props) SELECT
.OrderBy(props) ORDER BY
.Section(skip, take) 分页
.Set(assignments) UPDATE SET

9. Equals 与组合语义

9.1 名称和别名比较忽略大小写

PropertyExprTableExprForeignExprFunctionExprSelectExprSelectItemExprCommonTableExprGenericSqlExpr 等表达式,在做 Equals / GetHashCode 时,名称与别名按忽略大小写处理

例如:

Expr.Prop("User", "Name")
Expr.Prop("user", "name")

会被视为相等表达式。

9.2 AndExpr / OrExpr 采用 Set 语义

AndExpr.ItemsOrExpr.Items 现在按 Set 语义处理:

所以:

new AndExpr(a, a, b)
new AndExpr(a, b)

在组合语义上等价。

10. 相关链接