Shaunebu.Data.SQLite 1.1.0

dotnet add package Shaunebu.Data.SQLite --version 1.1.0
                    
NuGet\Install-Package Shaunebu.Data.SQLite -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Shaunebu.Data.SQLite" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Shaunebu.Data.SQLite" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Shaunebu.Data.SQLite" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Shaunebu.Data.SQLite --version 1.1.0
                    
#r "nuget: Shaunebu.Data.SQLite, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Shaunebu.Data.SQLite@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Shaunebu.Data.SQLite&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Shaunebu.Data.SQLite&version=1.1.0
                    
Install as a Cake Tool

Shaunebu.Data.SQLite

NuGet Version NuGet Downloads .NET C# SQLite .NET MAUI Async API Dependency Injection Query Builder Interceptors Observability Structured Logging XML Documentation License Cross Platform Production Ready NuGet Distribution

Shaunebu.Data.SQLite is a lightweight convenience layer over sqlite-net-pcl for .NET and .NET MAUI applications that need simple asynchronous CRUD, table management, fluent table queries, dependency injection, lifecycle management, interceptors, and structured operation logging.

It keeps sqlite-net close at hand, but wraps the repetitive production plumbing that application teams usually have to build themselves.

๐Ÿ’ก Tip
Use SQLiteManagerService.GetInstance(...) for a shared path-based manager, or AddShaunebuSQLite(...) when your app is already composed with IServiceCollection.

โœจ Features

Feature Supported
โœ… Async CRUD Yes
โœ… Async aliases Yes
โœ… Batch operations Yes
โœ… Query builder Yes
โœ… TableContext<T> Yes
โœ… Dependency injection Yes
โœ… Configurable connection options Yes
โœ… Write-ahead logging Yes
โœ… Busy timeout Yes
โœ… Observability Yes
โœ… Interceptors Yes
โœ… Microsoft logging Yes
โœ… Shaunebu.Common.Logging Yes
โœ… Custom sqlite-net mappings Yes
โœ… .NET MAUI Yes
โœ… .NET 9 / .NET 10 Yes
โœ… XML documentation Yes
โœ… NuGet distribution Yes

๐Ÿงญ Why Shaunebu.Data.SQLite?

sqlite-net-pcl is excellent. Shaunebu.Data.SQLite is for teams that want a focused, application-level manager around it.

Capability What this package adds
Cleaner API A compact service for CRUD, table management, batch operations, and fluent queries.
MAUI-ready setup Works naturally in .NET MAUI apps and validates against MAUI consumer builds.
Built-in DI AddShaunebuSQLite(...) supports singleton, scoped, and transient registrations.
Lifecycle management Path-based singleton lookup, idempotent close, dispose support, and recreate-after-close behavior.
Production defaults Sensible default open flags and explicit configuration for WAL, busy timeout, mutex, shared cache, and date storage.
Observability Structured operation logs through Microsoft.Extensions.Logging.
Interceptors Ordered operation callbacks for metrics, audit, diagnostics, and failure observation.
Async naming Existing compatibility methods plus conventional InsertAsync, UpdateAsync, and DeleteAsync aliases.

๐Ÿ” Comparison

Feature / Library Shaunebu.Data.SQLite SQLite-net direct usage Entity Framework Core
Fluent table API โœ… TableContext<T> and TableQueryBuilder<T> โš ๏ธ Manual composition over .Table<T>() โœ… LINQ + DbSet
Batch insert/update/delete โœ… Supported with transaction-backed execution โš ๏ธ Usually implemented manually โœ… Supported
Reset / ensure table exists โœ… EnsureTableExistsAsync<T>() / ResetTableAsync<T>() โš ๏ธ Manual setup required โš ๏ธ EnsureCreated() or migrations
Dependency injection โœ… Built into the core package โš ๏ธ Application-owned wiring โœ… Built in
Operation logging โœ… Structured ILogger events โš ๏ธ Application-owned logging โœ… ILogger support
Interceptors โœ… SQLite operation callbacks โš ๏ธ Application-owned wrapper logic โœ… Interceptor model
Thread-safe manager lookup โœ… Singleton per normalized database path โš ๏ธ Depends on application code โš ๏ธ DbContext lifetime rules
Weight and complexity โœ… Lightweight wrapper, no EF Core dependency โœ… Very lightweight โŒ Heavier ORM model
Recommended use Lightweight to medium .NET / MAUI applications Micro-projects and prototypes Large data models, migrations, relational projections

