fix(data): harden FK pragma per-connection and seed concurrency
- Add SqliteForeignKeyInterceptor (DbConnectionInterceptor) registered via OnConfiguring so every IDbContextFactory-created context runs PRAGMA foreign_keys=ON, not only the MigrateAndConfigure context. - DefaultListsSeeder: replace TOCTOU read-then-insert with atomic INSERT … SELECT … WHERE NOT EXISTS — one SQLite writer lock, no race. - AppSettingsRepository.GetAsync: catch DbUpdateException on the get-or-create path and re-read so concurrent startup cannot throw. - Migration 20260609000000_UniqueListName: de-duplicates empty list rows (startup-race leftovers) then adds a UNIQUE index on lists.name. - ForeignKeyTests: verifies ON DELETE SET NULL (blocked_by_task_id) is enforced on a fresh DbContext with no manual PRAGMA call. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
using ClaudeDo.Data.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ClaudeDo.Data.Seeding;
|
||||
@@ -9,17 +8,18 @@ public static class DefaultListsSeeder
|
||||
|
||||
public static async Task SeedAsync(ClaudeDoDbContext ctx, CancellationToken ct = default)
|
||||
{
|
||||
var existing = await ctx.Lists.Select(l => l.Name).ToListAsync(ct);
|
||||
var now = DateTime.UtcNow;
|
||||
foreach (var name in Defaults.Where(n => !existing.Contains(n)))
|
||||
foreach (var name in Defaults)
|
||||
{
|
||||
ctx.Lists.Add(new ListEntity
|
||||
{
|
||||
Id = Guid.NewGuid().ToString(),
|
||||
Name = name,
|
||||
CreatedAt = now,
|
||||
});
|
||||
var id = Guid.NewGuid().ToString();
|
||||
// Atomic conditional insert: the SELECT ... WHERE NOT EXISTS is a single
|
||||
// SQLite statement and cannot race — only one writer holds the lock.
|
||||
await ctx.Database.ExecuteSqlAsync(
|
||||
$"""
|
||||
INSERT INTO lists (id, name, created_at, default_commit_type, sort_order)
|
||||
SELECT {id}, {name}, {now}, 'chore', 0
|
||||
WHERE NOT EXISTS (SELECT 1 FROM lists WHERE name = {name})
|
||||
""", ct);
|
||||
}
|
||||
await ctx.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user