Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"permissions": {
"allow": [
"Bash(wc -l /d/PersonalGitRepos/EFCore.Sqlite.Concurrency/EntityFrameworkCore.Sqlite.Concurrency/src/*.cs)",
"Bash(dotnet build:*)",
"WebFetch(domain:docs.binance.us)",
"Bash(xargs grep *)",
"Bash(dotnet:*)",
"Bash(dotnet build --help:*)",
"Bash(python -c \"import json,sys; json.load\\(open\\(r'C:\\\\Users\\\\mikeg\\\\.claude\\\\settings.json'\\)\\); print\\('valid'\\)\")"
]
}
}
16 changes: 15 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

---

## [10.1.1] - 2026-10-05

### Security
- **Upgraded SQLitePCLRaw.core to 2.1.12** — addresses vulnerability [GHSA-2m69-gcr7-jv3q](https://github.com/advisories/GHSA-2m69-gcr7-jv3q)
(SQLite embedded library security fix). All transitive `SQLitePCLRaw.*` packages updated to 2.1.12.
Vulnerability suppression comment removed from project file.

### Compatibility
- **Zero API changes.** Fully backward compatible with v10.1.0.
- **Zero migration effort.** All existing code works without modification.

---

## [10.1.0] - 2026-07-05

### Added
Expand Down Expand Up @@ -95,7 +108,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- `SqliteDiagnostics` — opt-in Spectre.Console diagnostics UI (`-p:IncludeSpectre=true`).
- `MemoryPackExtensions` — opt-in MemoryPack serialization (`-p:IncludeMemoryPack=true`).

[Unreleased]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.1.0...HEAD
[Unreleased]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.1.1...HEAD
[10.1.1]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.1.0...v10.1.1
[10.1.0]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.0.3...v10.1.0
[10.0.3]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.0.2...v10.0.3
[10.0.2]: https://github.com/CornerstoneCode/EntityFrameworkCore.Sqlite.Concurrency/compare/v10.0.1...v10.0.2
Expand Down
81 changes: 81 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Build Commands

```bash
# Restore (locked packages enforced via packages.lock.json)
dotnet restore EntityFrameworkCore.Sqlite.Concurrency/EFCore.Sqlite.Concurrency.csproj

# Build
dotnet build EntityFrameworkCore.Sqlite.Concurrency.sln -c Release

# Pack NuGet package
dotnet pack EntityFrameworkCore.Sqlite.Concurrency/EFCore.Sqlite.Concurrency.csproj -c Release --no-build -o out

# Build with optional dependencies
dotnet build ... -p:IncludeMemoryPack=true # enables MemoryPackExtensions.cs
dotnet build ... -p:IncludeSpectre=true # enables SqliteDiagnostics.cs (Spectre.Console)
```

**No test projects exist.** CI only restores, builds, and packs.

## Project Overview

This is a .NET 10 NuGet library (`EntityFrameworkCore.Sqlite.Concurrency`) that adds write serialization, WAL-mode optimization, retry logic, and bulk-insert performance on top of `Microsoft.EntityFrameworkCore.Sqlite`. It is a pure library — no apps, no test harness.

Target: `net10.0` | Root namespace: `EntityFrameworkCore.Sqlite.Concurrency`

## Architecture

All source lives in `EntityFrameworkCore.Sqlite.Concurrency/src/`.

### Component Map

| File | Role |
|---|---|
| `SqliteConcurrencyOptions.cs` | Central configuration POCO; `Validate()` called at startup |
| `SqliteConnectionEnhancer.cs` | Process-global registry; owns all shared static state |
| `SqliteConcurrencyInterceptor.cs` | EF Core interceptor wiring; applies PRAGMAs and rewrites `BEGIN` |
| `SqliteConcurrencyExtensions.cs` | Public API — `UseSqliteWithConcurrency()`, `SaveChangesSerializedAsync()`, `ExecuteWithRetryAsync()`, `BulkInsertOptimizedAsync()` |
| `SqliteConcurrencyServiceCollectionExtensions.cs` | DI registration (`AddConcurrentSqliteDbContext<T>()`, `AddConcurrentSqliteDbContextFactory<T>()`) |
| `ThreadSafeFactory.cs` | DI-free factory for environments without `IServiceCollection` |
| `ThreadSafeSqliteContext<TContext>` | Optional generic base `DbContext` with write/read/bulk APIs built in |
| `SqliteErrorCodes.cs` | Internal constants and classifiers for SQLite extended error codes |
| `MemoryPackExtensions.cs` | `#if INCLUDEMEMORYPACK` — opt-in MemoryPack integration |
| `SqliteDiagnostics.cs` | `#if INCLUDESPECTRE` — opt-in Spectre.Console diagnostics UI |

### Key Design Decisions

**Write serialization has two layers:**
1. Application-level: one `SemaphoreSlim(1,1)` per database file stored in `SqliteConnectionEnhancer._writeLocks`, keyed by normalized connection string.
2. SQLite-level: `busy_timeout` PRAGMA applied on every connection open.

**`SqliteConnectionEnhancer` is the process-global brain:**
- `_writeLocks` — write-lock registry (one semaphore per DB file)
- `_interceptors` — enforces consistent options; throws `ArgumentException` on option mismatch for same DB
- `_initializedDatabases` / `_preparedDatabases` — once-per-process guards (database-scoped PRAGMAs applied once; connection-scoped PRAGMAs applied on every `ConnectionOpened`)
- `_connectionStringCache` — memoizes optimized connection strings; rejects `Cache=Shared` (incompatible with WAL)
- `IsWriteLockHeld` (`AsyncLocal<bool>`) — reentrancy guard preventing deadlock on nested write calls

**`SQLITE_BUSY_SNAPSHOT` requires full operation restart** (not just statement retry) because the read snapshot is stale. `ExecuteWithRetryAsync()` reruns the entire user-supplied lambda.

**Pre-open file preparation** (`PrepareForConnectionOpen`) strips `FileAttributes.ReadOnly` and deletes stale `.db-shm` — handles a Windows MSBuild quirk where `CopyToOutputDirectory` marks the DB read-only.

**Options equality excludes `LoggerFactory`** — used to enforce consistent interceptor registration per database; logger infrastructure is not behavioral identity.

**Conditional compilation** keeps the default package lean:
- `#if INCLUDEMEMORYPACK` — requires `-p:IncludeMemoryPack=true`
- `#if INCLUDESPECTRE` — requires `-p:IncludeSpectre=true`

### Retry Backoff

Exponential backoff with full jitter: delay ∈ `[baseDelay, 2×baseDelay]` using `Random.Shared`. Prevents thundering-herd storms on concurrent retries.

## Project File Notes

- `<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>` — always update `packages.lock.json` after adding/changing dependencies
- `<Nullable>enable</Nullable>` and `<GenerateDocumentationFile>true</GenerateDocumentationFile>` — missing XML doc comments produce build warnings
- `<ContinuousIntegrationBuild>` activates deterministic builds on GitHub Actions
- SourceLink and `EmbedUntrackedSources` are configured for NuGet symbol packages
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="coverlet.collector" Version="10.0.1">
<PackageReference Include="coverlet.collector" Version="10.1.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.7.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="18.10.1" />
<PackageReference Include="xunit" Version="2.9.3"/>
<PackageReference Include="xunit.runner.visualstudio" Version="3.1.5">
<PackageReference Include="xunit.runner.visualstudio" Version="4.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,35 +12,53 @@

<!-- Core Package Identity -->
<PackageId>EntityFrameworkCore.Sqlite.Concurrency</PackageId>
<Version>10.1.0</Version>
<Version>10.1.1</Version>

<!-- Core Metadata for Trust & Recognition -->
<Authors>Mike Gotfryd</Authors>
<Company>Cornerstone Code</Company>
<Copyright>© 2026 Cornerstone Code. All rights reserved.</Copyright>

<!-- ========== CRITICAL SEO OPTIMIZATIONS ========== -->
<!-- 1. OPTIMIZED DESCRIPTION (Front-loaded value) -->
<!-- 1. OPTIMIZED DESCRIPTION (Front-loaded value + discoverability) -->
<Description>
Fixes SQLITE_BUSY / "database is locked" / SQLITE_BUSY_SNAPSHOT errors in Entity
Framework Core + SQLite apps. Provides Channel-based write serialization, automatic
BEGIN IMMEDIATE transaction upgrades, exponential-backoff retry, WAL-mode optimization,
and 10x faster bulk inserts. Drop-in replacement for UseSqlite() targeting .NET 10
and netstandard 2.0.
Solve SQLite concurrency errors (SQLITE_BUSY, "database is locked", SQLITE_BUSY_SNAPSHOT) in
Entity Framework Core with drop-in write serialization. Implements Channel-based queuing,
automatic BEGIN IMMEDIATE transaction upgrades, exponential-backoff retry with full jitter,
WAL-mode optimization, and 10x faster bulk inserts. Solves multi-writer, multi-reader conflicts
without connection pooling complexity. Works with .NET 10 and netstandard 2.0 runtimes.
Security: Updated SQLitePCLRaw.core to 2.1.12 (fixes GHSA-2m69-gcr7-jv3q).
</Description>

<!-- 2. CORRECTED TAGS FORMAT (Space-delimited per NuGet spec) -->
<PackageTags>sqlite sqlite3 entity-framework-core efcore concurrency thread-safe multi-threading database-locked sqlite-busy sqlite-busy-snapshot error-5 write-serialization channel-queue netstandard2 efcore-concurrency performance bulk-insert parallel-reads write-ahead-logging wal dotnet-10 entity-framework orm database-provider high-performance async transactions locking queue background-service task-whenall</PackageTags>
<!-- 2. ENHANCED TAGS (SEO + Discovery) -->
<PackageTags>sqlite sqlite3 entity-framework-core efcore concurrency thread-safe multi-threading database-locked sqlite-busy sqlite-busy-snapshot error-5 write-serialization channel-queue netstandard2 efcore-concurrency performance bulk-insert parallel-reads write-ahead-logging wal dotnet-10 entity-framework orm database-provider high-performance async transactions locking queue background-service task-whenall sql-locking deadlock-prevention database-concurrency write-queue security vulnerability patch</PackageTags>

<!-- 3. STRUCTURED RELEASE NOTES -->
<!-- 3. STRUCTURED RELEASE NOTES (Clear, SEO-optimized) -->
<PackageReleaseNotes>
<![CDATA[
v10.1.1 — Security Patch: Dependency Upgrade

SECURITY UPDATE
• Upgraded SQLitePCLRaw.core to 2.1.12 — addresses vulnerability GHSA-2m69-gcr7-jv3q
(SQLite core library security fix). All transitive SQLitePCLRaw packages updated.
Suppression comment removed; vulnerability now fully patched.

COMPATIBILITY
• No API changes. No breaking changes. Zero migration effort.
• Fully backward compatible with v10.1.0.

VERIFICATION
• All dependencies verified on NuGet.
• Build passes with 0 errors (3 pre-existing warnings unrelated to security).
• No outdated packages detected.

===== PREVIOUS RELEASE: v10.1.0 =====

v10.1.0 — Channel-Based Write Queue, netstandard 2.0, Bug Fixes

NEW FEATURES
• Channel-based write queue: replaces SemaphoreSlim with Channel<IWriteRequest> + single
background writer. SemaphoreSlim wakes all N waiters simultaneously (thundering herd);
Channel parks callers in FIFO order — no wakeup storm, fairer scheduling, lower CPU.
background writer. Eliminates thundering herd; FIFO fairness; lower CPU under concurrent load.
Zero API change — existing call sites benefit automatically.
• WriteQueueCapacity option: new int? on SqliteConcurrencyOptions (null = unbounded).
Set a value to create a bounded channel with back-pressure (BoundedChannelFullMode.Wait).
Expand Down Expand Up @@ -88,21 +106,20 @@ NO BREAKING CHANGES — all existing call sites compile and behave correctly wit

<!-- Dependencies — EF Core 10.x is net10.0 only; netstandard2.0 gets the data layer only -->
<ItemGroup Condition="'$(TargetFramework)' == 'net8.0' or '$(TargetFramework)' == 'net9.0'">
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.12" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net10.0'">
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.9" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.9" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.12" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.12" />
</ItemGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
<PackageReference Include="Microsoft.Data.Sqlite" Version="7.0.20" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="8.0.3" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="8.0.2" />
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />
<PackageReference Include="Microsoft.Data.Sqlite" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.12" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="10.0.12" />
<PackageReference Include="System.Threading.Channels" Version="10.0.12" />
</ItemGroup>

<!-- EF Core source files excluded from netstandard2.0 (EF Core 10+ is net10.0-only) -->
Expand All @@ -117,15 +134,9 @@ NO BREAKING CHANGES — all existing call sites compile and behave correctly wit
</ItemGroup>
<!-- SourceLink for debugging support -->
<ItemGroup>
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.300" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="10.0.401" PrivateAssets="All" />
</ItemGroup>

<!-- Suppress GHSA-2m69-gcr7-jv3q: SQLitePCLRaw.lib.e_sqlite3 2.1.11 is the latest
available version; no patched release exists yet on NuGet. Remove this suppression
once a fixed version ships and update the SQLitePCLRaw transitive references. -->
<ItemGroup>
<NuGetAuditSuppress Include="https://github.com/advisories/GHSA-2m69-gcr7-jv3q" />
</ItemGroup>

<!-- Optional Dependencies (Conditional) -->
<ItemGroup>
Expand Down
Loading
Loading