Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44c56c4a0a | ||
|
|
d019ed588b | ||
|
|
054ef81c63 | ||
|
|
9c353f40c6 | ||
|
|
cba16f25b9 | ||
|
|
13a8e309e7 |
7 changed files with 1707 additions and 13 deletions
|
|
@ -20,6 +20,7 @@ namespace BMA.EHR.Recruit.Data
|
||||||
modelBuilder.Entity<Models.Recruits.Recruit>().HasMany(x => x.Addresses).WithOne(x => x.Recruit).OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Models.Recruits.Recruit>().HasMany(x => x.Addresses).WithOne(x => x.Recruit).OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Models.Recruits.Recruit>().HasMany(x => x.Certificates).WithOne(x => x.Recruit).OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Models.Recruits.Recruit>().HasMany(x => x.Certificates).WithOne(x => x.Recruit).OnDelete(DeleteBehavior.Cascade);
|
||||||
modelBuilder.Entity<Models.Recruits.Recruit>().HasMany(x => x.Payments).WithOne(x => x.Recruit).OnDelete(DeleteBehavior.Cascade);
|
modelBuilder.Entity<Models.Recruits.Recruit>().HasMany(x => x.Payments).WithOne(x => x.Recruit).OnDelete(DeleteBehavior.Cascade);
|
||||||
|
modelBuilder.Entity<ScoreImport>().HasMany(x => x.Scores).WithOne(x => x.ScoreImport).HasForeignKey(x => x.ScoreImportId).OnDelete(DeleteBehavior.Cascade);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DbSet<Document> Documents { get; set; }
|
public DbSet<Document> Documents { get; set; }
|
||||||
|
|
|
||||||
1605
Migrations/20260519102256_fix relation.Designer.cs
generated
Normal file
1605
Migrations/20260519102256_fix relation.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
64
Migrations/20260519102256_fix relation.cs
Normal file
64
Migrations/20260519102256_fix relation.cs
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace BMA.EHR.Recruit.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class fixrelation : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_ScoreImports_Documents_ImportFileId",
|
||||||
|
table: "ScoreImports");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<Guid>(
|
||||||
|
name: "ImportFileId",
|
||||||
|
table: "ScoreImports",
|
||||||
|
type: "char(36)",
|
||||||
|
nullable: true,
|
||||||
|
collation: "ascii_general_ci",
|
||||||
|
oldClrType: typeof(Guid),
|
||||||
|
oldType: "char(36)")
|
||||||
|
.OldAnnotation("Relational:Collation", "ascii_general_ci");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_ScoreImports_Documents_ImportFileId",
|
||||||
|
table: "ScoreImports",
|
||||||
|
column: "ImportFileId",
|
||||||
|
principalTable: "Documents",
|
||||||
|
principalColumn: "Id");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropForeignKey(
|
||||||
|
name: "FK_ScoreImports_Documents_ImportFileId",
|
||||||
|
table: "ScoreImports");
|
||||||
|
|
||||||
|
migrationBuilder.AlterColumn<Guid>(
|
||||||
|
name: "ImportFileId",
|
||||||
|
table: "ScoreImports",
|
||||||
|
type: "char(36)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new Guid("00000000-0000-0000-0000-000000000000"),
|
||||||
|
collation: "ascii_general_ci",
|
||||||
|
oldClrType: typeof(Guid),
|
||||||
|
oldType: "char(36)",
|
||||||
|
oldNullable: true)
|
||||||
|
.OldAnnotation("Relational:Collation", "ascii_general_ci");
|
||||||
|
|
||||||
|
migrationBuilder.AddForeignKey(
|
||||||
|
name: "FK_ScoreImports_Documents_ImportFileId",
|
||||||
|
table: "ScoreImports",
|
||||||
|
column: "ImportFileId",
|
||||||
|
principalTable: "Documents",
|
||||||
|
principalColumn: "Id",
|
||||||
|
onDelete: ReferentialAction.Cascade);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1353,7 +1353,7 @@ namespace BMA.EHR.Recruit.Migrations
|
||||||
.HasColumnOrder(101)
|
.HasColumnOrder(101)
|
||||||
.HasComment("User Id ที่สร้างข้อมูล");
|
.HasComment("User Id ที่สร้างข้อมูล");
|
||||||
|
|
||||||
b.Property<Guid>("ImportFileId")
|
b.Property<Guid?>("ImportFileId")
|
||||||
.HasColumnType("char(36)");
|
.HasColumnType("char(36)");
|
||||||
|
|
||||||
b.Property<string>("LastUpdateFullName")
|
b.Property<string>("LastUpdateFullName")
|
||||||
|
|
@ -1550,9 +1550,7 @@ namespace BMA.EHR.Recruit.Migrations
|
||||||
{
|
{
|
||||||
b.HasOne("BMA.EHR.Recruit.Models.Documents.Document", "ImportFile")
|
b.HasOne("BMA.EHR.Recruit.Models.Documents.Document", "ImportFile")
|
||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("ImportFileId")
|
.HasForeignKey("ImportFileId");
|
||||||
.OnDelete(DeleteBehavior.Cascade)
|
|
||||||
.IsRequired();
|
|
||||||
|
|
||||||
b.HasOne("BMA.EHR.Recruit.Models.Recruits.RecruitImport", "RecruitImport")
|
b.HasOne("BMA.EHR.Recruit.Models.Recruits.RecruitImport", "RecruitImport")
|
||||||
.WithOne("ScoreImport")
|
.WithOne("ScoreImport")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
|
|
||||||
namespace BMA.EHR.Recruit.Models.Recruits
|
namespace BMA.EHR.Recruit.Models.Recruits
|
||||||
{
|
{
|
||||||
|
|
@ -83,6 +84,9 @@ namespace BMA.EHR.Recruit.Models.Recruits
|
||||||
[MaxLength(50), Comment("สถานะคัดกรองคุณสมบัติ")]
|
[MaxLength(50), Comment("สถานะคัดกรองคุณสมบัติ")]
|
||||||
public string ExamAttribute { get; set; } = string.Empty;
|
public string ExamAttribute { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
[ForeignKey("ScoreImport")]
|
||||||
|
public Guid ScoreImportId { get; set; }
|
||||||
|
|
||||||
public ScoreImport ScoreImport { get; set; }
|
public ScoreImport ScoreImport { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,9 @@ namespace BMA.EHR.Recruit.Models.Recruits
|
||||||
{
|
{
|
||||||
public int Year { get; set; }
|
public int Year { get; set; }
|
||||||
|
|
||||||
public Document ImportFile { get; set; } = new Document();
|
public Guid? ImportFileId { get; set; }
|
||||||
|
|
||||||
|
public Document ImportFile { get; set; }
|
||||||
|
|
||||||
public virtual List<RecruitScore> Scores { get; set; } = new List<RecruitScore>();
|
public virtual List<RecruitScore> Scores { get; set; } = new List<RecruitScore>();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -572,12 +572,21 @@ public class ImportBackgroundService : BackgroundService
|
||||||
|
|
||||||
var rec_import_id = rec_import.Id;
|
var rec_import_id = rec_import.Id;
|
||||||
var rec_import_year = rec_import.Year;
|
var rec_import_year = rec_import.Year;
|
||||||
var hasExistingScores = rec_import.ScoreImport != null && rec_import.ScoreImport.Scores != null;
|
var existingScoreImport = rec_import.ScoreImport;
|
||||||
|
|
||||||
if (hasExistingScores)
|
if (existingScoreImport != null)
|
||||||
{
|
{
|
||||||
var existingScores = rec_import.ScoreImport.Scores.ToList();
|
var existingScores = existingScoreImport.Scores?.ToList() ?? new List<RecruitScore>();
|
||||||
await _context.BulkDeleteAsync(existingScores);
|
if (existingScores.Count > 0)
|
||||||
|
await _context.BulkDeleteAsync(existingScores);
|
||||||
|
// Also delete the ScoreImport row itself (has unique index on RecruitImportId)
|
||||||
|
// Use Remove+SaveChanges instead of BulkDelete since BulkDelete fails with detached entities
|
||||||
|
var scoreImportToDelete = existingScoreImport;
|
||||||
|
_context.ChangeTracker.Clear();
|
||||||
|
var deleteStub = new ScoreImport { Id = scoreImportToDelete.Id };
|
||||||
|
_context.ScoreImports.Attach(deleteStub);
|
||||||
|
_context.ScoreImports.Remove(deleteStub);
|
||||||
|
await _context.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clear tracker to avoid stale references after BulkDelete (which bypasses EF tracking)
|
// Clear tracker to avoid stale references after BulkDelete (which bypasses EF tracking)
|
||||||
|
|
@ -596,12 +605,13 @@ public class ImportBackgroundService : BackgroundService
|
||||||
});
|
});
|
||||||
historyEntry.Property("RecruitImportId").CurrentValue = rec_import_id;
|
historyEntry.Property("RecruitImportId").CurrentValue = rec_import_id;
|
||||||
|
|
||||||
// get doc from minio
|
// get doc from minio (MinIOService already saves Document to its own context)
|
||||||
var doc = await _minioService.UploadFileAsync(new DummyFormFile(job.ImportFile));
|
var doc = await _minioService.UploadFileAsync(new DummyFormFile(job.ImportFile));
|
||||||
|
var scoreImport_id = Guid.NewGuid(); // Pre-generate Id to use as FK
|
||||||
var imported = new ScoreImport();
|
var imported = new ScoreImport();
|
||||||
|
imported.Id = scoreImport_id;
|
||||||
imported.Year = rec_import_year;
|
imported.Year = rec_import_year;
|
||||||
imported.RecruitImportId = rec_import_id;
|
imported.RecruitImportId = rec_import_id;
|
||||||
imported.ImportFile = doc;
|
|
||||||
imported.CreatedAt = DateTime.Now;
|
imported.CreatedAt = DateTime.Now;
|
||||||
imported.CreatedUserId = job.UserId ?? "";
|
imported.CreatedUserId = job.UserId ?? "";
|
||||||
imported.CreatedFullName = job.FullName ?? "System Administrator";
|
imported.CreatedFullName = job.FullName ?? "System Administrator";
|
||||||
|
|
@ -610,7 +620,8 @@ public class ImportBackgroundService : BackgroundService
|
||||||
imported.LastUpdateFullName = job.FullName ?? "System Administrator";
|
imported.LastUpdateFullName = job.FullName ?? "System Administrator";
|
||||||
imported.Scores = new List<RecruitScore>();
|
imported.Scores = new List<RecruitScore>();
|
||||||
|
|
||||||
// Save ScoreImport parent first to get its Id
|
// Save ScoreImport — set ImportFileId FK directly (explicit property, not shadow)
|
||||||
|
imported.ImportFileId = doc.Id;
|
||||||
_context.ScoreImports.Add(imported);
|
_context.ScoreImports.Add(imported);
|
||||||
await _context.SaveChangesAsync();
|
await _context.SaveChangesAsync();
|
||||||
_context.ChangeTracker.Clear();
|
_context.ChangeTracker.Clear();
|
||||||
|
|
@ -653,6 +664,7 @@ public class ImportBackgroundService : BackgroundService
|
||||||
if (string.IsNullOrEmpty(cell1)) break;
|
if (string.IsNullOrEmpty(cell1)) break;
|
||||||
|
|
||||||
var r = new RecruitScore();
|
var r = new RecruitScore();
|
||||||
|
r.Id = Guid.NewGuid();
|
||||||
r.ExamId = reader.GetValue(1)?.ToString();
|
r.ExamId = reader.GetValue(1)?.ToString();
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(r.ExamId) && recruitsDict.TryGetValue(r.ExamId, out var recruit))
|
if (!string.IsNullOrEmpty(r.ExamId) && recruitsDict.TryGetValue(r.ExamId, out var recruit))
|
||||||
|
|
@ -695,7 +707,6 @@ public class ImportBackgroundService : BackgroundService
|
||||||
r.LastUpdatedAt = DateTime.Now;
|
r.LastUpdatedAt = DateTime.Now;
|
||||||
r.LastUpdateUserId = job.UserId ?? "";
|
r.LastUpdateUserId = job.UserId ?? "";
|
||||||
r.LastUpdateFullName = job.FullName ?? "System Administrator";
|
r.LastUpdateFullName = job.FullName ?? "System Administrator";
|
||||||
r.ScoreImport = imported;
|
|
||||||
|
|
||||||
batchScores.Add(r);
|
batchScores.Add(r);
|
||||||
}
|
}
|
||||||
|
|
@ -705,6 +716,11 @@ public class ImportBackgroundService : BackgroundService
|
||||||
|
|
||||||
if (batchCount >= batchSize)
|
if (batchCount >= batchSize)
|
||||||
{
|
{
|
||||||
|
// Set ScoreImportId FK for all scores in batch
|
||||||
|
foreach (var score in batchScores)
|
||||||
|
{
|
||||||
|
score.ScoreImportId = scoreImport_id;
|
||||||
|
}
|
||||||
await _context.BulkInsertAsync(batchScores);
|
await _context.BulkInsertAsync(batchScores);
|
||||||
batchScores.Clear();
|
batchScores.Clear();
|
||||||
batchCount = 0;
|
batchCount = 0;
|
||||||
|
|
@ -715,6 +731,10 @@ public class ImportBackgroundService : BackgroundService
|
||||||
// Process remaining records
|
// Process remaining records
|
||||||
if (batchScores.Count > 0)
|
if (batchScores.Count > 0)
|
||||||
{
|
{
|
||||||
|
foreach (var score in batchScores)
|
||||||
|
{
|
||||||
|
score.ScoreImportId = scoreImport_id;
|
||||||
|
}
|
||||||
await _context.BulkInsertAsync(batchScores);
|
await _context.BulkInsertAsync(batchScores);
|
||||||
}
|
}
|
||||||
} while (reader.NextResult());
|
} while (reader.NextResult());
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue