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:
@@ -18,8 +18,18 @@ public sealed class AppSettingsRepository
|
||||
|
||||
row = new AppSettingsEntity { Id = AppSettingsEntity.SingletonId };
|
||||
_context.AppSettings.Add(row);
|
||||
await _context.SaveChangesAsync(ct);
|
||||
_context.Entry(row).State = EntityState.Detached;
|
||||
try
|
||||
{
|
||||
await _context.SaveChangesAsync(ct);
|
||||
_context.Entry(row).State = EntityState.Detached;
|
||||
}
|
||||
catch (DbUpdateException)
|
||||
{
|
||||
// Concurrent process already inserted the singleton — discard our attempt and re-read.
|
||||
_context.Entry(row).State = EntityState.Detached;
|
||||
row = await _context.AppSettings.AsNoTracking()
|
||||
.FirstAsync(s => s.Id == AppSettingsEntity.SingletonId, ct);
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user