From cf98121993aacc42f5794f597417e671d91e36a3 Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 18 May 2026 13:57:48 +0700 Subject: [PATCH 1/4] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B0=E0=B8=9A=E0=B8=9A?= =?UTF-8?q?=E0=B9=81=E0=B8=88=E0=B9=89=E0=B8=87=20Error=20#2497?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/ImportBackgroundService.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Services/ImportBackgroundService.cs b/Services/ImportBackgroundService.cs index 0d3de83..6143171 100644 --- a/Services/ImportBackgroundService.cs +++ b/Services/ImportBackgroundService.cs @@ -598,8 +598,12 @@ public class ImportBackgroundService : BackgroundService // Save ScoreImport parent first to get its Id rec_import.ScoreImport = imported; await _context.SaveChangesAsync(); + var scoreImportId = imported.Id; _context.ChangeTracker.Clear(); + // Re-attach ScoreImport reference to avoid FK issues during bulk insert + var importRef = _context.Attach(new ScoreImport { Id = scoreImportId }).Entity; + // preload recruits (lightweight - only ExamId) var recruitsDict = await _context.Recruits .Where(x => x.RecruitImport.Id == rec_import.Id) @@ -669,7 +673,7 @@ public class ImportBackgroundService : BackgroundService r.LastUpdatedAt = DateTime.Now; r.LastUpdateUserId = job.UserId ?? ""; r.LastUpdateFullName = job.FullName ?? "System Administrator"; - r.ScoreImport = imported; + r.ScoreImport = importRef; batchScores.Add(r); } From e33448508e5882380b4482d4fbe0eac66ce50a0a Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 18 May 2026 14:45:27 +0700 Subject: [PATCH 2/4] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B0=E0=B8=9A=E0=B8=9A?= =?UTF-8?q?=E0=B9=81=E0=B8=88=E0=B9=89=E0=B8=87=20Error=20#2497?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/ImportBackgroundService.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/Services/ImportBackgroundService.cs b/Services/ImportBackgroundService.cs index 6143171..2a0d7f2 100644 --- a/Services/ImportBackgroundService.cs +++ b/Services/ImportBackgroundService.cs @@ -635,6 +635,7 @@ public class ImportBackgroundService : BackgroundService if (!string.IsNullOrEmpty(r.ExamId) && recruitsDict.TryGetValue(r.ExamId, out var recruit)) { + r.Id = Guid.NewGuid(); // Generate unique ID for each record r.CitizenId = workSheet?.Cells[row, 3]?.GetValue()?.Trim(); r.FullA = 200; r.SumA = string.IsNullOrWhiteSpace(workSheet?.Cells[row, 5]?.GetValue()) ? 0.00 : Math.Round(workSheet.Cells[row, 5].GetValue(), 2); From 4ea7c0010b0e65a44d8dd4477767fc354d3a0c9b Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 18 May 2026 17:48:51 +0700 Subject: [PATCH 3/4] =?UTF-8?q?fix=20=E0=B8=A3=E0=B8=B0=E0=B8=9A=E0=B8=9A?= =?UTF-8?q?=E0=B9=81=E0=B8=88=E0=B9=89=E0=B8=87=20Error=20#2497?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/ImportBackgroundService.cs | 50 +++++++++++++++++++++++++++-- 1 file changed, 47 insertions(+), 3 deletions(-) diff --git a/Services/ImportBackgroundService.cs b/Services/ImportBackgroundService.cs index 2a0d7f2..7b56e9c 100644 --- a/Services/ImportBackgroundService.cs +++ b/Services/ImportBackgroundService.cs @@ -566,9 +566,48 @@ public class ImportBackgroundService : BackgroundService if (rec_import.ScoreImport != null && rec_import.ScoreImport.Scores != null) { + // Store old ScoreImport ID and MinIO document ID for cleanup + var oldScoreImportId = rec_import.ScoreImport.Id; + var oldDocId = rec_import.ScoreImport.ImportFile?.Id; + + // Delete old scores first await _context.BulkDeleteAsync(rec_import.ScoreImport.Scores.ToList()); + + // Delete the old ScoreImport entity to prevent duplicate FK constraint + await _context.BulkDeleteAsync(new List { rec_import.ScoreImport }); + + // TODO: Implement retention policy for old MinIO documents + // For now, keep old files to allow recovery and avoid immediate deletion + // if (oldDocId.HasValue && oldDocId.Value != Guid.Empty) + // { + // try { await _minioService.DeleteFileAsync(oldDocId.Value); } catch { } + // } + + // Clear ChangeTracker after bulk operations + _context.ChangeTracker.Clear(); } + // Reload to get fresh entity state after clearing + rec_import = await _context.RecruitImports.AsQueryable() + .Include(x => x.ImportHostories) + .FirstOrDefaultAsync(x => x.Id == job.RecruitImportId) + ?? throw new Exception("RecruitImport not found after reload"); + + // get doc from minio + var doc = await _minioService.UploadFileAsync(new DummyFormFile(job.ImportFile)); + var docId = doc.Id; // Store the ID before clearing tracker + + // Detach document entity to prevent duplicate INSERT on next SaveChangesAsync + _context.Entry(doc).State = EntityState.Detached; + _context.ChangeTracker.Clear(); + + // Reload RecruitImport after clearing tracker to get fresh entity + rec_import = await _context.RecruitImports.AsQueryable() + .Include(x => x.ImportHostories) + .FirstOrDefaultAsync(x => x.Id == job.RecruitImportId) + ?? throw new Exception("RecruitImport not found after MinIO upload"); + + // Add import history rec_import.ImportHostories.Add(new RecruitImportHistory { Description = "นำเข้าข้อมูลผลคะแนนสอบ", @@ -580,12 +619,14 @@ public class ImportBackgroundService : BackgroundService LastUpdateFullName = job.FullName ?? "System Administrator", }); - // get doc from minio - var doc = await _minioService.UploadFileAsync(new DummyFormFile(job.ImportFile)); + // Load Document entity fresh from database to avoid tracking conflicts + var freshDoc = await _context.Documents.FindAsync(docId) + ?? throw new Exception("Failed to retrieve uploaded document. Please contact support."); + var imported = new ScoreImport { Year = rec_import.Year, - ImportFile = doc, + ImportFile = freshDoc, CreatedAt = DateTime.Now, CreatedUserId = job.UserId ?? "", CreatedFullName = job.FullName ?? "System Administrator", @@ -595,6 +636,9 @@ public class ImportBackgroundService : BackgroundService Scores = new List() }; + // Add ScoreImport to context explicitly to ensure EF Core knows to INSERT it + _context.Add(imported); + // Save ScoreImport parent first to get its Id rec_import.ScoreImport = imported; await _context.SaveChangesAsync(); From eea7fbcfa181e0bb427ea27b9e29734c152105ba Mon Sep 17 00:00:00 2001 From: harid Date: Tue, 19 May 2026 13:40:50 +0700 Subject: [PATCH 4/4] =?UTF-8?q?fix=20Update=20FK=20=E0=B8=95=E0=B8=B4?= =?UTF-8?q?=E0=B8=94=20Error?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Services/ImportBackgroundService.cs | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/Services/ImportBackgroundService.cs b/Services/ImportBackgroundService.cs index 7b56e9c..ddc4036 100644 --- a/Services/ImportBackgroundService.cs +++ b/Services/ImportBackgroundService.cs @@ -790,15 +790,28 @@ public class ImportBackgroundService : BackgroundService await _context.SaveChangesAsync(); _context.ChangeTracker.Clear(); - // preload scores - re-query from DB to avoid tracking issues + // หา ScoreImportId ก่อน + var scoreImportId = await _context.ScoreImports + .Where(x => x.RecruitImportId == rec_import.Id) + .Select(x => x.Id) + .FirstOrDefaultAsync(); + + if (scoreImportId == Guid.Empty) return; + + // preload scores - query all entities that will be updated (with tracking) var scoreList = await _context.RecruitScores - .Where(s => s.ScoreImport.RecruitImportId == rec_import.Id && !string.IsNullOrEmpty(s.ExamId)) + .Where(s => EF.Property(s, "ScoreImportId") == scoreImportId && !string.IsNullOrEmpty(s.ExamId)) .GroupBy(x => x.ExamId) .Where(g => g.Count() == 1) .Select(g => g.First()) .ToListAsync(); + + // Group by ExamId for easy lookup var score = scoreList.ToDictionary(s => s.ExamId!, s => s); + // ถ้าไม่มีผลคะแนนสอบคัดเลือกผู้พิการให้จบการทำงาน + if (score.Count == 0) return; + // Read from saved file (ResultFile uses stream from Form, but we saved to disk) using var stream = System.IO.File.OpenRead(job.ImportFile); using var c_package = new ExcelPackage(stream); @@ -809,7 +822,6 @@ public class ImportBackgroundService : BackgroundService int batchCount = 0; const int batchSize = 500; var endRow = workSheet.Dimension.End.Row; - var batchUpdates = new List(); while (row <= endRow) { @@ -827,7 +839,6 @@ public class ImportBackgroundService : BackgroundService existingScore.LastUpdatedAt = DateTime.Now; existingScore.LastUpdateUserId = job.UserId ?? ""; existingScore.LastUpdateFullName = job.FullName ?? "System Administrator"; - batchUpdates.Add(existingScore); batchCount++; } @@ -835,16 +846,17 @@ public class ImportBackgroundService : BackgroundService if (batchCount >= batchSize) { - await _context.BulkUpdateAsync(batchUpdates); - batchUpdates.Clear(); + await _context.SaveChangesAsync(); + _context.ChangeTracker.Clear(); batchCount = 0; } } // Process remaining records - if (batchUpdates.Count > 0) + if (batchCount > 0) { - await _context.BulkUpdateAsync(batchUpdates); + await _context.SaveChangesAsync(); + _context.ChangeTracker.Clear(); } } }