SpikerSoft.Business/Domain/Activity/Queries/GetChildKnowledgeProgress/GetChildKnowledgeProgressQueryHandler.cs (~16) -- UserProfiles.ToListAsync() to find one user
SpikerSoft.Business/Domain/Activity/Queries/CompareKnowledgeProgress/CompareKnowledgeProgressQueryHandler.cs (~13) -- same
SpikerSoft.Business/Domain/Activity/Queries/GetActivityCoverage/GetActivityCoverageQueryHandler.cs (~13-16) -- UserActivities.ToListAsync() then in-memory filter
Problem: Currently fine for demo data, but at any meaningful user count this becomes O(N) per request and a memory pressure source.
Fix: Push filters into the LINQ query, use Where(...).FirstOrDefaultAsync() or Where(...).ToListAsync() after filtering, not before.
Acceptance criteria:
All three handlers query only the relevant rows
EF Core query plans verified (logged SQL)
No behavior regression in tests
**Severity:** High (perf/scalability)
**Files:**
- `SpikerSoft.Business/Domain/Activity/Queries/GetChildKnowledgeProgress/GetChildKnowledgeProgressQueryHandler.cs` (~16) -- `UserProfiles.ToListAsync()` to find one user
- `SpikerSoft.Business/Domain/Activity/Queries/CompareKnowledgeProgress/CompareKnowledgeProgressQueryHandler.cs` (~13) -- same
- `SpikerSoft.Business/Domain/Activity/Queries/GetActivityCoverage/GetActivityCoverageQueryHandler.cs` (~13-16) -- `UserActivities.ToListAsync()` then in-memory filter
**Problem:** Currently fine for demo data, but at any meaningful user count this becomes O(N) per request and a memory pressure source.
**Fix:** Push filters into the LINQ query, use `Where(...).FirstOrDefaultAsync()` or `Where(...).ToListAsync()` after filtering, not before.
**Acceptance criteria:**
- [ ] All three handlers query only the relevant rows
- [ ] EF Core query plans verified (logged SQL)
- [ ] No behavior regression in tests
All three handlers now push filters into the LINQ query rather than loading full tables:
GetChildKnowledgeProgressQueryHandler -- replaced UserProfiles.ToListAsync() + in-memory FirstOrDefault with a single FirstOrDefaultAsync(p => p.KeycloakUserId == request.ChildUserId)
CompareKnowledgeProgressQueryHandler -- replaced full-table scan with Where(p => request.UserIds.Contains(p.KeycloakUserId)).ToListAsync(), then dictionary lookup for both auth gate and display-name lookup
GetActivityCoverageQueryHandler -- pushes the optional userId filter into the IQueryable before materialising
**Resolved.**
All three handlers now push filters into the LINQ query rather than loading full tables:
- `GetChildKnowledgeProgressQueryHandler` -- replaced `UserProfiles.ToListAsync()` + in-memory `FirstOrDefault` with a single `FirstOrDefaultAsync(p => p.KeycloakUserId == request.ChildUserId)`
- `CompareKnowledgeProgressQueryHandler` -- replaced full-table scan with `Where(p => request.UserIds.Contains(p.KeycloakUserId)).ToListAsync()`, then dictionary lookup for both auth gate and display-name lookup
- `GetActivityCoverageQueryHandler` -- pushes the optional `userId` filter into the `IQueryable` before materialising
Files changed:
- `SpikerSoft.Business/Domain/Activity/Queries/GetChildKnowledgeProgress/GetChildKnowledgeProgressQueryHandler.cs`
- `SpikerSoft.Business/Domain/Activity/Queries/CompareKnowledgeProgress/CompareKnowledgeProgressQueryHandler.cs`
- `SpikerSoft.Business/Domain/Activity/Queries/GetActivityCoverage/GetActivityCoverageQueryHandler.cs`
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Severity: High (perf/scalability)
Files:
SpikerSoft.Business/Domain/Activity/Queries/GetChildKnowledgeProgress/GetChildKnowledgeProgressQueryHandler.cs(~16) --UserProfiles.ToListAsync()to find one userSpikerSoft.Business/Domain/Activity/Queries/CompareKnowledgeProgress/CompareKnowledgeProgressQueryHandler.cs(~13) -- sameSpikerSoft.Business/Domain/Activity/Queries/GetActivityCoverage/GetActivityCoverageQueryHandler.cs(~13-16) --UserActivities.ToListAsync()then in-memory filterProblem: Currently fine for demo data, but at any meaningful user count this becomes O(N) per request and a memory pressure source.
Fix: Push filters into the LINQ query, use
Where(...).FirstOrDefaultAsync()orWhere(...).ToListAsync()after filtering, not before.Acceptance criteria:
Resolved.
All three handlers now push filters into the LINQ query rather than loading full tables:
GetChildKnowledgeProgressQueryHandler-- replacedUserProfiles.ToListAsync()+ in-memoryFirstOrDefaultwith a singleFirstOrDefaultAsync(p => p.KeycloakUserId == request.ChildUserId)CompareKnowledgeProgressQueryHandler-- replaced full-table scan withWhere(p => request.UserIds.Contains(p.KeycloakUserId)).ToListAsync(), then dictionary lookup for both auth gate and display-name lookupGetActivityCoverageQueryHandler-- pushes the optionaluserIdfilter into theIQueryablebefore materialisingFiles changed:
SpikerSoft.Business/Domain/Activity/Queries/GetChildKnowledgeProgress/GetChildKnowledgeProgressQueryHandler.csSpikerSoft.Business/Domain/Activity/Queries/CompareKnowledgeProgress/CompareKnowledgeProgressQueryHandler.csSpikerSoft.Business/Domain/Activity/Queries/GetActivityCoverage/GetActivityCoverageQueryHandler.cs