LiteOrm’s ExprJsonConverter is easiest to understand when you think in two JSON shapes:
For learning, this order works well:
$, #, @, and $where| Marker | Meaning | Example |
|---|---|---|
! |
Negation (Not) | {"!": {"#": "IsActive"}} |
# |
Property reference | {"#": "Name"} or {"#": "u.Name"} |
$ |
Type/operator identifier | {"$": "table"} or {"$": "=="} |
$and |
Logical AND | {"$and": [...]} |
$or |
Logical OR | {"$or": [...]} |
@ |
Variable value | {"@": 42} or {"@": "hello"} |
| Expr Comparison Operator | JSON Short Form |
|---|---|
.Contains() |
contains |
.EndsWith() |
endswith |
.In() |
in |
.Like() |
like |
.NotContains() |
notcontains |
.NotEndsWith() |
notendswith |
.NotIn() |
notin |
.NotLike() |
notlike |
.RegexpLike() |
regexp |
.NotRegexpLike() |
notregexp |
.NotStartsWith() |
notstartswith |
.StartsWith() |
startswith |
< |
< |
<= |
<= |
<> |
!= |
= |
== |
> |
> |
>= |
>= |
This table is aligned with the current implementation. Older notes may still show outdated names.
| ExprType | Description | Short Marker | Normal Mode $ Value |
|---|---|---|---|
And |
Logic AND expression group | $and |
"and" |
Delete |
Delete segment, represents DELETE | $delete |
"delete" |
Foreign |
Foreign-key EXISTS expression | $foreign |
"foreign" |
From |
From segment, represents a data source | $from |
"from" |
Function |
Function call expression | {"$":"func","ActualFunctionName":[...]} |
"func" |
GenericSql |
SQL fragment generated via delegate or registration | - | "sql" |
GroupBy |
Group-by segment, represents GROUP BY | $group |
"group" |
Having |
Having segment, represents HAVING | $having |
"having" |
Lambda |
Lambda will be parsed into internal expressions | - | - |
LogicBinary |
Logic binary expression (comparisons) | "$":"==", "$":"!=", "$":">", "$":">=", "$":"<", "$":"<=", "$":"in"… |
"logic" |
Not |
Logic NOT expression | ! |
"not" |
Or |
Logic OR expression group | $or |
"or" |
OrderBy |
Order-by segment, represents ORDER BY | $orderby |
"orderby" |
OrderByItem |
ORDER BY item | - | "orderbyitem" |
Property |
Property (column) reference expression | # |
"prop" |
Section |
Pagination segment, represents LIMIT/OFFSET | $section |
"section" |
Select |
Select segment, represents a SELECT query | $select |
"select" |
SelectItem |
Select item, for SELECT column definition | - | "selectitem" |
Table |
Table segment, represents a single table or subquery | $table |
"table" |
TableJoin |
Table join segment, represents a JOIN clause | $join |
"join" |
Unary |
Unary expression (for example DISTINCT or -a) | - | "unary" |
Update |
Update segment, represents UPDATE | $update |
"update" |
Value |
Value expression | @ (variable) or direct value (const) |
"value" |
ValueBinary |
Value binary expression (arithmetic or concat) | "$":"+","$":"-","$":"*", "$":"/, "$":"%", "$":"||" |
"bin" |
ValueSet |
Value-set expression (for IN or CONCAT) | - | "set" |
Where |
Filter segment, represents WHERE | $where |
"where" |
Use the examples below with one simple rule in mind:
Short Format:
{"#": "Name"}
{"#": "u.Name"}
Normal Format:
{
"$": "property",
"PropertyName": "Name",
"TableAlias": null
}
Short Format - IsConst=true (Constant Value):
42
"hello"
Mapped non-primitive value types use a typed wrapper so the CLR type survives round-tripping:
{"$datetime": "2024-01-15T10:30:45Z"}
{"$datetimeoffset": "2024-01-15T10:30:45+08:00"}
{"$timespan": "01:00:00"}
{"$guid": "6f9619ff-8b86-d011-b42d-00c04fc964ff"}
{"$bytes": "AQID/w=="}
Short Format - IsConst=false (Variable Value):
{"@": 42}
{"@": "variableName"}
For variable values, the same typed wrapper is used inside @:
{"@": {"$guid": "6f9619ff-8b86-d011-b42d-00c04fc964ff"}}
{"@": {"$bytes": "AQID/w=="}}
Normal Format - IsConst=true (Constant Value):
{
"$": "value",
"Value": 42,
"IsConst": true
}
Normal Format - IsConst=false (Variable Value):
{
"$": "value",
"Value": 42,
"IsConst": false
}
Currently, typed wrappers are only used for these mapped runtime types:
DateTime -> $datetimeDateTimeOffset -> $datetimeoffsetTimeSpan -> $timespanGuid -> $guidbyte[] -> $bytesShort Format:
{
"$": "==",
"Left": {"#": "Age"},
"Right": {"@": 18}
}
Normal Format:
{
"$": "logic",
"Operator": 0,
"Left": {
"$": "property",
"PropertyName": "Age",
"TableAlias": null
},
"Right": {
"$": "value",
"Value": 18,
"IsConst": false
}
}
Short Format:
{
"$and": [
{"$": "==", "Left": {"#": "Status"}, "Right": {"@": "Pending"}},
{"$": ">=", "Left": {"#": "TotalAmount"}, "Right": {"@": 300}}
]
}
Normal Format:
{
"$": "and",
"Items": [
{
"$": "logic",
"Operator": 0,
"Left": {"$": "property", "PropertyName": "Status"},
"Right": {"$": "value", "Value": "Pending", "IsConst": false}
},
{
"$": "logic",
"Operator": 3,
"Left": {"$": "property", "PropertyName": "TotalAmount"},
"Right": {"$": "value", "Value": 300, "IsConst": false}
}
]
}
Short Format:
{
"!": {"$": "==", "Left": {"#": "IsActive"}, "Right": {"@": false}}
}
Normal Format:
{
"$": "not",
"Operand": {
"$": "logic",
"Operator": 0,
"Left": {"$": "property", "PropertyName": "IsActive"},
"Right": {"$": "value", "Value": false, "IsConst": false}
}
}
This is usually the hardest part to read because From -> Where -> OrderBy -> Section is nested layer by layer. Read the outer marker first, then inspect its Source and sibling properties.
Short Format - TableExpr:
{"$table": "LiteOrm.Tests.Models.TestUser"}
Short Format - TableExpr with Parameters:
{
"$table": "LiteOrm.Tests.Models.TestUser",
"TableArgs": ["2024", "01"],
"Alias": "u"
}
Normal Format - TableExpr:
{
"$": "table",
"Type": "LiteOrm.Tests.Models.TestUser",
"TableArgs": ["2024", "01"],
"Alias": "u"
}
Note: FromExpr uses $from in short format and directly contains TableExpr ($table) plus Joins.
Short Format - FromExpr:
{
"$from": {
"$table": "LiteOrm.Tests.Models.TestUser",
"TableArgs": ["2024", "01"],
"Alias": "u"
},
"Joins": []
}
Normal Format - FromExpr:
{
"$": "from",
"Source": {
"$": "table",
"Type": "LiteOrm.Tests.Models.TestUser",
"TableArgs": ["2024", "01"],
"Alias": "u"
},
"Joins": []
}
Short Format:
{
"$where": {"$from": {"$table": "LiteOrm.Tests.Models.TestUser"}},
"Where": {
"$and": [
{"$": "==", "Left": {"#": "Status"}, "Right": {"@": "Pending"}},
{"$": ">=", "Left": {"#": "TotalAmount"}, "Right": {"@": 300}}
]
}
}
Normal Format:
{
"$": "where",
"Source": {
"$": "from",
"Source": {
"$": "table",
"Type": "LiteOrm.Tests.Models.TestUser"
}
},
"Where": {
"$": "and",
"Items": [
{"$": "logic", "Operator": 0, "Left": {"#": "Status"}, "Right": {"@": "Pending"}},
{"$": "logic", "Operator": 3, "Left": {"#": "TotalAmount"}, "Right": {"@": 300}}
]
}
}
Short Format:
{
"$orderby": {
"$where": {"$from": {"$table": "LiteOrm.Tests.Models.TestUser"}}
},
"OrderBys": [
{"Field": {"#": "TotalAmount"}, "Asc": false},
{"Field": {"#": "CreatedTime"}, "Asc": false}
]
}
Normal Format:
{
"$": "orderby",
"Source": {...},
"OrderBys": [
{
"$": "orderbyitem",
"Field": {"$": "property", "PropertyName": "TotalAmount"},
"Asc": false
},
{
"$": "orderbyitem",
"Field": {"$": "property", "PropertyName": "CreatedTime"},
"Asc": false
}
]
}
Short Format:
{
"$section": {
"$orderby": {...},
"OrderBys": [
{"Field": {"#": "CreatedTime"}, "Asc": false}
]
},
"Skip": 0,
"Take": 10
}
{
"$section": {
"$orderby": {
"$where": {"$from": {"$table": "LiteOrm.Tests.Models.TestUser"}},
"Where": {
"$and": [
{"$": "==", "Left": {"#": "Status"}, "Right": {"@": "Pending"}},
{"$": ">=", "Left": {"#": "TotalAmount"}, "Right": {"@": 300}},
{"$": "contains", "Left": {"#": "DepartmentName"}, "Right": {"@": "Operations"}}
]
},
"OrderBys": [
{"Field": {"#": "TotalAmount"}, "Asc": false},
{"Field": {"#": "CreatedTime"}, "Asc": false}
]
}
},
"Skip": 0,
"Take": 5
}
{
"$": "section",
"Source": {
"$": "orderby",
"Source": {
"$": "where",
"Source": {
"$": "from",
"Source": {
"$": "table",
"Type": "LiteOrm.Tests.Models.Order"
}
},
"Where": {
"$": "and",
"Items": [
{
"$": "logic",
"Operator": 0,
"Left": {"$": "property", "PropertyName": "Status"},
"Right": {"$": "value", "Value": "Pending", "IsConst": false}
},
{
"$": "logic",
"Operator": 3,
"Left": {"$": "property", "PropertyName": "TotalAmount"},
"Right": {"$": "value", "Value": 300, "IsConst": false}
},
{
"$": "logic",
"Operator": 11,
"Left": {"$": "property", "PropertyName": "DepartmentName"},
"Right": {"$": "value", "Value": "Operations", "IsConst": false}
}
]
}
},
"OrderBys": [
{
"$": "orderbyitem",
"Field": {"$": "property", "PropertyName": "TotalAmount"},
"Asc": false
},
{
"$": "orderbyitem",
"Field": {"$": "property", "PropertyName": "CreatedTime"},
"Asc": false
}
]
},
"Skip": 0,
"Take": 5
}
Note:
FunctionExprFunctionExpr is easy to document incorrectly because its short form is not $fn. The current implementation uses the func marker, then stores the function name as the property name.
{
"$": "func",
"DateDiffDays": [
{"#": "EndTime"},
{"#": "StartTime"}
]
}
{
"$": "func",
"FunctionName": "DateDiffDays",
"Args": [
{"$": "property", "PropertyName": "EndTime"},
{"$": "property", "PropertyName": "StartTime"}
]
}
FunctionName and parameters live in Args