From 45947053c81cbd605b5ad8f649ff5571fdaf4ee1 Mon Sep 17 00:00:00 2001 From: gamer147 Date: Sat, 27 Jun 2026 11:21:32 -0400 Subject: [PATCH] fix(guild): single-pass bucket filter in GuildRepository.SearchAsync Replace double-correlated COUNT subqueries in the member-bucket switch with a project-filter-project pattern that computes the count once per row. Add SearchAsync_bucket_2_matches_guilds_with_11_to_25_members to cover the previously untested medium-bucket range. Co-Authored-By: Claude Sonnet 4.6 --- .../Repositories/Guild/GuildRepository.cs | 18 ++++++------- .../Guild/GuildRepositoryTests.cs | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/SVSim.Database/Repositories/Guild/GuildRepository.cs b/SVSim.Database/Repositories/Guild/GuildRepository.cs index 6f094381..d4f7ee8f 100644 --- a/SVSim.Database/Repositories/Guild/GuildRepository.cs +++ b/SVSim.Database/Repositories/Guild/GuildRepository.cs @@ -53,18 +53,18 @@ public sealed class GuildRepository : IGuildRepository if (joinCondition != 0) q = q.Where(g => (int)g.JoinCondition == joinCondition); - if (memberBucket != 0) + if (memberBucket is 1 or 2 or 3) { - q = memberBucket switch + var (lo, hi) = memberBucket switch { - 1 => q.Where(g => _db.GuildMembers.Count(m => m.GuildId == g.GuildId) >= 1 - && _db.GuildMembers.Count(m => m.GuildId == g.GuildId) <= 10), - 2 => q.Where(g => _db.GuildMembers.Count(m => m.GuildId == g.GuildId) >= 11 - && _db.GuildMembers.Count(m => m.GuildId == g.GuildId) <= 25), - 3 => q.Where(g => _db.GuildMembers.Count(m => m.GuildId == g.GuildId) >= 26 - && _db.GuildMembers.Count(m => m.GuildId == g.GuildId) <= maxMemberCap), - _ => q, + 1 => (1, 10), + 2 => (11, 25), + _ => (26, maxMemberCap), }; + q = q + .Select(g => new { Guild = g, MemberCount = _db.GuildMembers.Count(m => m.GuildId == g.GuildId) }) + .Where(x => x.MemberCount >= lo && x.MemberCount <= hi) + .Select(x => x.Guild); } return await q.Take(resultCap).ToListAsync(ct); diff --git a/SVSim.UnitTests/Repositories/Guild/GuildRepositoryTests.cs b/SVSim.UnitTests/Repositories/Guild/GuildRepositoryTests.cs index fd2ee3a1..b3633594 100644 --- a/SVSim.UnitTests/Repositories/Guild/GuildRepositoryTests.cs +++ b/SVSim.UnitTests/Repositories/Guild/GuildRepositoryTests.cs @@ -68,4 +68,29 @@ public class GuildRepositoryTests var small = await repo.SearchAsync("", 0, 0, 1, 30, 50); Assert.That(small.Select(x => x.GuildId), Has.No.Member(bigId)); } + + [Test] + public async Task SearchAsync_bucket_2_matches_guilds_with_11_to_25_members() + { + await using var db = Db(); + var repo = new GuildRepository(db); + + // Guild with 15 members — should appear in bucket 2 + var midId = 10; + await repo.AddAsync(new GuildEntity { GuildId = midId, Name = "Mid", Activity = GuildActivity.All, JoinCondition = GuildJoinCondition.Free, CreatedAt = DateTime.UtcNow }); + for (int i = 0; i < 15; i++) + db.GuildMembers.Add(new GuildMember { GuildId = midId, ViewerId = 2000 + i, Role = GuildRole.Regular, JoinedAt = DateTime.UtcNow }); + + // Guild with 5 members — should appear in bucket 1 only + var smallId = 11; + await repo.AddAsync(new GuildEntity { GuildId = smallId, Name = "Small", Activity = GuildActivity.All, JoinCondition = GuildJoinCondition.Free, CreatedAt = DateTime.UtcNow }); + for (int i = 0; i < 5; i++) + db.GuildMembers.Add(new GuildMember { GuildId = smallId, ViewerId = 3000 + i, Role = GuildRole.Regular, JoinedAt = DateTime.UtcNow }); + + await db.SaveChangesAsync(); + + var bucket2 = await repo.SearchAsync("", 0, 0, 2, 30, 50); + Assert.That(bucket2.Select(x => x.GuildId), Has.Member(midId), "15-member guild should be in bucket 2"); + Assert.That(bucket2.Select(x => x.GuildId), Has.No.Member(smallId), "5-member guild must not appear in bucket 2"); + } }