๐Ÿ“ฆ Installation

Install-Package Shaunebu.Data.SQLite

The package currently targets:

Target Framework Status
net9.0 Supported
net10.0 Supported

โ„น๏ธ Note
Version 1.3.0 is standardized on modern .NET target frameworks. Older net8.0 assets are not included in this package version.

๐Ÿš€ Quick Start

using Microsoft.Extensions.Logging;
using Shaunebu.Data.SQLite;

var loggerFactory = LoggerFactory.Create(builder => builder.AddConsole());
var logger = loggerFactory.CreateLogger<SQLiteManagerService>();

var db = SQLiteManagerService.GetInstance("mydatabase.db", logger);

await db.EnsureTableExistsAsync<User>();

GetInstance keeps one live manager per normalized database path. Calls are safe for concurrent instance lookup, and the first logger supplied for a live path is the logger used by that shared instance. After CloseAsync, a later GetInstance call recreates a new manager for that path.

flowchart TD
    A["Application / .NET MAUI App"] --> B["SQLiteManagerService"]
    B --> C["sqlite-net-pcl"]
    C --> D["SQLite"]

โš™๏ธ Configuration

Use SQLiteManagerOptions when you need to configure sqlite-net open flags or startup behavior:

var db = SQLiteManagerService.GetInstance(new SQLiteManagerOptions
{
    DatabasePath = "mydatabase.db",
    OpenFlags = SQLiteOpenFlags.ReadWrite |
                SQLiteOpenFlags.Create |
                SQLiteOpenFlags.SharedCache,
    EnableWriteAheadLogging = true,
    BusyTimeout = TimeSpan.FromSeconds(30),
    StoreDateTimeAsTicks = true,
    FullMutex = true,
    SharedCache = true
});

OpenFlags is the source of truth for connection flags. FullMutex and SharedCache are convenience accessors that add or remove their corresponding SQLiteOpenFlags values from OpenFlags.

Existing path-based APIs continue to use default options: ReadWrite, Create, FullMutex, and StoreDateTimeAsTicks = true. When using GetInstance(SQLiteManagerOptions), the live singleton is still keyed by normalized DatabasePath; changing options after the first live instance is created does not reconfigure that instance. Close it before recreating the manager with different options for the same path.

๐Ÿ” Lifecycle

Close pooled SQLite connections when the database is no longer needed and all database operations have completed:

await db.CloseAsync();

or:

await using var db = SQLiteManagerService.GetInstance("mydatabase.db");

CloseAsync() is idempotent, so repeated calls are safe. Treat close/dispose as an application shutdown boundary for that database manager. Do not close the manager while other threads are actively using it.

โš ๏ธ Important
After close, using the manager, or any TableContext<T> or TableQueryBuilder<T> created by that manager, throws ObjectDisposedException. Closing does not cancel SQLite operations that were already in flight.

๐Ÿ’ก Best Practices

  1. Use GetInstance(dbPath) or singleton DI registration when one shared manager per database file is the right lifetime for your application.
  2. Use scoped or transient DI registration when a manager must be isolated from the global singleton registry.
  3. Use SQLiteManagerOptions rather than ad hoc setup calls when WAL, busy timeout, open flags, or observability should be applied during construction.
  4. Use ResetTableAsync<T>() for tests, demos, and explicit table reset workflows; avoid it for user data unless destructive reset is intentional.
  5. Use TableContext<T> for table-scoped CRUD workflows and TableQueryBuilder<T> when fluent query composition is the focus.
  6. Keep CloseAsync() as a shutdown boundary after database work has completed.
  7. Keep sensitive values out of log messages and let structured logging carry operation metadata.

