From 06bec9b0ae4ecd1c75cc9eb66cd1f7ffa27de205 Mon Sep 17 00:00:00 2001 From: claude Date: Fri, 29 May 2026 14:14:00 +0300 Subject: [PATCH] fix(results-schema): include Language in unique constraint for CvMatchResults The unique constraint on cvMatcher.Results was defined as (CvDocumentId, JobDocumentId) but the code checks for (CvDocumentId, JobDocumentId, Language). This mismatch caused duplicate key violations when matching the same CV+Job in different languages. Update the constraint to (CvDocumentId, JobDocumentId, Language) to allow different languages for the same CV+Job pair while preventing true duplicates. Resolves: Duplicate key constraint violations on concurrent/repeated match requests --- ...queConstraintToIncludeLanguage.Designer.cs | 130 ++++++++++++++++++ ...esultsUniqueConstraintToIncludeLanguage.cs | 46 +++++++ .../CvMatcherDbContextModelSnapshot.cs | 2 +- 3 files changed, 177 insertions(+), 1 deletion(-) create mode 100644 Apis/cv-matcher-data/Migrations/20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage.Designer.cs create mode 100644 Apis/cv-matcher-data/Migrations/20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage.cs diff --git a/Apis/cv-matcher-data/Migrations/20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage.Designer.cs b/Apis/cv-matcher-data/Migrations/20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage.Designer.cs new file mode 100644 index 0000000..560b81f --- /dev/null +++ b/Apis/cv-matcher-data/Migrations/20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage.Designer.cs @@ -0,0 +1,130 @@ +// +using System; +using CvMatcher.Data; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + +#nullable disable + +namespace CvMatcher.Data.Migrations +{ + [DbContext(typeof(CvMatcherDbContext))] + [Migration("20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage")] + partial class UpdateResultsUniqueConstraintToIncludeLanguage + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasDefaultSchema("cvMatcher") + .HasAnnotation("ProductVersion", "10.0.7") + .HasAnnotation("Relational:MaxIdentifierLength", 128); + + SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder); + + modelBuilder.Entity("CvMatcher.Data.Entities.AiPromptEntity", b => + { + b.Property("Key") + .HasMaxLength(128) + .HasColumnType("nvarchar(128)"); + + b.Property("Language") + .HasMaxLength(8) + .HasColumnType("nvarchar(8)"); + + b.Property("Description") + .IsRequired() + .ValueGeneratedOnAdd() + .HasMaxLength(500) + .HasColumnType("nvarchar(500)") + .HasDefaultValue(""); + + b.Property("UpdatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Value") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.HasKey("Key", "Language"); + + b.ToTable("AiPrompts", "cvMatcher"); + }); + + modelBuilder.Entity("CvMatcher.Data.Entities.CvMatchResultEntity", b => + { + b.Property("Id") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("CvDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("JobDocumentId") + .IsRequired() + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("Language") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ResultJson") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Score") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CvDocumentId", "JobDocumentId", "Language") + .IsUnique(); + + b.ToTable("Results", "cvMatcher"); + }); + + modelBuilder.Entity("CvMatcher.Data.Entities.CvMatcherChatCacheEntity", b => + { + b.Property("CacheKey") + .HasMaxLength(64) + .HasColumnType("nvarchar(64)"); + + b.Property("CreatedAt") + .ValueGeneratedOnAdd() + .HasColumnType("datetime2") + .HasDefaultValueSql("SYSUTCDATETIME()"); + + b.Property("Model") + .IsRequired() + .HasMaxLength(120) + .HasColumnType("nvarchar(120)"); + + b.Property("ResponseText") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Temperature") + .HasColumnType("decimal(4,2)"); + + b.HasKey("CacheKey"); + + b.ToTable("ChatCache", "cvMatcher"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/Apis/cv-matcher-data/Migrations/20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage.cs b/Apis/cv-matcher-data/Migrations/20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage.cs new file mode 100644 index 0000000..550b5bd --- /dev/null +++ b/Apis/cv-matcher-data/Migrations/20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage.cs @@ -0,0 +1,46 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace CvMatcher.Data.Migrations +{ + /// + public partial class UpdateResultsUniqueConstraintToIncludeLanguage : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + // The Language column was added in migration 20260524140335, but the unique constraint + // was never updated from (CvDocumentId, JobDocumentId) to include Language. + // This caused duplicate key violations when matching the same CV+Job in different languages. + + migrationBuilder.DropIndex( + name: "IX_Results_CvDocumentId_JobDocumentId", + schema: MigrationConstants.SchemaName, + table: "Results"); + + migrationBuilder.CreateIndex( + name: "IX_Results_CvDocumentId_JobDocumentId_Language", + schema: MigrationConstants.SchemaName, + table: "Results", + columns: new[] { "CvDocumentId", "JobDocumentId", "Language" }, + unique: true); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropIndex( + name: "IX_Results_CvDocumentId_JobDocumentId_Language", + schema: MigrationConstants.SchemaName, + table: "Results"); + + migrationBuilder.CreateIndex( + name: "IX_Results_CvDocumentId_JobDocumentId", + schema: MigrationConstants.SchemaName, + table: "Results", + columns: new[] { "CvDocumentId", "JobDocumentId" }, + unique: true); + } + } +} diff --git a/Apis/cv-matcher-data/Migrations/CvMatcherDbContextModelSnapshot.cs b/Apis/cv-matcher-data/Migrations/CvMatcherDbContextModelSnapshot.cs index 33937c3..5e2fe70 100644 --- a/Apis/cv-matcher-data/Migrations/CvMatcherDbContextModelSnapshot.cs +++ b/Apis/cv-matcher-data/Migrations/CvMatcherDbContextModelSnapshot.cs @@ -88,7 +88,7 @@ namespace CvMatcher.Data.Migrations b.HasKey("Id"); - b.HasIndex("CvDocumentId", "JobDocumentId") + b.HasIndex("CvDocumentId", "JobDocumentId", "Language") .IsUnique(); b.ToTable("Results", "cvMatcher");