Staging to Production #51
@@ -36,7 +36,7 @@ public sealed class CvMatcherDbContext : DbContext
|
|||||||
entity.Property(x => x.JobDocumentId).HasMaxLength(64).IsRequired();
|
entity.Property(x => x.JobDocumentId).HasMaxLength(64).IsRequired();
|
||||||
entity.Property(x => x.ResultJson).IsRequired();
|
entity.Property(x => x.ResultJson).IsRequired();
|
||||||
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
||||||
entity.HasIndex(x => new { x.CvDocumentId, x.JobDocumentId }).IsUnique();
|
entity.HasIndex(x => new { x.CvDocumentId, x.JobDocumentId, x.Language }).IsUnique();
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity<CvMatcherChatCacheEntity>(entity =>
|
modelBuilder.Entity<CvMatcherChatCacheEntity>(entity =>
|
||||||
|
|||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using CvMatcher.Data;
|
using CvMatcher.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -12,7 +12,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
|||||||
namespace CvMatcher.Data.Migrations
|
namespace CvMatcher.Data.Migrations
|
||||||
{
|
{
|
||||||
[DbContext(typeof(CvMatcherDbContext))]
|
[DbContext(typeof(CvMatcherDbContext))]
|
||||||
[Migration("20260529111000_UpdateResultsUniqueConstraintToIncludeLanguage")]
|
[Migration("20260601131043_UpdateResultsUniqueConstraintToIncludeLanguage")]
|
||||||
partial class UpdateResultsUniqueConstraintToIncludeLanguage
|
partial class UpdateResultsUniqueConstraintToIncludeLanguage
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -80,7 +80,7 @@ namespace CvMatcher.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Language")
|
b.Property<string>("Language")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
b.Property<string>("ResultJson")
|
b.Property<string>("ResultJson")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
+23
-10
@@ -1,5 +1,4 @@
|
|||||||
using Microsoft.EntityFrameworkCore.Migrations;
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
using CvMatcher.Data;
|
|
||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
@@ -11,18 +10,23 @@ namespace CvMatcher.Data.Migrations
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
protected override void Up(MigrationBuilder migrationBuilder)
|
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(
|
migrationBuilder.DropIndex(
|
||||||
name: "IX_Results_CvDocumentId_JobDocumentId",
|
name: "IX_Results_CvDocumentId_JobDocumentId",
|
||||||
schema: MigrationConstants.SchemaName,
|
schema: "cvMatcher",
|
||||||
table: "Results");
|
table: "Results");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Language",
|
||||||
|
schema: "cvMatcher",
|
||||||
|
table: "Results",
|
||||||
|
type: "nvarchar(450)",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "nvarchar(max)");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
|
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
|
||||||
schema: MigrationConstants.SchemaName,
|
schema: "cvMatcher",
|
||||||
table: "Results",
|
table: "Results",
|
||||||
columns: new[] { "CvDocumentId", "JobDocumentId", "Language" },
|
columns: new[] { "CvDocumentId", "JobDocumentId", "Language" },
|
||||||
unique: true);
|
unique: true);
|
||||||
@@ -33,12 +37,21 @@ namespace CvMatcher.Data.Migrations
|
|||||||
{
|
{
|
||||||
migrationBuilder.DropIndex(
|
migrationBuilder.DropIndex(
|
||||||
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
|
name: "IX_Results_CvDocumentId_JobDocumentId_Language",
|
||||||
schema: MigrationConstants.SchemaName,
|
schema: "cvMatcher",
|
||||||
table: "Results");
|
table: "Results");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<string>(
|
||||||
|
name: "Language",
|
||||||
|
schema: "cvMatcher",
|
||||||
|
table: "Results",
|
||||||
|
type: "nvarchar(max)",
|
||||||
|
nullable: false,
|
||||||
|
oldClrType: typeof(string),
|
||||||
|
oldType: "nvarchar(450)");
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_Results_CvDocumentId_JobDocumentId",
|
name: "IX_Results_CvDocumentId_JobDocumentId",
|
||||||
schema: MigrationConstants.SchemaName,
|
schema: "cvMatcher",
|
||||||
table: "Results",
|
table: "Results",
|
||||||
columns: new[] { "CvDocumentId", "JobDocumentId" },
|
columns: new[] { "CvDocumentId", "JobDocumentId" },
|
||||||
unique: true);
|
unique: true);
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
// <auto-generated />
|
// <auto-generated />
|
||||||
using System;
|
using System;
|
||||||
using CvMatcher.Data;
|
using CvMatcher.Data;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
@@ -77,7 +77,7 @@ namespace CvMatcher.Data.Migrations
|
|||||||
|
|
||||||
b.Property<string>("Language")
|
b.Property<string>("Language")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("nvarchar(max)");
|
.HasColumnType("nvarchar(450)");
|
||||||
|
|
||||||
b.Property<string>("ResultJson")
|
b.Property<string>("ResultJson")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
@@ -88,7 +88,7 @@ namespace CvMatcher.Data.Migrations
|
|||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.HasIndex("CvDocumentId", "JobDocumentId")
|
b.HasIndex("CvDocumentId", "JobDocumentId", "Language")
|
||||||
.IsUnique();
|
.IsUnique();
|
||||||
|
|
||||||
b.ToTable("Results", "cvMatcher");
|
b.ToTable("Results", "cvMatcher");
|
||||||
|
|||||||
@@ -48,18 +48,27 @@ public sealed class EfMatcherRepository : IMatcherRepository
|
|||||||
|
|
||||||
if (exists) return;
|
if (exists) return;
|
||||||
|
|
||||||
_db.CvMatchResults.Add(new CvMatchResultEntity
|
try
|
||||||
{
|
{
|
||||||
Id = Guid.NewGuid().ToString("N"),
|
_db.CvMatchResults.Add(new CvMatchResultEntity
|
||||||
CvDocumentId = cvDocumentId,
|
{
|
||||||
JobDocumentId = jobDocumentId,
|
Id = Guid.NewGuid().ToString("N"),
|
||||||
Language = language,
|
CvDocumentId = cvDocumentId,
|
||||||
ResultJson = JsonSerializer.Serialize(response, new JsonSerializerOptions(JsonSerializerDefaults.Web)),
|
JobDocumentId = jobDocumentId,
|
||||||
Score = response.Score,
|
Language = language,
|
||||||
CreatedAt = DateTime.UtcNow
|
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)
|
public async Task<string?> GetChatCompletionAsync(string cacheKey, CancellationToken ct)
|
||||||
|
|||||||
Reference in New Issue
Block a user