Fix duplicate key violation in CvMatchResults by updating unique constraint to 3 columns

The Results table had a unique constraint on (CvDocumentId, JobDocumentId) but the code
expects uniqueness on (CvDocumentId, JobDocumentId, Language). When matching the same CV
against the same job in different languages, this caused duplicate key violations.

Changes:
- Updated CvMatcherDbContext to define 3-column unique index including Language
- Generated proper EF Core migration to drop 2-column index and create 3-column index
- Updated ModelSnapshot to reflect new 3-column index definition
- Added exception handling in SaveMatchAsync to gracefully handle any race conditions
  where duplicate key violations could occur between the existence check and insert

The migration will be automatically applied on container startup via db.Database.Migrate().

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-06-01 16:13:58 +03:00
parent 8b143dcb12
commit 87de7d3f77
5 changed files with 49 additions and 27 deletions
@@ -48,18 +48,27 @@ public sealed class EfMatcherRepository : IMatcherRepository
if (exists) return;
_db.CvMatchResults.Add(new CvMatchResultEntity
try
{
Id = Guid.NewGuid().ToString("N"),
CvDocumentId = cvDocumentId,
JobDocumentId = jobDocumentId,
Language = language,
ResultJson = JsonSerializer.Serialize(response, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Score = response.Score,
CreatedAt = DateTime.UtcNow
});
_db.CvMatchResults.Add(new CvMatchResultEntity
{
Id = Guid.NewGuid().ToString("N"),
CvDocumentId = cvDocumentId,
JobDocumentId = jobDocumentId,
Language = language,
ResultJson = JsonSerializer.Serialize(response, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
Score = response.Score,
CreatedAt = DateTime.UtcNow
});
await _db.SaveChangesAsync(ct);
await _db.SaveChangesAsync(ct);
}
catch (DbUpdateException ex) when (ex.InnerException?.Message.Contains("IX_Results_CvDocumentId_JobDocumentId_Language") == true
|| ex.InnerException?.Message.Contains("unique") == true)
{
// Duplicate key violation: record was inserted between the AnyAsync check and SaveChangesAsync.
// This is safe to ignore — the match result already exists in the database.
}
}
public async Task<string?> GetChatCompletionAsync(string cacheKey, CancellationToken ct)