Engine · SQL
SQL, live queries, and raw ADO.NET
SQL generation here is the stock EF Core SQLite provider, untouched. Bsqlite replaces
only the two services that assume synchronous I/O, and registers on every connection the same
ef_* functions, EF_DECIMAL collation, and regexp that EF
expects from a native SqliteConnection. Each panel below runs against this tab's
database and shows exactly what it ran.
Fastest persistent tier. Real files in the origin private file system. Concurrent connections, no COOP/COEP.
A query that re-runs itself
Live queries are table-level: Bsqlite reads the tables out of the generated
SQL, then re-executes the query when any of them is written - by this tab or, over
a BroadcastChannel, by another one. Include is honoured,
so the customer below comes along.
No orders yet.
_live = context.Orders
.Include(o => o.Customer)
.OrderByDescending(o => o.Id)
.AsLiveQuery();
_live.Changed += (_, rows) =>
{
_liveRows = rows;
_ = InvokeAsync(StateHasChanged);
};
_liveRows = await _live.RefreshAsync();Comparing decimals in SQL
decimal is not a SQLite affinity, so EF stores it as TEXT and compares
it with ef_compare under the EF_DECIMAL collation. Those
functions run on 96-bit coefficients inside the worker - not on JavaScript numbers,
which would quietly round.
var query = context.Customers
.AsNoTracking()
.Where(c => c.CreditLimit >= _minCredit)
.OrderBy(c => c.DisplayName);
_decimalSql = query.ToQueryString(); // the SQL below, straight from EF
_decimalRows = await query.ToListAsync();REGEXP, registered on every open
SQLite ships no regexp implementation; a host has to supply one. The
worker registers it on every connection, so the pattern is a JavaScript
RegExp - lookaround and backreferences included.
^a, or northwind|alpine, against the seeded emails.// regexp is registered on every open, so raw SQL can use it too.
_regexRows = await context.Customers
.FromSql($"SELECT * FROM \"Customers\" WHERE \"Email\" REGEXP {_pattern}")
.AsNoTracking()
.ToListAsync();No EF at all
The session exposes a DbConnection that is already open. Commands,
parameters, and readers behave as you expect, with one rule: the async overloads
only. The version below is read from wa-sqlite in the worker, not from a native
e_sqlite3.
// No EF: the session's connection is a DbConnection, already open.
await using var command = session.Connection.CreateCommand();
command.CommandText = "SELECT sqlite_version()";
var version = await command.ExecuteScalarAsync(cancellationToken);
// Parameters bind by CLR type; the binder picks TEXT / INTEGER / REAL / BLOB.
command.CommandText = "SELECT * FROM Products WHERE Price >= $min";
command.Parameters.Add(new BsqliteParameter { ParameterName = "$min", Value = 25.00m });
await using var reader = await command.ExecuteReaderAsync(cancellationToken);
while (await reader.ReadAsync(cancellationToken))
{
var sku = reader.GetString(reader.GetOrdinal("Sku"));
}