From 96b9489d49bcc4a85e5ef6810f61483a97db9e9c Mon Sep 17 00:00:00 2001 From: cesnimda Date: Sun, 5 Jul 2026 21:21:36 +0200 Subject: [PATCH] perf(gmail): batch the duplicate-message check in CreateSuggestedJob MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CreateSuggestedJob ran one AnyAsync per message in the thread to decide imported-vs-skip — an N+1 that scales with thread length. Replace it with a single query that loads the already-imported ExternalMessageIds for the job, then check in memory (identical skip/import behaviour), mirroring the batched pattern RelinkThread already uses. Build clean; backend suite 135/135 green. Co-Authored-By: Claude Opus 4.8 --- JobTrackerApi/Controllers/GmailController.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/JobTrackerApi/Controllers/GmailController.cs b/JobTrackerApi/Controllers/GmailController.cs index 7383ba3..57ac4f9 100644 --- a/JobTrackerApi/Controllers/GmailController.cs +++ b/JobTrackerApi/Controllers/GmailController.cs @@ -641,12 +641,17 @@ public sealed class GmailController : ControllerBase var threadMessages = await _gmail.ListThreadMessagesAsync(ownerUserId, request.ThreadId.Trim(), cancellationToken); var distinctMessageIds = threadMessages.Select(message => message.Id).Where(static id => !string.IsNullOrWhiteSpace(id)).Distinct(StringComparer.Ordinal).ToList(); + // Batch the "already imported?" check with a single query instead of one + // AnyAsync per message (N+1), mirroring RelinkThread below. + var existingMessageIds = await _db.Correspondences + .Where(message => message.JobApplicationId == job.Id && message.ExternalMessageId != null && distinctMessageIds.Contains(message.ExternalMessageId)) + .Select(message => message.ExternalMessageId!) + .ToListAsync(cancellationToken); var imported = 0; var skipped = 0; foreach (var messageId in distinctMessageIds) { - var existing = await _db.Correspondences.AnyAsync(message => message.JobApplicationId == job.Id && message.ExternalMessageId == messageId, cancellationToken); - if (existing) + if (existingMessageIds.Contains(messageId, StringComparer.Ordinal)) { skipped++; continue;