๐Ÿ—„๏ธ Table Management

Method Description
EnsureTableExistsAsync<T>() Creates the mapped table if it does not exist.
ResetTableAsync<T>() Drops and recreates the mapped table, including custom [Table] names.
For<T>().DeleteAllAsync() Deletes all rows from the mapped table.
await db.EnsureTableExistsAsync<User>();
await db.ResetTableAsync<User>();
await db.For<User>().DeleteAllAsync();

Custom sqlite-net mappings are honored by table creation, reset, query, and delete-all operations.

๐Ÿ”„ CRUD Operations

await db.Insert(new User { Name = "Alice" });
await db.InsertAsync(new User { Name = "Alice Async" });

await db.Insert(new List<User>
{
    new() { Name = "Bob" },
    new() { Name = "Charlie" }
});
await db.InsertAsync(new List<User>
{
    new() { Name = "Dana" },
    new() { Name = "Eve" }
});

var alice = await db.For<User>()
    .Where(user => user.Name == "Alice")
    .FirstOrDefaultAsync();

if (alice is not null)
{
    alice.Name = "Alice Updated";
    await db.Update(alice);
    await db.UpdateAsync(alice);
    await db.Delete(alice);
}

The SQLiteManagerService method names Insert, Update, and Delete are kept for compatibility. The InsertAsync, UpdateAsync, and DeleteAsync aliases are available for conventional .NET async naming and delegate to the existing methods.

โ„น๏ธ Note
The compatibility method names are asynchronous even though their names do not end with Async. New code can use the Async aliases for clarity.

๐Ÿ” Query Builder

Use For<T>() for table-scoped operations:

var users = await db.For<User>()
    .Where(user => user.Name.Contains("Alice"))
    .OrderBy(user => user.Name)
    .ToListAsync();

Use Table<T>() when you want the wrapper query builder and its CountAsync helper:

var count = await db.Table<User>()
    .Where(user => user.Name.Contains("Alice"))
    .CountAsync();
API Best for
TableContext<T> Table-scoped CRUD, delete-all, and simple query workflows.
TableQueryBuilder<T> Fluent query composition with Where, ordering, ToListAsync, FirstOrDefaultAsync, and CountAsync.

๐ŸŒ TableContext Helper

Create a table-specific context from the manager when the surrounding code is focused on one model:

var usersTable = db.For<User>();
await usersTable.InsertAsync(new User { Name = "Diana" });

var allUsers = await usersTable.ToListAsync();

Supported table-context query methods:

Method Description
Where(Expression<Func<T, bool>>) Filters results.
OrderBy<TKey>(Expression<Func<T, TKey>>) Sorts ascending.
OrderByDescending<TKey>(Expression<Func<T, TKey>>) Sorts descending.
ToListAsync() Executes the query and returns a list.
FirstOrDefaultAsync() Returns the first matching row or null.

๐Ÿ’‰ Dependency Injection

The core Shaunebu.Data.SQLite package includes dependency injection extensions. Register the manager in a .NET or .NET MAUI app with AddShaunebuSQLite():

using Microsoft.Extensions.DependencyInjection;

builder.Services.AddShaunebuSQLite(options =>
{
    options.DatabasePath = databasePath;
    options.EnableWriteAheadLogging = true;
    options.BusyTimeout = TimeSpan.FromSeconds(30);
});

The default lifetime is singleton. Scoped and transient registrations are supported:

builder.Services.AddShaunebuSQLite(
    options => options.DatabasePath = databasePath,
    ServiceLifetime.Scoped);

Singleton DI registration uses the same path-based shared manager as SQLiteManagerService.GetInstance. Scoped and transient registrations create independent managers. The DI container disposes resolved managers when their configured lifetime ends; manually calling CloseAsync is still valid, and the operation is idempotent.

