Moving from Microsoft.EntityFrameworkCore.Sqlite to
EntityFrameworkCore.Sqlite.Concurrency is a drop-in change. This guide covers every
registration pattern and provides a checklist for a clean migration.
dotnet add package EntityFrameworkCore.Sqlite.ConcurrencyRemove any package that was providing manual retry or locking logic — it is no longer needed.
// Before
options.UseSqlite("Data Source=app.db");
// After
options.UseSqliteWithConcurrency("Data Source=app.db");// Before
builder.Services.AddDbContext<AppDbContext>(o =>
o.UseSqlite("Data Source=app.db"));
// After
builder.Services.AddConcurrentSqliteDbContext<AppDbContext>("Data Source=app.db");// Before
builder.Services.AddDbContextFactory<AppDbContext>(o =>
o.UseSqlite("Data Source=app.db"));
// After
builder.Services.AddConcurrentSqliteDbContextFactory<AppDbContext>("Data Source=app.db");// After — with options
builder.Services.AddConcurrentSqliteDbContext<AppDbContext>(
"Data Source=app.db",
options =>
{
options.BusyTimeout = TimeSpan.FromSeconds(30);
options.MaxRetryAttempts = 5;
options.SynchronousMode = SqliteSynchronousMode.Full;
});-
Remove
Cache=Sharedfrom all connection strings.Cache=Sharedis incompatible with WAL mode. The library throwsArgumentExceptionat startup if it is present. Connection pooling is enabled automatically.// Before (remove this flag) "Data Source=app.db;Cache=Shared" // After "Data Source=app.db" -
Remove manual
SemaphoreSlimorlockwrite guards. The write queue handles write serialization. Application-level locks aroundSaveChangesAsyncare redundant and may cause deadlocks when combined with the write queue. -
Remove custom retry loops around
SaveChangesAsync. Built-in exponential backoff with full jitter handlesSQLITE_BUSYandSQLITE_BUSY_SNAPSHOTvariants. Custom retry code should be removed to avoid double-retry and incorrect snapshot restart behavior. -
Switch concurrent workloads from shared
DbContexttoIDbContextFactory<T>. If any background service,Task.WhenAll, orParallel.ForEachAsyncblock shares a singleDbContextacross concurrent tasks, switch toAddConcurrentSqliteDbContextFactory<T>and injectIDbContextFactory<AppDbContext>. CallCreateDbContext()per task. See Concurrent EF Core patterns. -
Remove custom
PRAGMAsetup fromOnConfiguringor migration scripts. The library appliesjournal_mode=WAL,busy_timeout,wal_autocheckpoint, andsynchronouson every connection open. Custom PRAGMA calls may conflict. UseSqliteConcurrencyOptionsto configure these values instead. -
Remove any
SaveChangesSerializedAsyncworkarounds (if you had a custom version).SaveChangesSerializedAsyncis a first-class extension method onDbContext— call it directly anywhere you previously calledSaveChangesAsync.
dotnet build
dotnet testRun your normal integration tests. If any test hits SQLITE_BUSY, check the checklist
above — the most common cause is a shared DbContext across concurrent tasks.
| Version | Highlights |
|---|---|
| 10.1.0 | Channel-based write queue, WriteQueueCapacity, netstandard 2.0 TFM, BulkInsertSafeAsync O(n) fix and ChangeTracker.Clear() |
| 10.0.3 | SQLITE_BUSY_SNAPSHOT correct restart, AddConcurrentSqliteDbContextFactory<T>, structured logging, WAL checkpoint monitoring, migration lock recovery |
| 10.0.2 | SynchronousMode option, UpgradeTransactionsToImmediate option, startup validation |
| 10.0.1 | Cache=Shared rejection at startup, full jitter backoff, GetWalCheckpointStatusAsync |
| 10.0.0 | Initial release — WAL mode, BEGIN IMMEDIATE upgrade, BulkInsertOptimizedAsync, DI registration |
None in any version. All releases have been fully backwards-compatible.