/SqliteWasmHelper

Persistent SQLite in Blazor WebAssembly apps with EF Core 6.0 and your browser's cache.

Primary LanguageC#MIT LicenseMIT

SqliteWasmHelper

.NET Builds .NET Tests Generate and Publish Documentation

Download this package from Nuget.

SqliteWasmHelper is a package designed to make it easy to work with SQLite databases in Blazor Wasm apps. Although you could install Eric Sink's SQLitePCLRaw.bundle_e_sqlite3 package directly, that will only provide an in-memory implementation. This package automatically injects the code needed to persist your database in cache with the help of EF Core.

WARNING The browser cache is both easily accessible by the end user and can be flushed any time. Do not use SQLite in the browser to store sensitive data. Do not use it to store user-entered data unless the data is temporary in nature or you have a process to synchronize data to the back end.

Links

Quick start

Let's get right to the point! Boring text follows, a more exciting video with questionable audio quality can be viewed here:

📽️ How to use SQLiteWasmHelper to add EF Core 6.0 and SQLite to your Blazor WebAssembly projects

Prerequisites

For the Wasm client to get properly linked, you must have the WebAssembly Tools workload installed.

Instlallation and use

  1. Install the lastest SQlite in WebAssembly helper NuGet package or reference the SqliteWasmHelper project. This automatically installs all necessary dependencies:
    1. SqliteWasmHelper
    2. Entity Framework Core and the SQLite provider
    3. The SQLitePCLRaw.bundle for running SQLite in WebAssembly
  2. Add the following to the .csproj file for your Blazor WebAssembly project (it can be added to an existing PropertyGroup):
    <PropertyGroup>
        <WasmBuildNative>true</WasmBuildNative>
    </PropertyGroup>
  3. Add using SqliteWasmHelper; to the top of the Program.cs file in your Blazor WebAssembly project
  4. Use the extension method to add a special DbContext factory:
      builder.Services.AddSqliteWasmDbContextFactory<ThingContext>(
        opts => opts.UseSqlite("Data Source=things.sqlite3"));
  5. Inject the factory into the components that need it
    @inject ISqliteWasmDbContextFactory<ThingContext> Factory
  6. Use the DbContext as you normally would
    using var ctx = await Factory.CreateDbContextAsync();
    ctx.Things.Add(new Thing { Name = newThing });
    await ctx.SaveChangesAsync();
  7. If you want access to the file, look at the GenerateDownloadLinkAsync documentation or use/customize the BackupLink component.

The BlazorWasmExample is a working example to show it in use.

⚠️ IMPORTANT The helper requires JavaScript interop to store the database in cache. For this reason, it is important you always call SaveChangesAsync not SaveChanges when saving updates. Any other operations such as calling EnsureCreated or executing queries can be done either synchronously or asynchronously.

⚠️ ALSO IMPORTANT The helper calls EnsureCreated on the database before passing control to JavaScript. This won't conflict with other calls but may lead to unexpected behavior. For example, if you seed your database based on a successful call, you will need to change your logic to check for data in tables instead of using the EnsureCreated result.

How it works

When your app requests a DbContext, the special factory uses JavaScript interop to check for the existence of a cache. If the cache exists, it is restored and returned, otherwise a new database is created.

graph TD
A(Start) --> B[Get filename from data source]
B --> C{First time?}
C -->|Yes| D[Generate restore filename]
C -->|No| E[Generate backup filename]
D --> G[Call JavaScript]
E --> F[Backup database]
F --> G
G --> H{First time?}
H -->|Yes| I{Backup in cache?}
H -->|No| K[Store backup to cache]
I -->|Yes| J[Restore to Wasm filesystem]
I --> |No| L
J --> L[Return to DotNet]
K --> L
L --> M{Backup loaded from cache?}
M --> |Yes| N[Restore database]
M --> |No| O(End)
N --> O

The first time you context is generated, the database will be restored if a backup exists in cache. Any call to SaveChangesAsync will result in the database being saved to cache.

To see the cache, open developer tools in your browser and navigate to Application -> Cache -> Cache Storage -> SqliteWasmHelper. The key for your database is /data/cache/filename.

Access your database for troubleshooting

Run the application and use F12 to open developer tools. Navigate to the Console tab. Open the cache:

const cache = await caches.open('SqliteWasmHelper');

Now load the database backup from the cache. Swap things.db with the filename of your database.

const resp = await cache.match('/data/cache/things.db');

If the resp instance is populated, access the underlying blob:

const blob = await resp.blob();

Finally, generate the link. This should emit a link to the console you can click on to download your database.

URL.createObjectURL(blob);

You can then examine the database with your SQLite tool of choice. You can use a similar approach for synchronization but sending the blob to the server.

API documentation

Read the autogenerated API Docs.

Release notes

Read the release notes.

Summary

Questions? DM @JeremyLikness or open a GitHub issue.