87de7d3f77
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>
65 lines
2.8 KiB
C#
65 lines
2.8 KiB
C#
using CvMatcher.Data.Entities;
|
|
using Microsoft.EntityFrameworkCore;
|
|
|
|
namespace CvMatcher.Data;
|
|
|
|
public sealed class CvMatcherDbContext : DbContext
|
|
{
|
|
public const string SchemaName = MigrationConstants.SchemaName;
|
|
public const string MigrationTableName = MigrationConstants.MigrationTableName;
|
|
|
|
public CvMatcherDbContext(DbContextOptions<CvMatcherDbContext> options) : base(options)
|
|
{
|
|
}
|
|
|
|
public DbSet<CvMatchResultEntity> CvMatchResults => Set<CvMatchResultEntity>();
|
|
public DbSet<CvMatcherChatCacheEntity> CvMatcherChatCache => Set<CvMatcherChatCacheEntity>();
|
|
public DbSet<AiPromptEntity> AiPrompts => Set<AiPromptEntity>();
|
|
|
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
|
{
|
|
base.OnConfiguring(optionsBuilder);
|
|
// Configure migration history table to use schema-qualified name: [cvMatcher].[_Migrations]
|
|
optionsBuilder.UseSqlServer(x => x.MigrationsHistoryTable(MigrationTableName, SchemaName));
|
|
}
|
|
|
|
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
|
{
|
|
modelBuilder.HasDefaultSchema(SchemaName);
|
|
|
|
modelBuilder.Entity<CvMatchResultEntity>(entity =>
|
|
{
|
|
entity.ToTable("Results");
|
|
entity.HasKey(x => x.Id);
|
|
entity.Property(x => x.Id).HasMaxLength(64);
|
|
entity.Property(x => x.CvDocumentId).HasMaxLength(64).IsRequired();
|
|
entity.Property(x => x.JobDocumentId).HasMaxLength(64).IsRequired();
|
|
entity.Property(x => x.ResultJson).IsRequired();
|
|
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
|
entity.HasIndex(x => new { x.CvDocumentId, x.JobDocumentId, x.Language }).IsUnique();
|
|
});
|
|
|
|
modelBuilder.Entity<CvMatcherChatCacheEntity>(entity =>
|
|
{
|
|
entity.ToTable("ChatCache");
|
|
entity.HasKey(x => x.CacheKey);
|
|
entity.Property(x => x.CacheKey).HasMaxLength(64);
|
|
entity.Property(x => x.Model).HasMaxLength(120).IsRequired();
|
|
entity.Property(x => x.Temperature).HasColumnType("decimal(4,2)");
|
|
entity.Property(x => x.ResponseText).IsRequired();
|
|
entity.Property(x => x.CreatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
|
});
|
|
|
|
modelBuilder.Entity<AiPromptEntity>(entity =>
|
|
{
|
|
entity.ToTable("AiPrompts");
|
|
entity.HasKey(x => new { x.Key, x.Language });
|
|
entity.Property(x => x.Key).HasMaxLength(128);
|
|
entity.Property(x => x.Language).HasMaxLength(8);
|
|
entity.Property(x => x.Value).IsRequired();
|
|
entity.Property(x => x.Description).HasMaxLength(500).HasDefaultValue(string.Empty);
|
|
entity.Property(x => x.UpdatedAt).HasDefaultValueSql("SYSUTCDATETIME()");
|
|
});
|
|
}
|
|
}
|