-
Notifications
You must be signed in to change notification settings - Fork 2
Expressions
LINQ expressions inside Where, Select and other methods are translated directly to SQL. The framework stays close to the shape of the LINQ query and does not rewrite the query just to make a method work.
The standard arithmetic operators all work:
var results = await db.Table<Book>()
.Where(b => (b.Price * 1.1) + 5 > 20)
.ToListAsync();
var discounted = await db.Table<Book>()
.Select(b => new { b.Title, Sale = b.Price * 0.9 })
.ToListAsync();
var evens = await db.Table<Book>()
.Where(b => b.Id % 2 == 0)
.ToListAsync();Supported operators are +, -, *, / and %.
A few arithmetic and comparison cases have SQLite-specific behavior, such as division by zero, floating-point domain errors and order comparisons on a nullable column. See Limitations.
| C# | SQL |
|---|---|
Math.Abs(x) |
ABS(x) |
Math.Round(x) |
round half to even (banker's), the .NET default, via a CASE over ROUND
|
Math.Round(x, digits) |
round half to even (banker's), the .NET default, via a CASE over ROUND
|
Math.Round(x, MidpointRounding.AwayFromZero) |
ROUND(x) |
Math.Round(x, MidpointRounding.ToEven) |
round half to even (banker's), via a CASE over ROUND
|
Math.Round(x, digits, MidpointRounding.AwayFromZero) |
ROUND(x, digits) |
Math.Round(x, digits, MidpointRounding.ToEven) |
round half to even (banker's), via a CASE over ROUND
|
Math.Floor(x) |
FLOOR(x) |
Math.Ceiling(x) |
CEIL(x) |
Math.Truncate(x) |
TRUNC(x) |
Math.Pow(x, y) |
POWER(CAST(x AS REAL), y) |
Math.Sqrt(x) |
SQRT(x) |
Math.Cbrt(x) |
CASE WHEN x >= 0 THEN POWER(x, 1.0/3.0) ELSE -POWER(-x, 1.0/3.0) END |
Math.Exp(x) |
EXP(x) |
Math.Log(x) |
LN(x) |
Math.Log(x, base) |
LOG(base, x) |
Math.Log10(x) |
LOG10(x) |
Math.Log2(x) |
LOG2(x) |
Math.Sign(x) |
CASE WHEN x > 0 THEN 1 WHEN x < 0 THEN -1 ELSE 0 END |
Math.Max(x, y) |
MAX(x, y) |
Math.Min(x, y) |
MIN(x, y) |
Math.Clamp(x, min, max) |
MAX(min, MIN(x, max)) |
Math.Sin(x) |
SIN(x) |
Math.Cos(x) |
COS(x) |
Math.Tan(x) |
TAN(x) |
Math.Asin(x) |
ASIN(x) |
Math.Acos(x) |
ACOS(x) |
Math.Atan(x) |
ATAN(x) |
Math.Atan2(y, x) |
ATAN2(y, x) |
Math.Sinh(x) |
SINH(x) |
Math.Cosh(x) |
COSH(x) |
Math.Tanh(x) |
TANH(x) |
Math.Asinh(x) |
ASINH(x) |
Math.Acosh(x) |
ACOSH(x) |
Math.Atanh(x) |
ATANH(x) |
var results = await db.Table<Book>()
.Where(b => Math.Abs(b.Price - 10) < 1)
.ToListAsync();
var rounded = await db.Table<Book>()
.Select(b => new { b.Title, Price = Math.Round(b.Price, 2) })
.ToListAsync();| C# | SQL |
|---|---|
s.Length |
LENGTH(s) |
s.ToUpper() / s.ToUpperInvariant()
|
UPPER(s) |
s.ToLower() / s.ToLowerInvariant()
|
LOWER(s) |
s.Trim() |
TRIM(s, <whitespace>) |
s.TrimStart() |
LTRIM(s, <whitespace>) |
s.TrimEnd() |
RTRIM(s, <whitespace>) |
s.Contains(value) |
s LIKE '%value%' ESCAPE '\' |
s.StartsWith(value) |
s LIKE 'value%' ESCAPE '\' |
s.EndsWith(value) |
s LIKE '%value' ESCAPE '\' |
s.Equals(value) |
s IS value |
s.Replace(old, new) |
REPLACE(s, old, new) |
s.Substring(start, length) |
SUBSTR(s, start + 1, length) |
s[index] |
SUBSTR(s, index + 1, 1) |
s.IndexOf(value) |
INSTR(s, value) - 1 |
s.IndexOf(value, startIndex) |
INSTR(SUBSTR(s, startIndex + 1), value) adjusted back to a 0-based absolute index or -1
|
s.LastIndexOf(value) |
CASE WHEN LENGTH(value) = 0 THEN LENGTH(s) ELSE COALESCE((WITH RECURSIVE find_pos(pos, rem) AS (SELECT 0, s UNION ALL SELECT pos + INSTR(rem, value), SUBSTR(rem, INSTR(rem, value) + 1) FROM find_pos WHERE INSTR(rem, value) > 0) SELECT MAX(pos) - 1 FROM find_pos WHERE pos > 0), -1) END |
s.LastIndexOf(value, startIndex) |
the same LastIndexOf search run over the prefix SUBSTR(s, 1, startIndex + 1), so the match must fall within the first startIndex + 1 characters |
s.Insert(index, value) |
SUBSTR(s, 1, index) || value || SUBSTR(s, index + 1) |
s.Remove(start) |
SUBSTR(s, 1, start) |
s.Remove(start, count) |
SUBSTR(s, 1, start) || SUBSTR(s, start + count + 1) |
s.PadLeft(n) |
CASE WHEN LENGTH(s) >= n THEN s ELSE (SELECT SUBSTR(REPLACE(HEX(ZEROBLOB(n - LENGTH(s))), '00', ' '), 1, n - LENGTH(s)) || s) END |
s.PadLeft(n, c) |
CASE WHEN LENGTH(s) >= n THEN s ELSE (SELECT SUBSTR(REPLACE(HEX(ZEROBLOB(n - LENGTH(s))), '00', c), 1, n - LENGTH(s)) || s) END |
s.PadRight(n) |
CASE WHEN LENGTH(s) >= n THEN s ELSE (s || (SELECT SUBSTR(REPLACE(HEX(ZEROBLOB(n - LENGTH(s))), '00', ' '), 1, n - LENGTH(s)))) END |
s.PadRight(n, c) |
CASE WHEN LENGTH(s) >= n THEN s ELSE (s || (SELECT SUBSTR(REPLACE(HEX(ZEROBLOB(n - LENGTH(s))), '00', c), 1, n - LENGTH(s)))) END |
s + other |
s || other |
string.Concat(a, b, ...) |
a || b || ... |
string.Join(sep, values) |
val1 || sep || val2 || ... |
string.Compare(a, b) |
CASE WHEN a = b THEN 0 WHEN a < b THEN -1 ELSE 1 END |
string.Compare(a, indexA, b, indexB, length) |
the same Compare, run over SUBSTR(a, indexA + 1, length) and SUBSTR(b, indexB + 1, length)
|
s.CompareTo(other) |
CASE WHEN s = other THEN 0 WHEN s < other THEN -1 ELSE 1 END |
string.IsNullOrEmpty(s) |
(COALESCE(s, '') = '') |
string.IsNullOrWhiteSpace(s) |
(TRIM(COALESCE(s, ''), CHAR(9, 10, 11, 12, 13, 32, 133, 160, 5760, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8232, 8233, 8239, 8287, 12288)) = '') |
In +, string.Concat and string.Join, a nullable string column is wrapped in COALESCE(col, ''), so a NULL value becomes an empty string. This matches .NET, where string.Concat and string.Join treat a null argument as empty.
String length, comparison, ordering and Substring bounds have SQLite-specific behavior. See Limitations.
Contains, StartsWith and EndsWith use LIKE, which is case-insensitive for ASCII by default. To make them case-sensitive, build the database with UseCaseSensitiveStringComparison(). They then translate to INSTR / SUBSTR instead of LIKE. See Storage Options.
Pass StringComparison.OrdinalIgnoreCase to Contains, StartsWith or EndsWith to force a case-insensitive match regardless of that option:
var results = await db.Table<Book>()
.Where(b => b.Title.Contains("test", StringComparison.OrdinalIgnoreCase))
.ToListAsync();The ?? operator translates to COALESCE:
var results = await db.Table<Book>()
.Select(b => new { b.Id, Notes = b.Notes ?? "No notes" })
.ToListAsync();| C# | SQL |
|---|---|
char.ToLower(c) / char.ToLowerInvariant(c)
|
LOWER(c) |
char.ToUpper(c) / char.ToUpperInvariant(c)
|
UPPER(c) |
char.IsWhiteSpace(c) |
TRIM(c, CHAR(9, 10, 11, 12, 13, 32, 133, 160, 5760, 8192, 8193, 8194, 8195, 8196, 8197, 8198, 8199, 8200, 8201, 8202, 8232, 8233, 8239, 8287, 12288)) = '' |
char.IsAsciiDigit(c) |
c BETWEEN '0' AND '9' |
char.IsAsciiLetter(c) |
LOWER(c) BETWEEN 'a' AND 'z' |
char.IsAsciiLetterOrDigit(c) |
(c BETWEEN '0' AND '9') OR (LOWER(c) BETWEEN 'a' AND 'z') |
char.IsAsciiLetterLower(c) |
c BETWEEN 'a' AND 'z' |
char.IsAsciiLetterUpper(c) |
c BETWEEN 'A' AND 'Z' |
You can read individual components of a DateTime column directly:
var recent = await db.Table<Order>()
.Where(o => o.PlacedAt.Year == 2024 && o.PlacedAt.Month == 12)
.ToListAsync();
var years = await db.Table<Order>()
.Select(o => o.PlacedAt.Year)
.ToListAsync();Supported properties are Year, Month, Day, Hour, Minute, Second, Millisecond, Ticks, DayOfWeek, DayOfYear, Date and TimeOfDay.
var shifted = await db.Table<Order>()
.Select(o => new { o.Id, Due = o.PlacedAt.AddDays(30) })
.ToListAsync();Supported methods are Add, AddYears, AddMonths, AddDays, AddHours, AddMinutes, AddSeconds, AddMilliseconds, AddMicroseconds and AddTicks. The static DateTime.IsLeapYear(year) method also works with a constant, captured value or translated integer expression.
You can read individual components of a DateOnly column the same way as DateTime:
var recent = await db.Table<Order>()
.Where(o => o.Date.Year == 2024 && o.Date.Month == 12)
.ToListAsync();Supported properties are Year, Month, Day, DayOfWeek and DayOfYear.
var shifted = await db.Table<Order>()
.Select(o => new { o.Id, Due = o.Date.AddDays(30) })
.ToListAsync();Supported methods are AddYears, AddMonths and AddDays.
Supported properties are Year, Month, Day, Hour, Minute, Second, Millisecond, Ticks, DayOfWeek, DayOfYear, Date and TimeOfDay.
Supported methods are Add, AddYears, AddMonths, AddDays, AddHours, AddMinutes, AddSeconds, AddMilliseconds, AddMicroseconds and AddTicks.
Supported properties are Hour, Minute, Second and Ticks.
Supported methods are Add, AddHours and AddMinutes.
Supported properties are Days, TotalDays, Hours, TotalHours, Minutes, TotalMinutes, Seconds, TotalSeconds, Milliseconds, TotalMilliseconds and Ticks.
var results = await db.Table<Order>()
.Where(o => o.Duration.Subtract(TimeSpan.FromHours(1)).TotalHours > 5)
.ToListAsync();Supported methods are Add, Subtract, Negate and Duration.
You can also call the static TimeSpan creation methods inside an expression:
var results = await db.Table<Order>()
.Where(o => o.Duration == TimeSpan.FromHours(o.Id))
.ToListAsync();Supported static methods are FromDays, FromHours, FromMinutes, FromSeconds, FromMilliseconds, FromMicroseconds and FromTicks.
Guid columns support equality comparisons:
Guid id = Guid.NewGuid();
var result = await db.Table<Order>()
.Where(o => o.TrackingId == id)
.FirstOrDefaultAsync();A byte[] column supports Length, value equality through == or SequenceEqual, and Contains of a single byte. Reading an individual byte by index is not supported.
var matches = await db.Table<FileRecord>()
.Where(f => f.Content.Contains((byte)0x7F))
.ToListAsync();