Keep your Microsoft.Data.Sqlite or EF Core code and drop the native e_sqlite3 binary. RecursiveAI.OneLite.SQLitePCLRaw is, in its docs’ words, “An ISQLite3Provider for SQLitePCLRaw that runs on OneLite’s engine. Microsoft.Data.Sqlite and Entity Framework Core’s SQLite provider use SQLitePCLRaw for every database call, so they run on OneLite, with no native SQLite loaded, by swapping one package and one line of start-up code.”
dotnet new console -o MdsNoNative && cd MdsNoNative dotnet add package Microsoft.Data.Sqlite.Core --version 10.0.14 dotnet add package RecursiveAI.OneLite.SQLitePCLRaw --version 1.0.0
Reference Microsoft.Data.Sqlite.Core, not Microsoft.Data.Sqlite: in the docs’ words, its “default bundle brings the native library”. The packages target net10.0, so you need the .NET 10 SDK. Put this in Program.cs and run dotnet run:
using Microsoft.Data.Sqlite; using RecursiveAI.OneLite.SQLitePCLRaw; // Instead of SQLitePCL.Batteries_V2.Init() and the native e_sqlite3 bundle. OneLiteBatteries.Init(); using var conn = new SqliteConnection("Data Source=shop.db"); conn.Open(); using (var create = conn.CreateCommand()) { create.CommandText = """ drop table if exists products; create table products (id integer primary key, name text not null, price real not null); """; create.ExecuteNonQuery(); } using (var tx = conn.BeginTransaction()) { using var insert = conn.CreateCommand(); insert.CommandText = "insert into products (name, price) values ($name, $price)"; var name = insert.Parameters.Add("$name", SqliteType.Text); var price = insert.Parameters.Add("$price", SqliteType.Real); foreach (var (n, p) in new[] { ("Kettle", 24.5), ("Teapot", 18.0), ("Mug", 6.25) }) { name.Value = n; price.Value = p; insert.ExecuteNonQuery(); } tx.Commit(); } using var query = conn.CreateCommand(); query.CommandText = "select name, price from products where price < $max order by price"; query.Parameters.AddWithValue("$max", 20); using (var reader = query.ExecuteReader()) while (reader.Read()) Console.WriteLine($"{reader.GetString(0)}: {reader.GetDouble(1):0.00}"); using var fk = conn.CreateCommand(); fk.CommandText = "pragma foreign_keys"; Console.WriteLine($"foreign_keys: {fk.ExecuteScalar()}"); Console.WriteLine($"provider: {SQLitePCL.raw.GetNativeLibraryName()}"); Console.WriteLine($"sqlite version: {conn.ServerVersion}");
Mug: 6.25 Teapot: 18.00 foreign_keys: 1 provider: OneLite (managed) sqlite version: 3.53.4
Everything except OneLiteBatteries.Init() is ordinary Microsoft.Data.Sqlite code: $name parameters, a transaction, a reader. Init() “calls SQLitePCL.raw.SetProvider with a shared provider; it is safe to call more than once”. The provider reports itself as OneLite (managed) and the version as 3.53.4, “the SQLite release OneLite is compatible with”. Foreign keys are on, as with the native bundle (see Limits).
Nothing native in the output
dotnet publish -c Release -r linux-x64 --self-contained false -o outfind out -type f -printf '%f\n' | sortMdsNoNative MdsNoNative.deps.json MdsNoNative.dll MdsNoNative.pdb MdsNoNative.runtimeconfig.json Microsoft.Data.Sqlite.dll RecursiveAI.OneLite.dll RecursiveAI.OneLite.SQLitePCLRaw.dll SQLitePCLRaw.core.dll
No libe_sqlite3.so, and no runtimes/ folder of per-platform binaries. The package “is NativeAOT- and trim-clean”; this publishes one self-contained executable:
dotnet publish -c Release -r linux-x64 -p:PublishAot=true -o aot./aot/MdsNoNativeMug: 6.25 Teapot: 18.00 foreign_keys: 1 provider: OneLite (managed) sqlite version: 3.53.4
The file is an ordinary SQLite database. The sqlite3 shell (3.53.4, from sqlite.org) reads it and checks it:
sqlite3 shop.db "select * from products" "pragma integrity_check"1|Kettle|24.5 2|Teapot|18.0 3|Mug|6.25 ok
With EF Core
The same swap works for EF Core: reference Microsoft.EntityFrameworkCore.Sqlite.Core (not Microsoft.EntityFrameworkCore.Sqlite, which brings the native bundle) and call OneLiteBatteries.Init() before the first DbContext.
cd .. && dotnet new console -o EfNoNative && cd EfNoNative dotnet add package Microsoft.EntityFrameworkCore.Sqlite.Core --version 10.0.14 dotnet add package RecursiveAI.OneLite.SQLitePCLRaw --version 1.0.0
using Microsoft.EntityFrameworkCore; using RecursiveAI.OneLite.SQLitePCLRaw; OneLiteBatteries.Init(); // before the first DbContext using (var db = new BlogContext()) { db.Database.EnsureDeleted(); db.Database.EnsureCreated(); db.Blogs.Add(new Blog { Name = "Field notes", Posts = { new Post { Title = "Hello" }, new Post { Title = "Second post" } }, }); db.SaveChanges(); } using (var db = new BlogContext()) { foreach (var blog in db.Blogs.Include(b => b.Posts).OrderBy(b => b.Name)) Console.WriteLine($"{blog.Name}: {string.Join(", ", blog.Posts.Select(p => p.Title))}"); Console.WriteLine($"posts titled like 'post': {db.Posts.Count(p => p.Title.Contains("post"))}"); } public class BlogContext : DbContext { public DbSet<Blog> Blogs => Set<Blog>(); public DbSet<Post> Posts => Set<Post>(); protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite("Data Source=blog.db"); } public class Blog { public int Id { get; set; } public string Name { get; set; } = ""; public List<Post> Posts { get; } = new(); } public class Post { public int Id { get; set; } public string Title { get; set; } = ""; public int BlogId { get; set; } public Blog Blog { get; set; } = null!; }
Field notes: Hello, Second post posts titled like 'post': 1
sqlite3 blog.db ".tables" "select * from Posts" "pragma integrity_check"Blogs Posts 1|Hello|1 2|Second post|1 ok
How well it is tested
From the changelog at 1.0.0, the project’s own runs of the two suites: “Microsoft.Data.Sqlite’s own tests pass 670 of 687 (7 skipped by the suite, 10 failures classified in docs/adapter.md) and EF Core’s SQLite functional tests 37,121 of 37,418 (289 skipped by the suites; the 8 failures are classified in docs/adapter.md).” The adapter docs give the versions (“Microsoft.Data.Sqlite 10.0.14’s and EF Core 10.0.14’s tests, with SQLitePCLRaw 3.0.5”) and classify every failure: load_extension (4), shared-cache table locks (2, plus 6 in EF Core), Password= (2: “the tests expect the native library to reject Password=; OneLite supports SQLCipher-compatible encryption, so it accepts it”), EXPLAIN (1), pragma_function_list (1), one EF test that “checks the fetched subset of the test tree against the full one”, and one EF test that “asserts a wrong answer that the native library gives”.
Limits
From the adapter docs, where “Each of these returns a documented result, never a silent success”:
- Foreign keys on by default. “The adapter starts every connection with
PRAGMA foreign_keys=ON”, as the native bundle does; OneLite’s own API and ADO.NET provider keep SQLite’s default, off. “SetOneLiteBatteries.ForeignKeysByDefault = falsebefore opening connections to get off through the adapter; an explicitPRAGMA foreign_keysorForeign Keys=connection-string key always wins.” - No native extensions.
load_extensionfails: “load_extension is not supported by OneLite; register managed extensions through the engine API”. - No
EXPLAINbytecode. “Bytecode listings are not produced (unsupported: EXPLAIN);EXPLAIN QUERY PLANtext differs.” - Not implemented:
set_authorizer(“A non-null authorizer fails withSQLITE_ERROR”),serialize/deserialize, snapshots anddb_config(SQLITE_ERROR),PRAGMA function_list, and user functions on a UTF-16 database.traceandprofileare “Accepted and never called”. - Shared cache. “a reader sees an uncommitted write instead of
SQLITE_LOCKED” on a shared in-memory database. - Text. “Invalid UTF-8 in a bound text value is replaced, not preserved byte for byte (bind a
BLOBfor arbitrary bytes).” - The engine’s own differences apply: every known difference from SQLite 3.53.4 is on the compatibility page.
Starting a new project? OneLite’s own API needs no SQLitePCLRaw at all: SQLite in C# without native dependencies.