Registered interceptors are resolved automatically by the core package DI extensions:

builder.Services.AddSingleton<ISQLiteInterceptor, MetricsInterceptor>();
builder.Services.AddSingleton<ISQLiteInterceptor, AuditInterceptor>();

Interceptors are resolved once per manager construction and preserve registration order. Scoped and transient SQLite managers do not use the global singleton registry. Scoped interceptors are rejected for singleton manager registration; use scoped or transient manager registration when interceptors need scoped dependencies.

๐Ÿ“Š Observability

Enable operation logging through SQLiteManagerOptions:

builder.Services.AddShaunebuSQLite(options =>
{
    options.DatabasePath = databasePath;
    options.EnableObservability = true;
    options.LogLevel = LogLevel.Information;
    options.SlowOperationThreshold = TimeSpan.FromMilliseconds(500);
});

EnableObservability = true enables routine completion logs at LogLevel and slow-operation warnings at warning level. EnableObservability = false disables those routine operation logs, but it does not silently skip registered interceptors and it does not suppress error logs that are accepted by the configured ILogger filters.

The library logs through Microsoft.Extensions.Logging.ILogger. It does not implement a separate logging pipeline. If your application uses Shaunebu.Common.Logging, configure its existing Microsoft logging provider and SQLite logs will flow through that pipeline:

builder.Services
    .AddShaunebuLogging(options =>
    {
        options.ApplicationName = "MyMauiApp";
        options.Redaction.Enabled = true;
    })
    .AddShaunebuMicrosoftLogging();
flowchart TD
    A["Application"] --> B["ILogger / ILogger<T>"]
    B --> C["Shaunebu.Common.Logging Microsoft Adapter"]
    C --> D["Shaunebu.Common.Logging"]
    D --> E["Console Provider"]
    D --> F["File Provider"]
    D --> G["Custom Providers"]

Operation logs include operation type, table name when known, duration, slow-operation warnings, and exception details. Entity values, SQL parameter values, connection strings, encryption keys, tokens, and absolute database paths are not logged by default. SQLiteManagerOptions.LogLevel chooses the requested level for successful completion logs; normal Microsoft.Extensions.Logging filters still decide whether that level is emitted.

๐Ÿงพ Sample Log Output

[Information] OperationCompleted
Table: Users
Duration: 14 ms

With Shaunebu.Common.Logging, SQLite logs flow through its provider pipeline:

INFORMATION [Shaunebu.Data.SQLite.SQLiteManagerService] SQLite operation Insert completed in 7.3 ms for table users
PROPERTIES: {"OperationType":4,"DurationMs":7.3,"TableName":"users","EventName":"OperationCompleted"}

SQLite logs use stable event names:

Event name When emitted
DatabaseOpened Startup open observation completed.
DatabaseClosed CloseAsync completed.
OperationCompleted A non-special SQLite operation completed.
SlowOperation A completed operation met or exceeded SlowOperationThreshold.
OperationFailed A SQLite operation failed.
InterceptorFailed OnExecutedAsync or OnExceptionAsync threw.
WriteAheadLoggingEnabled WAL was enabled.
BusyTimeoutConfigured Busy timeout was configured.

๐Ÿ’ก Tip
Keep application-level redaction enabled in your logging stack when logs may leave the device or workstation.

๐Ÿงฉ Interceptors

Implement ISQLiteInterceptor to observe operations:

public sealed class MetricsInterceptor : ISQLiteInterceptor
{
    public ValueTask OnExecutingAsync(SQLiteOperationContext context)
        => ValueTask.CompletedTask;

    public ValueTask OnExecutedAsync(SQLiteOperationContext context)
    {
        var duration = context.Duration;
        return ValueTask.CompletedTask;
    }

    public ValueTask OnExceptionAsync(SQLiteOperationExceptionContext context)
    {
        var exception = context.OperationException;
        return ValueTask.CompletedTask;
    }
}

