You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
New Features
Added fine-grained SQLite concurrency configuration options: SynchronousMode for durability control and UpgradeTransactionsToImmediate for transaction optimization.
Introduced factory pattern support via new AddConcurrentSqliteDbContextFactory method for concurrent workload scenarios.
Added WAL checkpoint and migration lock management APIs for advanced control.
Improvements
Enhanced error handling and retry logic with full-jitter exponential backoff and improved error classification.
Integrated logging support throughout the concurrency layer.
Documentation
Updated setup guidance with clearer configuration patterns and concurrent usage best practices.
This first major release transforms SQLite into a robust database for concurrent .NET applications by fixing core limitations of the standard provider.
40
-
41
-
**✅ SOLVES: Concurrency & Locking Errors**
42
-
• **Eliminates `SQLITE_BUSY` / "database is locked" errors** with automatic, application-level write serialization.
43
-
• **Guarantees 100% write reliability** under any multi-threaded load.
44
-
45
-
**⚡ DELIVERS: Exceptional Performance**
46
-
• **Achieves up to 10x faster bulk inserts** vs. standard `SaveChanges()` through intelligent batching.
47
-
• **Enables true parallel read scaling** with non-blocking connections.
48
-
• **Optimizes all interactions** (connections, transactions, WAL mode) for maximum throughput.
49
-
50
-
**🧩 PROVIDES: Seamless Developer Experience**
51
-
• **Drop-in replacement** – change `UseSqlite()` to `UseSqliteWithConcurrency()`.
52
-
• **Full EF Core compatibility** – all existing DbContexts, models, and LINQ queries work unchanged.
One context is created per HTTP request through the DI scope. ASP.NET Core processes requests one thread at a time per scope, so sharing a context here is safe.
#### Concurrent use (background workers, Task.WhenAll, channels, hosted services)
60
+
61
+
A `DbContext` is **not thread-safe** — it must not be shared across concurrent operations. Use `IDbContextFactory<T>` instead. Each concurrent flow calls `CreateDbContext()` to get its own independent instance.
awaitTask.WhenAll(tasks); // ✅ Each task has its own context — no EF thread-safety violation
87
+
}
88
+
}
59
89
```
60
90
91
+
> **Note:**`Cache=Shared` in the connection string is incompatible with WAL mode and will throw an `ArgumentException` at startup. Use the default connection string format (`Data Source=blog.db`) — connection pooling is enabled automatically.
92
+
61
93
## Basic Usage Examples
62
94
63
95
### Writing Data (Automatically Thread-Safe)
@@ -168,45 +200,60 @@ public class ImportService
168
200
Imagine a scenario where multiple background workers are processing tasks:
169
201
170
202
```csharp
171
-
// WITHOUT ThreadSafeEFCore.SQLite - This would fail with "database is locked"
203
+
// ❌ WRONG — sharing one DbContext across concurrent tasks
204
+
// EF Core will throw InvalidOperationException about concurrent usage,
205
+
// and SQLite returns "database is locked" for simultaneous writers.
172
206
publicclassTaskProcessor
173
207
{
208
+
privatereadonlyAppDbContext_context; // shared — unsafe for concurrent use
|`BusyTimeout`| 30 seconds | Per-connection `PRAGMA busy_timeout`. First layer of busy handling; SQLite retries lock acquisition internally for up to this duration. |
304
+
|`MaxRetryAttempts`| 3 | Application-level retry attempts for `SQLITE_BUSY*` errors, with exponential backoff and jitter. |
305
+
|`CommandTimeout`| 300 seconds | EF Core SQL command timeout in seconds. |
306
+
|`WalAutoCheckpoint`| 1000 pages | WAL auto-checkpoint interval (`PRAGMA wal_autocheckpoint`). Each page is 4 096 bytes by default (~4 MB). Set to `0` to disable. |
307
+
|`SynchronousMode`|`Normal`| Durability vs. performance trade-off (`PRAGMA synchronous`). `Normal` is recommended for WAL mode: safe against application crashes; a power loss or OS crash may roll back the last commit(s) not yet checkpointed. Use `Full` or `Extra` for stronger durability guarantees. |
308
+
|`UpgradeTransactionsToImmediate`|`true`| Rewrites `BEGIN`/`BEGIN TRANSACTION` to `BEGIN IMMEDIATE` to prevent `SQLITE_BUSY_SNAPSHOT` mid-transaction. Disable only if you manage write transactions explicitly yourself. |
309
+
310
+
## Multi-Instance Deployments and Migration Locks
311
+
312
+
EF Core uses a `__EFMigrationsLock` table to serialize concurrent migrations. If a migration process crashes after acquiring the lock but before releasing it, subsequent calls to `Database.Migrate()` will block indefinitely.
313
+
314
+
**Recommended approach:** run migrations once as a controlled startup step rather than calling `Database.Migrate()` from every app instance simultaneously.
315
+
316
+
If a stale lock does occur, use the built-in helper to detect and clear it:
logger.LogWarning("Stale EF migration lock found and released. Proceeding with migration.");
327
+
328
+
awaitdb.Database.MigrateAsync();
329
+
```
330
+
331
+
Pass `release: false` to check for a stale lock without removing it (useful for diagnostics).
332
+
333
+
> **Network filesystem warning:** SQLite WAL mode requires all connections to be on the **same physical host**. Do not point the database at an NFS, SMB, or other network-mounted path. If your app runs across multiple machines or containers, use a client/server database instead.
262
334
263
335
## Best Practices
264
336
265
-
1.**Use Dependency Injection** when possible for automatic context management
266
-
2.**Keep write transactions short** - queue your data and write quickly
267
-
3.**Use `BulkInsertOptimizedAsync`** for importing large amounts of data
268
-
4.**Enable WAL mode** (already done by default) for better concurrency
269
-
5.**Monitor performance** with the built-in diagnostics when needed
337
+
1.**Use `IDbContextFactory<T>` for concurrent workloads** — inject the factory and call `CreateDbContext()` per concurrent operation; never share a single `DbContext` instance across concurrent tasks
338
+
2.**Use `AddConcurrentSqliteDbContext<T>` for request-scoped workloads** — standard ASP.NET Core controllers and Razor Pages where one request = one thread = one context
339
+
3.**Keep write transactions short** — acquire the write slot, write, commit; long-held write transactions block all other writers
340
+
4.**Use `BulkInsertOptimizedAsync`** for importing large amounts of data
341
+
5.**WAL mode is enabled automatically** — do not add `Cache=Shared` to the connection string; it is incompatible with WAL
342
+
6.**Run migrations from a single process** — avoid calling `Database.Migrate()` concurrently from multiple instances; use `TryReleaseMigrationLockAsync` if a stale lock occurs
343
+
7.**Stay on local disk** — WAL mode does not work over network filesystems (NFS, SMB); use a client/server database for multi-host deployments
0 commit comments