[Medium] AggregateKnowledgeCommandHandler has N+1 queries per domain group #62

Closed
opened 2026-05-05 04:15:43 +00:00 by spikerj · 1 comment
Owner

Severity: Medium (perf)

File: SpikerSoft.Business/Domain/Activity/Commands/AggregateKnowledge/AggregateKnowledgeCommandHandler.cs (~20-34)

Problem: Per-domain group, a separate UserKnowledgeProgress.Where(...).ToListAsync() query runs. With N domains this is N queries per aggregate operation.

Fix: Single bulk Where(p => p.UserId == userId).ToListAsync() then .GroupBy in memory.

Acceptance criteria:

  • One query per aggregation, regardless of domain count
  • Result identical to current behavior
  • Existing AggregateKnowledgeCommandHandlerTests still pass
**Severity:** Medium (perf) **File:** `SpikerSoft.Business/Domain/Activity/Commands/AggregateKnowledge/AggregateKnowledgeCommandHandler.cs` (~20-34) **Problem:** Per-domain group, a separate `UserKnowledgeProgress.Where(...).ToListAsync()` query runs. With N domains this is N queries per aggregate operation. **Fix:** Single bulk `Where(p => p.UserId == userId).ToListAsync()` then `.GroupBy` in memory. **Acceptance criteria:** - [ ] One query per aggregation, regardless of domain count - [ ] Result identical to current behavior - [ ] Existing AggregateKnowledgeCommandHandlerTests still pass
Author
Owner

Resolved.

Replaced the per-domain UserKnowledgeProgress.Where(...).ToListAsync() (N queries) with a single up-front load:

var existingProgressByDomain = (await context.UserKnowledgeProgress
        .Where(p => p.UserId == request.UserId)
        .ToListAsync(cancellationToken))
    .GroupBy(p => p.Domain)
    .ToDictionary(g => g.Key, g => g.First(), StringComparer.Ordinal);

The per-domain loop now does an in-memory TryGetValue against this dictionary. One query regardless of domain count.

File: SpikerSoft.Business/Domain/Activity/Commands/AggregateKnowledge/AggregateKnowledgeCommandHandler.cs.

**Resolved.** Replaced the per-domain `UserKnowledgeProgress.Where(...).ToListAsync()` (N queries) with a single up-front load: ```csharp var existingProgressByDomain = (await context.UserKnowledgeProgress .Where(p => p.UserId == request.UserId) .ToListAsync(cancellationToken)) .GroupBy(p => p.Domain) .ToDictionary(g => g.Key, g => g.First(), StringComparer.Ordinal); ``` The per-domain loop now does an in-memory `TryGetValue` against this dictionary. One query regardless of domain count. File: `SpikerSoft.Business/Domain/Activity/Commands/AggregateKnowledge/AggregateKnowledgeCommandHandler.cs`.
Sign in to join this conversation.