From 4ea7c0010b0e65a44d8dd4477767fc354d3a0c9b Mon Sep 17 00:00:00 2001 From: harid Date: Mon, 18 May 2026 17:48:51 +0700 Subject: [PATCH] =?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();