LINQ first
Full IQueryable support. Joins, group by, subqueries, window functions and CTEs all translate to SQL.
Typed tables, async queries and a familiar EF-style API that translates your whole IQueryable to SQL. Built for MAUI, Avalonia and Native AOT.
dotnet add package SQLite.Framework
var options = new SQLiteOptionsBuilder("books.db")
.UseMinimumSqliteVersion(SQLiteMinimumVersion.V3_36)
.Build();
using SQLiteDatabase db = new(options);
var topAuthors = await (
from b in db.Table<Book>()
join a in db.Table<Author>() on b.AuthorId equals a.Id
where b.Price < 30
group b by a.Name into g
orderby g.Sum(b => b.Sales) descending
select new
{
Author = g.Key,
Titles = g.Count(),
Revenue = g.Sum(b => b.Sales),
}
).Take(5).ToListAsync();
Your query is read and turned into plain SQLite SQL. Pick a feature to see the SQL it generates.
var topAuthors = await (
from b in db.Table<Book>()
join a in db.Table<Author>()
on b.AuthorId equals a.Id
where b.Price < 30
group b by a.Name into g
orderby g.Sum(b => b.Sales) descending
select new
{
Author = g.Key,
Titles = g.Count(),
Revenue = g.Sum(b => b.Sales),
}
).Take(5).ToListAsync();
SELECT a0."AuthorName" AS "Author",
COUNT(*) AS "Titles",
SUM(b0."BookSales") AS "Revenue"
FROM "Books" AS b0
INNER JOIN "Authors" AS a0
ON b0."BookAuthorId" = a0."AuthorId"
WHERE b0."BookPrice" < @p0
GROUP BY a0."AuthorName"
ORDER BY SUM(b0."BookSales") DESC
LIMIT @p1;
Everything you expect from a modern ORM, sized for embedded SQLite.
Full IQueryable support. Joins, group by, subqueries, window functions and CTEs all translate to SQL.
Every operation has an async sibling. ToListAsync, FirstOrDefaultAsync, ExecuteUpdateAsync and more.
The framework never generates code at runtime. An optional source generator emits materializers at build time, so it ships clean under Native AOT.
First-class bindings for SQLite's full-text search and spatial modules. Type-safe, no raw SQL needed.
Query into JSON columns with LINQ. SQLite 3.45 JSONB is supported when your runtime has it.
ExecuteDelete and ExecuteUpdate let you change thousands of rows in one round trip.
How does a typed query work without emitting code at runtime?
A normal query against a typed table. The C# compiler turns it into an expression tree.
The framework walks that expression tree and writes plain SQLite SQL with bound parameters. No IL is emitted.
The SQL runs and SQLite hands back a row reader.
A small reader maps each column to a property and builds your object using Method.Invoke and PropertyInfo.SetValue. No Reflection.Emit, no runtime code generation.
Want the framework to be AOT safe? The optional source generator writes those materializers at build time, so there is no reflection on your hot path and the trimmer keeps every type. Read about the source generator
Each dot is one save of 100 rows. The gap before the next dot is how long that save took. The times come from real BenchmarkDotNet runs.
Where SQLite.Framework sits next to EF Core and sqlite-net-pcl. Based on the default setup of each library.
| Feature | SQLite.Framework | EF Core 10 | sqlite-net-pcl |
|---|---|---|---|
| LINQ IQueryable translation | Yes | Yes | No |
| Select projection to SQL | Yes | Yes | No |
| Async API | Yes | Yes | Yes |
| Native AOT ready | Yes | No | Partial |
| Bulk update and delete by predicate | Yes | Yes | No |
| Full text search (FTS5) | Yes | No | Partial |
| JSON columns | Yes | Yes | No |
| Window functions and CTEs | Yes | Partial | No |
| Encryption (SQLCipher) | Yes | No | Yes |
| Change tracking and migrations | Partial | Yes | No |
Pick the package that matches how SQLite is shipped in your app.
Uses the SQLite version that ships with the OS. The right default for most apps.
Ships its own SQLite binary. Use when the OS SQLite is too old or you want a pinned version.
Uses SQLCipher for encrypted databases. Call UseEncryptionKey on the options builder to enable.
No provider included. Bring your own SQLitePCLRaw bundle and stay in control.
Yes. The framework never generates code at runtime. Add the optional source generator and call UseGeneratedMaterializers. Queries run with no reflection on the materialization path, so the trimmer keeps every type. See Native AOT.
Yes. Create one SQLiteDatabase and share it across the app. Commands take a connection lock automatically. Turn on WAL mode so reads and writes do not block each other. See Multi-threading.
Yes. Use the SQLite.Framework.Cipher package, which ships SQLCipher. Call UseEncryptionKey on the options builder.
.NET 8, 9 and 10. It uses the SQLite that ships with your OS by default. You can set a minimum version on the builder or ship a pinned binary with the Bundled package.
It does not have a change tracker like EF Core. That is by design. It has lightweight stand-ins instead. Write hooks (OnAdd and OnUpdate) run before a save for audit and derived values. A versioned migration runner reconciles and versions the schema. There is no automatic change tracker, unit of work or lazy loading.
It translates full LINQ, including Select projections, to SQL like EF Core, but stays light and AOT friendly. Unlike sqlite-net-pcl it does not read whole rows into memory to run a projection. See Migrating from EF Core and Migrating from sqlite-net-pcl.
Pick the host you ship on and step through a guided setup. Console, MAUI, Avalonia, ASP.NET or Blazor.