The operation context exposes a generated operation id, operation type, sanitized database identifier, table name, entity type, item count, start/completion timestamps, duration, exception, and sanitized metadata. It never exposes entity values or SQL parameters.

Interceptor failure behavior is deterministic:

Callback Failure behavior
OnExecutingAsync Prevents the SQLite operation from running and propagates the interceptor exception.
OnExecutedAsync Does not turn a successful SQLite operation into a failed operation; the interceptor failure is reported through ILogger.
OnExceptionAsync Does not replace the original SQLite exception; interceptor failure is reported separately through ILogger.

Callbacks are invoked in registration order and exactly once for the applicable phase.

๐Ÿงพ Operation Mapping

Public operation SQLiteOperationType
Constructor startup observation Open
CloseAsync, DisposeAsync Close
EnsureTableExistsAsync<T>() CreateTable
ResetTableAsync<T>() ResetTable
Insert<T>(T), InsertAsync<T>(T), TableContext<T>.InsertAsync(T) Insert
Insert<T>(List<T>), InsertAsync<T>(List<T>), TableContext<T>.InsertAsync(List<T>) BatchInsert
Update<T>(T), UpdateAsync<T>(T), TableContext<T>.UpdateAsync(T) Update
Update<T>(List<T>), UpdateAsync<T>(List<T>), TableContext<T>.UpdateAsync(List<T>) BatchUpdate
Delete<T>(T), DeleteAsync<T>(T), TableContext<T>.DeleteAsync(T) Delete
Delete<T>(List<T>), DeleteAsync<T>(List<T>), TableContext<T>.DeleteAsync(List<T>) BatchDelete
TableContext<T>.DeleteAllAsync() DeleteAll
For<T>().ToListAsync(), Table<T>().ToListAsync() Query
For<T>().FirstOrDefaultAsync(), Table<T>().FirstOrDefaultAsync() FirstOrDefault
Table<T>().CountAsync() Count
Internal transaction-backed batch execution Surfaced as BatchInsert, BatchUpdate, or BatchDelete
EnableWriteAheadLoggingAsync() EnableWriteAheadLogging
SetBusyTimeoutAsync(TimeSpan) BusyTimeout

๐Ÿงฐ SQLite Options

The manager exposes common SQLite connection settings:

await db.SetBusyTimeoutAsync(TimeSpan.FromSeconds(5));
await db.EnableWriteAheadLoggingAsync();

The same settings can be applied at creation time through SQLiteManagerOptions:

var db = SQLiteManagerService.GetInstance(new SQLiteManagerOptions
{
    DatabasePath = "mydatabase.db",
    EnableWriteAheadLogging = true,
    BusyTimeout = TimeSpan.FromSeconds(5)
});
Option Purpose
DatabasePath Database file path used to create or locate the manager.
OpenFlags sqlite-net open flags used by the underlying connection.
EnableWriteAheadLogging Enables WAL during manager startup.
BusyTimeout Configures SQLite busy timeout during manager startup.
StoreDateTimeAsTicks Controls sqlite-net date/time storage behavior.
FullMutex Convenience flag for SQLiteOpenFlags.FullMutex.
SharedCache Convenience flag for SQLiteOpenFlags.SharedCache.
EnableObservability Enables routine structured operation logging.
LogLevel Completion log level requested by the manager.
SlowOperationThreshold Duration threshold for slow-operation warnings.
Interceptors Ordered SQLite operation interceptors.

๐Ÿงฑ Example Model

using SQLite;

public sealed class User
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }

    public string Name { get; set; } = "";
}

Custom sqlite-net mappings are honored:

[Table("app_users")]
public sealed class User
{
    [PrimaryKey, AutoIncrement]
    public int Id { get; set; }

    public string Name { get; set; } = "";
}

๐Ÿ› ๏ธ Full Flow Example

var userDb = SQLiteManagerService.GetInstance("users.db");
var productDb = SQLiteManagerService.GetInstance("products.db");

