-
Notifications
You must be signed in to change notification settings - Fork 2
Dependency Injection
The SQLite.Framework.DependencyInjection package registers SQLiteDatabase (or a subclass of it) into a Microsoft.Extensions.DependencyInjection service collection. The API looks a lot like Entity Framework Core's AddDbContext and AddDbContextFactory, so if you have used those you will feel at home.
dotnet add package SQLite.Framework
dotnet add package SQLite.Framework.DependencyInjectionConfigure SQLiteOptionsBuilder in a callback. The options are built once when the database is first resolved.
services.AddSQLiteDatabase<AppDatabase>(b =>
{
b.DatabasePath = "app.db";
b.MinimumSqliteVersion = SQLiteMinimumVersion.V3_35;
b.IsWalMode = true;
});Then inject SQLiteDatabase anywhere:
public class BookService
{
private readonly SQLiteDatabase db;
public BookService(SQLiteDatabase db)
{
this.db = db;
}
public Task<List<Book>> GetAllAsync()
{
return db.Table<Book>().ToListAsync();
}
}The default lifetime is Singleton. One database instance is created the first time you resolve it and reused for the rest of the app. That's a good choice for most mobile apps.
If you want a different lifetime, pass it as the second argument:
services.AddSQLiteDatabase<AppDatabase>(b => b.DatabasePath = dbPath, ServiceLifetime.Scoped);Transient is almost never what you want. You would get a brand new connection for every resolve.
If the database path (or anything else) comes from another registered service, use the overload that also takes the IServiceProvider:
services.AddSQLiteDatabase<AppDatabase>((sp, b) =>
{
IConfiguration config = sp.GetRequiredService<IConfiguration>();
b.DatabasePath = config["Db:Path"]!;
b.IsWalMode = config.GetValue("Db:Wal", true);
});Register a subclass of SQLiteDatabase the same way. The subclass needs a public constructor that takes SQLiteOptions, plus any other services you want DI to hand it:
public class AppDatabase : SQLiteDatabase
{
private readonly ILogger<AppDatabase> logger;
public AppDatabase(SQLiteOptions options, ILogger<AppDatabase> logger)
: base(options)
{
this.logger = logger;
}
}
services.AddSQLiteDatabase<AppDatabase>(b => b.DatabasePath = "library.db");ActivatorUtilities pulls the extra constructor arguments (ILogger<T> above) from the same service provider.
When a single scope needs more than one database instance (for example, a background worker that iterates items and wants a short lived database per item), register a factory instead:
services.AddSQLiteDatabaseFactory<LibraryDatabase>(b => b.DatabasePath = "library.db");Then inject ISQLiteDatabaseFactory<LibraryDatabase>:
public class ImportWorker
{
private readonly ISQLiteDatabaseFactory<LibraryDatabase> factory;
public ImportWorker(ISQLiteDatabaseFactory<LibraryDatabase> factory)
{
this.factory = factory;
}
public async Task RunAsync(IEnumerable<string> files)
{
foreach (string file in files)
{
using LibraryDatabase db = factory.CreateDatabase();
// ... use db ...
}
}
}The factory itself is Singleton by default. Each CreateDatabase() call returns a fresh LibraryDatabase. The caller owns the instance and is responsible for disposing it.