Skip to content

Data · CRUD

Catalog

Products and categories, read and written entirely on the client. SKU is unique, price is a decimal, lead time is a TimeSpan, and discontinued-on is a nullable DateOnly - the types a browser database is usually worst at. Every button below is a normal EF Core call; the snippet under each panel is the code it runs.

Client database OPFS

Fastest persistent tier. Real files in the origin private file system. Concurrent connections, no COOP/COEP.

Products

The whole catalog, read with one query that includes each product's category. Click a row to edit it; the editor and the categories sit under the table.

Try Edit a row and save, then reload the tab - the row is still there, because the write went to the storage backend named in the bar above.
Live
SKUNameCategoryPriceLeadStatusActions

Edit product

Select a row, or create one.

Categories

A category cascades to its products; a product on an order does not delete at all.

NameItems
csharp Loading this table
_categories = await context.Categories
    .AsNoTracking()
    .OrderBy(c => c.Name)
    .ToListAsync();

_products = await context.Products
    .AsNoTracking()
    .Include(p => p.Category)
    .OrderBy(p => p.Sku)
    .ToListAsync();

Inserting and updating

One code path for both: EF decides insert or update from the key. The write goes through the worker and lands in the storage backend named in the bar above, so it is still there after a reload.

csharp Saving the form
product.LeadTime = TimeSpan.FromDays(_leadDays);
if (!product.IsActive && product.DiscontinuedOn is null)
{
    product.DiscontinuedOn = DateOnly.FromDateTime(DateTime.UtcNow);
}

if (product.Id == 0)
{
    context.Products.Add(product);
}
else
{
    context.Products.Update(product);
}

// Async only. SaveChanges() throws BsqliteSynchronousApiNotSupportedException.
await context.SaveChangesAsync();
context.ChangeTracker.Clear();

Foreign keys are enforced, not simulated

The engine in the worker is real SQLite with foreign keys on. Nothing in the browser re-implements referential integrity, so a constraint failure comes back as the DbUpdateException you would get on a server.

Try Delete a product that appears on an order. The message above the table is SQLite's, surfaced through EF.
csharp Deleting a product
// OrderLine -> Product is DeleteBehavior.Restrict, so deleting a product that is on an
// order raises SQLITE_CONSTRAINT_FOREIGNKEY inside the worker and surfaces as a
// DbUpdateException here - foreign keys are enforced, not simulated.
var tracked = await context.Products.FirstAsync(p => p.Id == product.Id);
context.Products.Remove(tracked);
await context.SaveChangesAsync();

The model that shapes these tables

Ordinary IEntityTypeConfiguration. The same class compiles against the server's UseSqlite, and the same migration creates both databases - nothing here is browser-specific.

csharp ProductConfiguration.cs

CLR types stored in SQLite

SQLite has four storage classes. The ADO.NET binder decides which one each CLR value takes, so the worker never guesses and values round-trip as the type you wrote.

CLRSQLiteWhere in this demo
decimalTEXT + ef_*Price, credit limit
DateTimeTEXT ISOCreatedUtc
DateTimeOffsetTEXT + offsetOrderedAt
DateOnlyTEXT dateDate of birth, ship-by
TimeSpanTEXT cLead time
GuidTEXTPublicId
boolINTEGER 0/1IsActive, IsVip
enumTEXTOrder status
doubleREALWeight
csharp Product.cs
An unhandled error has occurred. Reload 🗙