await userDb.EnsureTableExistsAsync<User>();
await productDb.EnsureTableExistsAsync<Product>();
await productDb.ResetTableAsync<Product>();

await userDb.Insert(new User { Name = "Alice" });
await productDb.Insert(new Product { Name = "Laptop", Price = 1200 });

var moreUsers = new List<User>
{
    new() { Name = "Bob" },
    new() { Name = "Charlie" }
};
await userDb.Insert(moreUsers);

var users = await userDb.For<User>().ToListAsync();
Console.WriteLine($"Users count: {users.Count}");

var userAlice = await userDb.For<User>()
    .Where(user => user.Name == "Alice")
    .FirstOrDefaultAsync();

if (userAlice is not null)
{
    userAlice.Name = "Alice Updated";
    await userDb.Update(userAlice);
    await userDb.Delete(userAlice);
}

var sortedProducts = await productDb.For<Product>()
    .OrderByDescending(product => product.Price)
    .ToListAsync();

foreach (var product in sortedProducts)
{
    Console.WriteLine($"{product.Name} - {product.Price}");
}

โšก Performance

Shaunebu.Data.SQLite keeps the wrapper thin and delegates database execution to sqlite-net-pcl. Benchmark coverage is maintained for release validation across common operations.

Benchmark area Covered
Single insert Yes
Batch insert Yes
Delete Yes
Delete all Yes
First-or-default Yes
To-list query Yes
Count Yes
Create table Yes
Reset table Yes
WAL enablement Yes
Batch-size comparison Yes

โ„น๏ธ Note
Benchmarks are intended to establish measurable baselines. They are not a substitute for testing your app's real database shape, device class, storage, and concurrency profile.

๐Ÿงช Console Client

The public samples include Shaunebu.Data.SQLite.Client, a net10.0 interactive console showcase for the current public API. It demonstrates manager CRUD, async aliases, batch operations, query builder, TableContext, connection options, dependency injection, lifecycle, WAL and busy timeout, interceptors, observability, Shaunebu.Common.Logging, concurrency, and expected failures.

Mode Command Purpose
Interactive dotnet run --project Shaunebu.Data.SQLite.Client/Shaunebu.Data.SQLite.Client.csproj -c Release Opens the menu-driven showcase.
Validation dotnet run --project Shaunebu.Data.SQLite.Client/Shaunebu.Data.SQLite.Client.csproj -c Release -- --validate-all Runs the deterministic validation suite with concise output.
Scenario list dotnet run --project Shaunebu.Data.SQLite.Client/Shaunebu.Data.SQLite.Client.csproj -c Release -- --list-scenarios Prints available scenario keys.
Single scenario dotnet run --project Shaunebu.Data.SQLite.Client/Shaunebu.Data.SQLite.Client.csproj -c Release -- --scenario crud Runs one scenario by key.

Interactive mode and single-scenario mode display SQLite log output where relevant. Validation mode intentionally suppresses live console logging so automated output remains concise.

The client stores demo databases under the current user's local application data folder and prints a sanitized database identifier by default. It is a manual showcase and release-validation helper, not a replacement for automated unit tests.

๐Ÿ“˜ Public Assets

The public GitHub presence for Shaunebu.Data.SQLite is documentation- and sample-focused. Applications consume the compiled library through NuGet.

Asset Purpose
README Primary NuGet and GitHub usage guide.
Console Client / Samples Interactive examples and validation scenarios for the public API.
Documentation Feature guidance, lifecycle notes, observability guidance, and release-readiness notes.
NuGet package Compiled commercial library package for application consumption.

๐Ÿ“š References

๐Ÿ“„ License

Shaunebu.Data.SQLite is a closed-source commercial library distributed through NuGet. Review the license terms provided with the NuGet package or your commercial agreement.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.1.0 98 7/31/2026
1.0.0 587 10/1/2025

Add observability, structured operation logging, SQLite operation interceptors, and built-in dependency injection extensions while preserving existing APIs.