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
What was changed
SqliteConcurrencyServiceCollectionExtensions.cs
Added AddConcurrentSqliteDbContextFactory<TContext> — registers IDbContextFactory<TContext> with concurrency settings and auto-injected ILoggerFactory. Factory lifetime defaults to Singleton (appropriate since the factory holds no per-request state).
Updated AddConcurrentSqliteDbContext XML doc to point users toward the factory overload for concurrent workloads.
SqliteConnectionEnhancer.cs
ComputeOptimizedConnectionString now throws ArgumentException when Cache=Shared is detected, with a clear message explaining the WAL incompatibility and pointing to connection pooling as the correct alternative.
Added TryReleaseMigrationLockAsync — checks for a stale __EFMigrationsLock row and optionally deletes it. Accepts a release: false flag for diagnostic-only checks. Includes full XML docs covering when and why stale locks occur.
QUICKSTART.md
"Configure" section now shows both registration paths with guidance on when to use each.
"Real-World Scenario" corrected: the bad pattern now shows the EF thread-safety violation explicitly, and the good pattern uses IDbContextFactory + CreateDbContext() per task.
New "Multi-Instance Deployments and Migration Locks" section with TryReleaseMigrationLockAsync usage and network filesystem warning.
Best Practices list updated to 7 items covering factory pattern, Cache=Shared, migration lock, and single-host constraint.
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
+
}
58
89
```
59
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
+
60
93
## Basic Usage Examples
61
94
62
95
### Writing Data (Automatically Thread-Safe)
@@ -167,45 +200,60 @@ public class ImportService
167
200
Imagine a scenario where multiple background workers are processing tasks:
168
201
169
202
```csharp
170
-
// 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.
171
206
publicclassTaskProcessor
172
207
{
208
+
privatereadonlyAppDbContext_context; // shared — unsafe for concurrent use
|`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. |
260
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. |
261
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.
334
+
262
335
## Best Practices
263
336
264
-
1.**Use Dependency Injection** when possible for automatic context management
265
-
2.**Keep write transactions short** - queue your data and write quickly
266
-
3.**Use `BulkInsertOptimizedAsync`** for importing large amounts of data
267
-
4.**Enable WAL mode** (already done by default) for better concurrency
268
-
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
Copy file name to clipboardExpand all lines: EntityFrameworkCore.Sqlite.Concurrency/src/ExtensionMethods/SqliteConcurrencyServiceCollectionExtensions.cs
+80Lines changed: 80 additions & 0 deletions
Original file line number
Diff line number
Diff line change
@@ -20,10 +20,20 @@ public static class SqliteConcurrencyServiceCollectionExtensions
20
20
/// <param name="contextLifetime">The lifetime of the DbContext.</param>
21
21
/// <returns>The service collection.</returns>
22
22
/// <remarks>
23
+
/// <para>
23
24
/// This overload automatically resolves <see cref="ILoggerFactory"/> from the DI
24
25
/// container and injects it into the concurrency options so that <c>SQLITE_BUSY*</c>
25
26
/// events and <c>BEGIN IMMEDIATE</c> upgrades are logged through the application's
26
27
/// normal logging pipeline.
28
+
/// </para>
29
+
/// <para>
30
+
/// A <see cref="DbContext"/> instance is <b>not thread-safe</b>. For workloads that
0 commit comments