fix(kpi): untracked projected fold fetch — fold reads no longer flood the change tracker (plan R2-04 T1)

This commit is contained in:
Joseph Doherty
2026-07-13 09:42:31 -04:00
parent 1429ddaa0e
commit 29c1286943
2 changed files with 30 additions and 0 deletions
@@ -101,8 +101,14 @@ public sealed class KpiHistoryRepository : IKpiHistoryRepository
// fetch it and group IN MEMORY. This deliberately avoids provider-specific
// DATEADD/DATEPART hour-truncation translation (SQLite in tests, SQL Server in prod),
// keeping the hour-bucket arithmetic identical across providers and trivially correct.
// Projected, untracked fetch: the fold only reads these six fields and never
// mutates a KpiSample. A tracked ToListAsync registered ~5k-90k read-only
// entities in the change tracker per healthy 3h fold — all re-scanned by
// DetectChanges on the final SaveChanges (arch-review 04 round 2, R2). A
// projection is inherently untracked and materializes no entity at all.
var samples = await _context.KpiSamples
.Where(s => s.CapturedAtUtc >= from && s.CapturedAtUtc < to)
.Select(s => new FoldSample(s.Source, s.Metric, s.Scope, s.ScopeKey, s.CapturedAtUtc, s.Value))
.ToListAsync(cancellationToken);
if (samples.Count == 0)
{
@@ -212,4 +218,8 @@ public sealed class KpiHistoryRepository : IKpiHistoryRepository
/// <summary>In-memory grouping key: one KPI series (four-tuple) within one UTC hour.</summary>
private readonly record struct SeriesHourKey(
string Source, string Metric, string Scope, string? ScopeKey, DateTime HourStartUtc);
/// <summary>Narrow, untracked projection of one KpiSample row for the fold (R2).</summary>
private readonly record struct FoldSample(
string Source, string Metric, string Scope, string? ScopeKey, DateTime CapturedAtUtc, double Value);
}