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.
| 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 |
Expr when conditions must be accumulated dynamically: admin filters, frontend query builders, reusable cross-layer filters.ExprString when the DAO layer needs handwritten SQL: it can represent either a Search condition fragment or a full SQL statement, but Service APIs do not expose this entry point.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));
var page = await userService.SearchAsync(
q => q.Where(u => u.Age >= 18)
.OrderByDescending(u => u.CreateTime)
.Skip(0)
.Take(20)
);
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.
Exists and ExistsRelatedExistsLambda 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.
ExistsRelatedLambda 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.
Expr query entry pointusing 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.
ExprString Interpolated StringsExprString 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.
using static LiteOrm.Common.Expr;
var condition = Prop("Age") >= 18;
var users = await userViewDAO.Search(
$"WHERE {condition} ORDER BY CreateTime DESC"
).ToListAsync();
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:
Search condition fragments, or carry full SQL together with isFull: trueExpr, build the Expr first and then interpolate it into ExprString rather than hardcoding the condition in the stringExprString is parsed in the insertion order of the embedded Expr objects, so parameter generation order and context behavior can differ from full Expr parsing. For example, inside ExprString, SelectExpr is resolved before FromExpr; if the SelectExpr contains columns without an explicit table alias, they may not bind to the default table correctly. The main query already works around this by creating the default main-table context early, but subqueries still require extra care.[ and ] as provider-agnostic quote placeholders; LiteOrm rewrites them to the real identifier quotes of the current database dialect before executionWhen 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();
using static LiteOrm.Common.Expr;
var users1 = await userService.SearchAsync(u => u.Age >= 18);
var users2 = await userService.SearchAsync(Prop("Age") >= 18);
Expr, which fit business-facing query code, transactions, and AOP-backed service encapsulation.ExprString query overload; once the need becomes “handwritten SQL”, switch to 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();
Expr, and also adds ExprString, so it is the right layer for custom SQL fragments, full SQL, projections, and DataTable-oriented queries.SearchAs(...), Query(...), Execute(...), or GetValue(...), go directly through DAO.