LiteOrm在线文档

Query Guide

LiteOrm supports three main query styles: Lambda, Expr, and ExprString.
Lambda is also converted into Expr first and then translated into SQL through the same pipeline.
This page focuses on how to choose between them and the most common query entry points. If you want the full Expr construction model, static methods, extension methods, and composition semantics, start with the Expr Guide.

1. Comparing the three query styles

Style Syntax Best for Type safety
Lambda u => u.Age > 18 Fixed conditions and clear business intent ✅ Strong
Expr Expr.Prop("Age") > 18 Dynamic composition, query builders, admin filtering ✅ Compile-time
ExprString $"WHERE {expr}" DAO-side condition fragments or full SQL ❌ Runtime

1.1 Practical guidance

2. Lambda query entry points

2.1 Basic filters

var users = await userService.SearchAsync(u => u.Age >= 18);
var users = await userService.SearchAsync(u => u.UserName.Contains("admin"));
var users = await userService.SearchAsync(u => new[] { 1, 2, 3 }.Contains(u.Id));

2.2 Sorting and paging

var page = await userService.SearchAsync(
    q => q.Where(u => u.Age >= 18)
          .OrderByDescending(u => u.CreateTime)
          .Skip(0)
          .Take(20)
);

2.3 Variable capture and parameterization

var keyword = "admin";
var users = await userService.SearchAsync(u => u.UserName.Contains(keyword));

Variables declared outside the Lambda are parameterized.
For values such as DateTime.Now, assign them to a variable first if you want them parameterized.

3. Exists and ExistsRelated

3.1 Explicit Exists

Lambda style:

using static LiteOrm.Common.Expr;

var users = await userService.SearchAsync(
    u => Exists<Department>(d => d.Id == u.DeptId && d.Name == "R&D")
);

Expr style:

using static LiteOrm.Common.Expr;

var expr = Exists<Department>(
    Prop("Id") == Prop("T0", "DeptId") & Prop("Name") == "R&D"
);
var users = await userService.SearchAsync(expr);

Use this when you want to control the correlation condition yourself.

Lambda style:

using static LiteOrm.Common.Expr;

var users = await userService.SearchAsync(
    u => ExistsRelated<DepartmentView>(d => d.Name == "R&D")
);

Expr style:

using static LiteOrm.Common.Expr;

var expr = ExistsRelated<DepartmentView>(Prop("Name") == "R&D");
var users = await userService.SearchAsync(expr);

Use this when relationships are already declared in the model and you only want to filter the main table by related-table conditions.
For matching rules, inheritance behavior, and ConstFilter interaction, see Associations.

4. Expr query entry point

using static LiteOrm.Common.Expr;

LogicExpr condition = null;

if (minAge.HasValue)
    condition &= Prop("Age") >= minAge.Value;

if (!string.IsNullOrWhiteSpace(keyword))
    condition &= Prop("UserName").Contains(keyword);

var users = await userService.SearchAsync(condition);

The main value of Expr is that you can build first, then compose, then reuse. Lambda queries also go through Expr before SQL generation, so they are not a separate feature stack.

And for the full Expr construction model, static methods, extension methods, and composition semantics, see Expr Guide.

5. ExprString Interpolated Strings

ExprString lets you embed Expr objects and parameter values directly inside interpolated strings. It is suitable when the DAO layer needs to build a Search condition fragment or a full SQL statement manually. Service APIs do not expose a public ExprString query overload.

5.1 Basic usage

using static LiteOrm.Common.Expr;

var condition = Prop("Age") >= 18;
var users = await userViewDAO.Search(
    $"WHERE {condition} ORDER BY CreateTime DESC"
).ToListAsync();

5.2 Parameterization and safety

using static LiteOrm.Common.Expr;

int minAge = 18;
var result = await userViewDAO.Search(
    $"WHERE {Prop("Age")} >= {minAge}"
).ToListAsync();

Regular interpolated values are still parameterized. Embedded Expr objects are rendered as SQL fragments before they are inserted into the final command text.
That is why it is better to interpolate structured objects such as Expr.Prop(...), Expr.Value(...), and LogicExpr instead of handwriting large amounts of column/value text.

Recommendations:

When you hand-write identifiers, you can use [ and ] as provider-agnostic quote placeholders:

var result = await dataViewDAO.Search(
    $"SELECT [Id], [UserName] FROM [Users] WHERE [Age] >= {minAge}",
    isFull: true
).GetResultAsync();

ExprString does not automatically expand CommonTableExpr. If you need CTE, write the full WITH ... SELECT ... SQL directly, or build the WITH block through SelectExpr.

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();

6. Service vs DAO queries

6.1 Service

using static LiteOrm.Common.Expr;

var users1 = await userService.SearchAsync(u => u.Age >= 18);
var users2 = await userService.SearchAsync(Prop("Age") >= 18);

6.2 DAO

using static LiteOrm.Common.Expr;

var users1 = await userViewDAO.Search(u => u.Age >= 18).ToListAsync();
var users2 = await userViewDAO.Search(Prop("Age") >= 18).ToListAsync();
var users3 = await userViewDAO.Search($"WHERE {Prop("Age")} > {minAge}").ToListAsync();