Introduction
Getting started
One package, one registration and a context factory - then EF Core runs against a SQLite database in the user's browser tab, on the storage backend your preference order picks. Every page after this one runs against that database, in this tab.
Install
Bit.Bsqlite is the whole install: the ADO.NET surface, the EF Core integration, the worker
transport and every storage backend - OPFS, IndexedDB, Cache Storage and in-memory. It targets .NET 8, 9 and
10, each with the EF Core of the same major version. Browser-free unit tests add
Bit.Bsqlite.Testing to the test project.
dotnet add package Bit.BsqliteRegister the services
Register the backends you ship, the order you prefer them in, and the database. The first available backend
wins, but existing data outranks preference: a database created on IndexedDB stays there once OPFS becomes
available. AllowNonPersistentFallback() is what makes in-memory acceptable at all.
using Bit.Bsqlite.Storage.IndexedDb;
using Bit.Bsqlite.Storage.InMemory;
using Bit.Bsqlite.Storage.Opfs;
builder.Services.AddBitBsqliteServices(sqlite => sqlite
.AddStorageProvider<BsqliteOpfsStorageProvider>()
.AddStorageProvider<BsqliteIndexedDbStorageProvider>()
.AddStorageProvider<BsqliteInMemoryStorageProvider>()
.UseStorage(s => s
.Prefer(BsqliteOpfsStorageProvider.ProviderName)
.Fallback(BsqliteIndexedDbStorageProvider.ProviderName)
.Fallback(BsqliteInMemoryStorageProvider.ProviderName)
.AllowNonPersistentFallback())
.AddDatabase("app.db"));
builder.Services.AddBitBsqliteDbContext<AppDbContext>();AddDemoServices
extension method that both Program.cs files call.
Open a context and query
Inject the context factory wherever you would have opened a DbContext. The first context opens
the database and runs the migrations once per session; later ones reuse the session's connection. From there
it is ordinary EF Core - with the async API throughout, because the engine runs in a worker the UI thread
cannot wait on.
@page "/products"
@using Bit.Bsqlite.EntityFrameworkCore
@using Microsoft.EntityFrameworkCore
@inject IBsqliteDbContextFactory<AppDbContext> Contexts
@code {
private List<Product> _products = [];
protected override async Task OnInitializedAsync()
{
// The first context opens the database and applies the migrations; later ones reuse the session.
await using var db = await Contexts.CreateDbContextAsync();
_products = await db.Products.OrderBy(p => p.Name).ToListAsync();
}
private async Task AddAsync()
{
await using var db = await Contexts.CreateDbContextAsync();
db.Products.Add(new Product { Name = "Widget", Price = 19.99m });
await db.SaveChangesAsync();
}
}SaveChanges(), ToList(), DbConnection.Open() -
throw BsqliteSynchronousApiNotSupportedException naming the async overload. The
Limits page runs that on purpose.
Prerendering and render modes
A prerendered page runs once on the server, where there is no browser to open a database in. Open it only once
the page is interactive - RendererInfo.IsInteractive on Interactive WebAssembly, and
OperatingSystem.IsBrowser() on Interactive Auto, where a page on the server circuit is
interactive too. This site renders <Routes> with InteractiveWebAssembly and lets its
SessionLifecycle component do the check for every page.
protected override async Task OnInitializedAsync()
{
// Prerendering runs this on the server first, where there is no browser to open a database in.
// It runs again once the page is interactive on WebAssembly.
if (RendererInfo.IsInteractive is false) return;
await using var db = await Contexts.CreateDbContextAsync();
_orders = await db.Orders.Where(o => o.Status == OrderStatus.Draft).ToListAsync();
}Interactive Server is not supported: the engine runs in the user's tab, and EF Core on the server would need a native SQLite of its own.
Nothing in the host page
The engine (.wasm), the worker and the backends' VFSes are static web assets under
_content/Bit.Bsqlite/, each imported on demand: registering a backend costs nothing until it is
selected, and a session downloads one engine build and the VFS of the backend it selected. There is no script
tag or stylesheet to add.