This is not just an advanced trick. In LiteOrm, it is a practical day-to-day pattern: use Lambda to keep business intent readable, and use Expr where the filter needs to be built dynamically.
The two main patterns are:
expr.To<bool>(): keep Lambda on the outside and embed a dynamic Expr.Expr.Lambda<T>(): write stable conditions as Lambda first, then convert them into LogicExpr and keep composing.| Scenario | Recommended pattern | Why |
|---|---|---|
| Most conditions are fixed, only a few are runtime-driven | Lambda + expr.To<bool>() |
Keeps the query readable and strongly typed |
| A stable base condition must be reused with optional filters | Expr.Lambda<T>() |
Turns Lambda into a composable LogicExpr |
| Nearly everything is dynamic | Pure Expr |
Simpler than wrapping everything back into Lambda |
To<T>() is the bridge between Lambda and Expr. It only works during LiteOrm expression parsing, so use it only inside the Lambda passed to a LiteOrm query API.
using static LiteOrm.Common.Expr;
var users = await userService.SearchAsync(
u => u.Age >= 18 && Prop("UserName").Contains("John").To<bool>()
);
This style works well for “stable main condition + dynamic extra filter” queries.
using static LiteOrm.Common.Expr;
LogicExpr filter = null;
if (!string.IsNullOrWhiteSpace(keyword))
filter &= Prop("UserName").Contains(keyword);
if (minAge.HasValue)
filter &= Prop("Age") >= minAge.Value;
var users = await userService.SearchAsync(
u => u.IsActive == true && filter.To<bool>()
);
You keep the dynamic part reusable while still letting the final query read like business logic.
EXISTSusing static LiteOrm.Common.Expr;
var hasOpenOrder = ExistsRelated<Order>(
Prop("Status") != "Completed"
);
var activeUsers = await userService.SearchAsync(
u => u.IsActive == true && hasOpenOrder.To<bool>()
);
If relationships are already declared in the model, combining ExistsRelated(...) with Lambda is usually clearer than hand-writing the whole subquery.
When the base business rule is naturally expressed as Lambda but still needs optional runtime filters, convert it first with Expr.Lambda<T>().
using static LiteOrm.Common.Expr;
var baseCondition = Lambda<User>(u => u.IsActive == true && u.Age >= 18);
LogicExpr extraFilter = null;
if (!string.IsNullOrWhiteSpace(keyword))
extraFilter &= Prop("UserName").Contains(keyword);
if (deptId.HasValue)
extraFilter &= Prop("DeptId") == deptId.Value;
var combined = baseCondition & extraFilter;
var users = await userService.SearchAsync(
u => combined.To<bool>()
);
This is a good fit for “stable business baseline + optional filter set” query builders.
To<T>() should match the Lambda return typeIn query predicates that normally means bool:
using static LiteOrm.Common.Expr;
u => u.Age >= 18 && To<bool>()
To<T>() in normal runtime codeIt is not a general conversion API. It exists for LiteOrm’s Lambda parser.
To<T>() is replaced during parsing and still becomes part of the same SQL expression tree.
| Style | Strength | Best for |
|---|---|---|
| Pure Lambda | Most readable, best IDE experience | Fixed-condition queries |
Pure Expr |
Most flexible for runtime composition | Query builders and admin-style filters |
Lambda + Expr |
Keeps intent clear while staying dynamic | Stable business rules with optional filters |