diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index a8da82e..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(dotnet build:*)", - "WebSearch", - "Bash(dotnet add:*)" - ], - "deny": [], - "ask": [] - } -} diff --git a/BMA.EHR.Recruit.csproj b/BMA.EHR.Recruit.csproj index bba397f..eca7d61 100644 --- a/BMA.EHR.Recruit.csproj +++ b/BMA.EHR.Recruit.csproj @@ -22,7 +22,6 @@ - diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 273523d..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,76 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## Build & Run - -```bash -dotnet build BMA.EHR.Recruit.Service.sln -dotnet build BMA.EHR.Recruit.Service.sln -c Release -dotnet publish -c Release -o /app/publish /p:UseAppHost=false -``` - -No test projects exist in this solution. - -## Architecture - -**Stack:** ASP.NET Core 7.0 Web API / EF Core 7.0 / MySQL (Pomelo) / MinIO (S3) / Keycloak (JWT) - -**Pattern:** Controllers → Services → EF Core DbContext (no repository layer for main data) - -### Multiple DbContexts -Three separate MySQL databases, each with its own `DbContext`: -- `ApplicationDbContext` — Recruitment data (Recruits, Scores, Imports) -- `OrgDbContext` — Organization data -- `MetadataDbContext` — Metadata - -All registered as `Transient` lifetime. Auto-migration runs on startup. - -### Controllers -- `BaseController` provides standardized `Success()` / `Error()` response methods returning `ResponseObject` (Status, Message, Result) -- `RecruitController` is the sole business controller (route: `api/v{version}/recruit`) -- API versioning enabled via `Microsoft.AspNetCore.Mvc.Versioning` - -### Background Import System -Excel file imports run asynchronously through a Channel-based queue: -- `ImportBackgroundService` (BackgroundService) — dequeues and processes jobs -- `ImportJobQueue` — bounded Channel (capacity 100) -- `ImportJobTracker` — in-memory ConcurrentDictionary tracking - -Four import types: `CandidateFile`, `CandidateFileById`, `ScoreFile`, `ResultFile` - -All imports use `EFCore.BulkExtensions.MySql` (v6.7.16) for bulk operations to handle 50,000+ rows without memory issues. Pattern: -1. Insert parent/history entities via `SaveChangesAsync` (small operations) -2. `ChangeTracker.Clear()` to release references -3. Collect entities into separate `List` per table -4. `BulkInsertAsync` with `SetOutputIdentity = true` for parent entities -5. Assign generated Ids to child entities -6. `BulkInsertAsync` for each child entity table separately -7. Batch size: 500 - -### Entity Models -All entities inherit from `EntityBase` (Guid `Id` PK, audit fields: `CreatedAt`, `CreatedUserId`, `LastUpdatedAt`, etc.). Models are in `Models/` with subdirectories: `Recruits/`, `Documents/`, `HR/`, `MetaData/`, `Placement/`. - -Key relationships use navigation properties without explicit FK properties (EF shadow properties). Configured via fluent API in `OnModelCreating`. - -### External Services -- **Authentication:** Keycloak JWT Bearer (`hrmsbkk-id.case-collection.com/realms/hrms`) -- **File Storage:** MinIO via `MinIOService` (AWS S3 SDK) -- **Search/Logging:** Elasticsearch (NEST client) -- **Excel:** EPPlus for reading import files - -## Key Files - -- `Program.cs` — Service registration, middleware pipeline, auto-migration -- `Data/ApplicationDbContext.cs` — EF Core fluent API relationship configuration -- `Services/ImportBackgroundService.cs` — All bulk import logic (4 import methods) -- `Services/RecruitService.cs` — Core business logic -- `Controllers/BaseController.cs` — Standard response helpers -- `Responses/ResponseObject.cs` — Standard API response envelope - -## Conventions - -- Language: C# with nullable reference types enabled -- Naming: PascalCase properties, `_camelCase` parameters in service methods -- API responses: Always wrapped in `ResponseObject` -- Authorization: `[Authorize]` on controllers, user context from JWT claims diff --git a/Services/ImportBackgroundService.cs b/Services/ImportBackgroundService.cs index 230a37c..3c2c0c2 100644 --- a/Services/ImportBackgroundService.cs +++ b/Services/ImportBackgroundService.cs @@ -7,7 +7,6 @@ using Microsoft.Extensions.Logging; using OfficeOpenXml; using System.Net.Http.Headers; using BMA.EHR.Recruit.Data; -using EFCore.BulkExtensions; using BMA.EHR.Recruit.Extensions; using BMA.EHR.Recruit.Models.Recruits; using BMA.EHR.Recruit.Requests.Recruits; @@ -113,16 +112,9 @@ public class ImportBackgroundService : BackgroundService int row = 2; int batchCount = 0; - const int batchSize = 500; + const int batchSize = 100; int totalProcessed = 0; - var batchRecruits = new List(); - var batchEducations = new List(); - var batchOccupations = new List(); - var batchAddresses = new List(); - var batchPayments = new List(); - var batchCertificates = new List(); - while (row <= totalRows) { var cell1 = workSheet?.Cells[row, 1]?.GetValue(); @@ -149,7 +141,7 @@ public class ImportBackgroundService : BackgroundService r.PositionLevel = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.PositionLevel)]?.GetValue().IsNull(""); // address - var address = new RecruitAddress() + r.Addresses.Add(new RecruitAddress() { Address = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Address)]?.GetValue() ?? "", Moo = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Moo)]?.GetValue() ?? "", @@ -169,10 +161,10 @@ public class ImportBackgroundService : BackgroundService Amphur1 = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Amphur1)]?.GetValue() ?? "", Province1 = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Province1)]?.GetValue() ?? "", ZipCode1 = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.ZipCode1)]?.GetValue() ?? "", - }; + }); // payment - var payment = new RecruitPayment() + r.Payments.Add(new RecruitPayment() { PaymentId = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.PaymentID)]?.GetValue() ?? "", CompanyCode = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.CompanyCode)]?.GetValue() ?? "", @@ -190,28 +182,28 @@ public class ImportBackgroundService : BackgroundService ChequeNo = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.ChequeNo)]?.GetValue() ?? "", Amount = (decimal)workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Amount)]?.GetValue(), ChqueBankCode = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.ChqBankCode)]?.GetValue() ?? "" - }; + }); // occupation - var occupation = new RecruitOccupation() + r.Occupations.Add(new RecruitOccupation() { Occupation = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Occupation)]?.GetValue() ?? "", Position = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Position)]?.GetValue() ?? "", Workplace = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Workplace)]?.GetValue() ?? "", Telephone = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.WorkplaceTelephone)]?.GetValue() ?? "", WorkAge = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.WorkAge)]?.GetValue() ?? "", - }; + }); // certificate - var certificate = new RecruitCertificate() + r.Certificates.Add(new RecruitCertificate() { CertificateNo = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.CertificateNo)]?.GetValue() ?? "", Description = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.CertificateDesc)]?.GetValue() ?? "", IssueDate = Convert.ToDateTime(workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.CertificateIssueDate)]?.GetValue().ToDateTime(DateTimeFormat.Ymd, "-")), ExpiredDate = Convert.ToDateTime(workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.CertificateExpireDate)]?.GetValue().ToDateTime(DateTimeFormat.Ymd, "-")) - }; + }); - var education = new RecruitEducation() + r.Educations.Add(new RecruitEducation() { Degree = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Degree)]?.GetValue() ?? "", Major = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.Major)]?.GetValue() ?? "", @@ -222,21 +214,10 @@ public class ImportBackgroundService : BackgroundService Specialist = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.SpecialList)]?.GetValue() ?? "", HighDegree = workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.HighDegree)]?.GetValue() ?? "", BachelorDate = Convert.ToDateTime(workSheet?.Cells[row, GetColumnIndex(cols, CandidateFileHeader.BachelorDate)]?.GetValue().ToDateTime(DateTimeFormat.Ymd, "-")) - }; - - r.Addresses.Add(address); - r.Payments.Add(payment); - r.Occupations.Add(occupation); - r.Certificates.Add(certificate); - r.Educations.Add(education); + }); r.RecruitImport = imported; - batchRecruits.Add(r); - batchAddresses.Add(address); - batchPayments.Add(payment); - batchOccupations.Add(occupation); - batchCertificates.Add(certificate); - batchEducations.Add(education); + _context.Recruits.Add(r); row++; batchCount++; @@ -244,56 +225,16 @@ public class ImportBackgroundService : BackgroundService if (batchCount >= batchSize) { - await _context.BulkInsertAsync(batchRecruits, options => { options.SetOutputIdentity = true; }); - - for (int j = 0; j < batchRecruits.Count; j++) - { - batchAddresses[j].Recruit = batchRecruits[j]; - batchPayments[j].Recruit = batchRecruits[j]; - batchOccupations[j].Recruit = batchRecruits[j]; - batchCertificates[j].Recruit = batchRecruits[j]; - batchEducations[j].Recruit = batchRecruits[j]; - } - - await _context.BulkInsertAsync(batchAddresses); - await _context.BulkInsertAsync(batchPayments); - await _context.BulkInsertAsync(batchOccupations); - await _context.BulkInsertAsync(batchCertificates); - await _context.BulkInsertAsync(batchEducations); - - batchRecruits.Clear(); - batchAddresses.Clear(); - batchPayments.Clear(); - batchOccupations.Clear(); - batchCertificates.Clear(); - batchEducations.Clear(); + _context.SaveChanges(); + _context.ChangeTracker.Clear(); + _context.Entry(imported).State = EntityState.Unchanged; batchCount = 0; _tracker.UpdateStatus(job.JobId, ImportJobStatus.Running, totalProcessed); } } - - // Process remaining records - if (batchRecruits.Count > 0) - { - await _context.BulkInsertAsync(batchRecruits, options => { options.SetOutputIdentity = true; }); - - for (int j = 0; j < batchRecruits.Count; j++) - { - batchAddresses[j].Recruit = batchRecruits[j]; - batchPayments[j].Recruit = batchRecruits[j]; - batchOccupations[j].Recruit = batchRecruits[j]; - batchCertificates[j].Recruit = batchRecruits[j]; - batchEducations[j].Recruit = batchRecruits[j]; - } - - await _context.BulkInsertAsync(batchAddresses); - await _context.BulkInsertAsync(batchPayments); - await _context.BulkInsertAsync(batchOccupations); - await _context.BulkInsertAsync(batchCertificates); - await _context.BulkInsertAsync(batchEducations); - } } + _context.SaveChanges(); job.TotalCount = _tracker.GetJob(job.JobId)?.ProcessedCount ?? 0; } @@ -303,10 +244,23 @@ public class ImportBackgroundService : BackgroundService private async Task ProcessCandidateFileByIdAsync(ApplicationDbContext _context, MinIOService _minioService, RecruitService _recruitService, IWebHostEnvironment _webHostEnv, ImportJobInfo job) { - var imported = await _context.RecruitImports.FindAsync(job.RecruitImportId); + var imported = await _context.RecruitImports.AsQueryable() + .Include(x => x.ImportHostories) + .Include(x => x.ImportFile) + .Include(x => x.Recruits) + .ThenInclude(x => x.Addresses) + .Include(x => x.Recruits) + .ThenInclude(x => x.Occupations) + .Include(x => x.Recruits) + .ThenInclude(x => x.Certificates) + .Include(x => x.Recruits) + .ThenInclude(x => x.Educations) + .Include(x => x.Recruits) + .ThenInclude(x => x.Payments) + .FirstOrDefaultAsync(x => x.Id == job.RecruitImportId); + if (imported == null) throw new Exception("RecruitImport not found"); - // Save import history using regular SaveChanges (small operation) imported.ImportHostories.Add(new RecruitImportHistory { Description = "นำเข้าข้อมูลผู้สมัครสอบแข่งขัน", @@ -317,28 +271,19 @@ public class ImportBackgroundService : BackgroundService LastUpdateUserId = job.UserId ?? "", LastUpdateFullName = job.FullName ?? "System Administrator", }); - await _context.SaveChangesAsync(); - _context.ChangeTracker.Clear(); - - var importId = imported.Id; using var c_package = new ExcelPackage(new FileInfo(job.ImportFile)); for (int i = 0; i < c_package.Workbook.Worksheets.Count; i++) { var workSheet = c_package.Workbook.Worksheets[i]; var totalRows = workSheet.Dimension.Rows; + var cols = workSheet.GetHeaderColumns(); int row = 2; int batchCount = 0; - const int batchSize = 500; + const int batchSize = 100; int totalProcessed = 0; - var batchRecruits = new List(); - var batchEducations = new List(); - var batchOccupations = new List(); - var batchAddresses = new List(); - var batchPayments = new List(); - while (row <= totalRows) { var cell1 = workSheet?.Cells[row, 1]?.GetValue(); @@ -372,8 +317,8 @@ public class ImportBackgroundService : BackgroundService r.LastUpdateUserId = job.UserId ?? ""; r.LastUpdateFullName = job.FullName ?? "System Administrator"; - // Store child entities in separate lists for bulk insert - var education = new RecruitEducation() + // education + r.Educations.Add(new RecruitEducation() { Degree = workSheet?.Cells[row, 18]?.GetValue() ?? "", Major = workSheet?.Cells[row, 19]?.GetValue() == "อื่น ๆ" ? workSheet?.Cells[row, 20]?.GetValue() ?? "" : workSheet?.Cells[row, 19]?.GetValue() ?? "", @@ -390,9 +335,10 @@ public class ImportBackgroundService : BackgroundService LastUpdatedAt = DateTime.Now, LastUpdateUserId = job.UserId ?? "", LastUpdateFullName = job.FullName ?? "System Administrator" - }; + }); - var occupation = new RecruitOccupation() + // occupation + r.Occupations.Add(new RecruitOccupation() { Occupation = workSheet?.Cells[row, 33]?.GetValue() == "อื่น ๆ" ? workSheet?.Cells[row, 34]?.GetValue() ?? "" : workSheet?.Cells[row, 33]?.GetValue() ?? "", Position = workSheet?.Cells[row, 37]?.GetValue() ?? "", @@ -405,9 +351,10 @@ public class ImportBackgroundService : BackgroundService LastUpdatedAt = DateTime.Now, LastUpdateUserId = job.UserId ?? "", LastUpdateFullName = job.FullName ?? "System Administrator" - }; + }); - var address = new RecruitAddress() + // address + r.Addresses.Add(new RecruitAddress() { Address = $"{(workSheet?.Cells[row, 49]?.GetValue() ?? "")} {(workSheet?.Cells[row, 50]?.GetValue() ?? "")}", Moo = workSheet?.Cells[row, 51]?.GetValue() ?? "", @@ -433,9 +380,10 @@ public class ImportBackgroundService : BackgroundService LastUpdatedAt = DateTime.Now, LastUpdateUserId = job.UserId ?? "", LastUpdateFullName = job.FullName ?? "System Administrator" - }; + }); - var payment = new RecruitPayment() + // payment + r.Payments.Add(new RecruitPayment() { PaymentId = workSheet?.Cells[row, 104]?.GetValue() ?? "", CompanyCode = workSheet?.Cells[row, 105]?.GetValue() ?? "", @@ -459,18 +407,10 @@ public class ImportBackgroundService : BackgroundService LastUpdatedAt = DateTime.Now, LastUpdateUserId = job.UserId ?? "", LastUpdateFullName = job.FullName ?? "System Administrator" - }; + }); - r.Educations.Add(education); - r.Occupations.Add(occupation); - r.Addresses.Add(address); - r.Payments.Add(payment); - - batchRecruits.Add(r); - batchEducations.Add(education); - batchOccupations.Add(occupation); - batchAddresses.Add(address); - batchPayments.Add(payment); + r.RecruitImport = imported; + _context.Recruits.Add(r); row++; batchCount++; @@ -478,61 +418,16 @@ public class ImportBackgroundService : BackgroundService if (batchCount >= batchSize) { - // BulkInsert Recruits first (with SetOutputIdentity to get generated Ids) - await _context.BulkInsertAsync(batchRecruits, options => - { - options.SetOutputIdentity = true; - }); - - // Assign generated Recruit Id to child entities - for (int j = 0; j < batchRecruits.Count; j++) - { - batchEducations[j].Recruit = batchRecruits[j]; - batchOccupations[j].Recruit = batchRecruits[j]; - batchAddresses[j].Recruit = batchRecruits[j]; - batchPayments[j].Recruit = batchRecruits[j]; - } - - // BulkInsert child entities (no identity output needed) - await _context.BulkInsertAsync(batchEducations); - await _context.BulkInsertAsync(batchOccupations); - await _context.BulkInsertAsync(batchAddresses); - await _context.BulkInsertAsync(batchPayments); - - // Clear all lists for next batch - batchRecruits.Clear(); - batchEducations.Clear(); - batchOccupations.Clear(); - batchAddresses.Clear(); - batchPayments.Clear(); + _context.SaveChanges(); + _context.ChangeTracker.Clear(); + _context.Entry(imported).State = EntityState.Unchanged; batchCount = 0; _tracker.UpdateStatus(job.JobId, ImportJobStatus.Running, totalProcessed); } } - - // Process remaining records - if (batchRecruits.Count > 0) - { - await _context.BulkInsertAsync(batchRecruits, options => - { - options.SetOutputIdentity = true; - }); - - for (int j = 0; j < batchRecruits.Count; j++) - { - batchEducations[j].Recruit = batchRecruits[j]; - batchOccupations[j].Recruit = batchRecruits[j]; - batchAddresses[j].Recruit = batchRecruits[j]; - batchPayments[j].Recruit = batchRecruits[j]; - } - - await _context.BulkInsertAsync(batchEducations); - await _context.BulkInsertAsync(batchOccupations); - await _context.BulkInsertAsync(batchAddresses); - await _context.BulkInsertAsync(batchPayments); - } } + _context.SaveChanges(); job.TotalCount = _tracker.GetJob(job.JobId)?.ProcessedCount ?? 0; } @@ -552,7 +447,8 @@ public class ImportBackgroundService : BackgroundService if (rec_import.ScoreImport != null && rec_import.ScoreImport.Scores != null) { - await _context.BulkDeleteAsync(rec_import.ScoreImport.Scores.ToList()); + _context.RecruitScores.RemoveRange(rec_import.ScoreImport.Scores); + await _context.SaveChangesAsync(); } rec_import.ImportHostories.Add(new RecruitImportHistory @@ -581,12 +477,7 @@ public class ImportBackgroundService : BackgroundService Scores = new List() }; - // Save ScoreImport parent first to get its Id - rec_import.ScoreImport = imported; - await _context.SaveChangesAsync(); - _context.ChangeTracker.Clear(); - - // preload recruits (lightweight - only ExamId) + // preload recruits var recruitsDict = await _context.Recruits .Where(x => x.RecruitImport.Id == rec_import.Id) .GroupBy(x => x.ExamId) @@ -601,12 +492,10 @@ public class ImportBackgroundService : BackgroundService var cols = workSheet.GetHeaderColumns(); int row = 8; int batchCount = 0; - const int batchSize = 500; + const int batchSize = 100; int totalProcessed = 0; var endRow = workSheet.Dimension.End.Row; - var batchScores = new List(); - while (row <= endRow) { var cell1 = workSheet?.Cells[row, 1]?.GetValue(); @@ -655,9 +544,8 @@ public class ImportBackgroundService : BackgroundService r.LastUpdatedAt = DateTime.Now; r.LastUpdateUserId = job.UserId ?? ""; r.LastUpdateFullName = job.FullName ?? "System Administrator"; - r.ScoreImport = imported; - batchScores.Add(r); + imported.Scores.Add(r); } row++; @@ -666,20 +554,23 @@ public class ImportBackgroundService : BackgroundService if (batchCount >= batchSize) { - await _context.BulkInsertAsync(batchScores); - batchScores.Clear(); + rec_import.ScoreImport = imported; + await _context.SaveChangesAsync(); + _context.ChangeTracker.Clear(); + _context.Attach(rec_import); + _context.Attach(imported); + imported.Scores.Clear(); batchCount = 0; _tracker.UpdateStatus(job.JobId, ImportJobStatus.Running, totalProcessed); } } - - // Process remaining records - if (batchScores.Count > 0) - { - await _context.BulkInsertAsync(batchScores); - } } + if (imported.Scores.Count > 0) + { + rec_import.ScoreImport = imported; + await _context.SaveChangesAsync(); + } job.TotalCount = _tracker.GetJob(job.JobId)?.ProcessedCount ?? 0; } @@ -710,7 +601,7 @@ public class ImportBackgroundService : BackgroundService x.Number = string.Empty; x.RemarkExamOrder = string.Empty; } - await _context.BulkUpdateAsync(oldScores); + await _context.SaveChangesAsync(); } } @@ -724,17 +615,14 @@ public class ImportBackgroundService : BackgroundService LastUpdateUserId = job.UserId ?? "", LastUpdateFullName = job.FullName ?? "System Administrator", }); - await _context.SaveChangesAsync(); - _context.ChangeTracker.Clear(); - // preload scores - re-query from DB to avoid tracking issues - var scoreList = await _context.RecruitScores - .Where(s => s.ScoreImport.RecruitImportId == rec_import.Id && !string.IsNullOrEmpty(s.ExamId)) + // preload scores + var score = rec_import.ScoreImport.Scores + .Where(s => !string.IsNullOrEmpty(s.ExamId)) .GroupBy(x => x.ExamId) .Where(g => g.Count() == 1) .Select(g => g.First()) - .ToListAsync(); - var score = scoreList.ToDictionary(s => s.ExamId!, s => s); + .ToDictionary(s => s.ExamId, s => s); // Read from saved file (ResultFile uses stream from Form, but we saved to disk) using var stream = System.IO.File.OpenRead(job.ImportFile); @@ -744,9 +632,8 @@ public class ImportBackgroundService : BackgroundService { int row = 7; int batchCount = 0; - const int batchSize = 500; + const int batchSize = 100; var endRow = workSheet.Dimension.End.Row; - var batchUpdates = new List(); while (row <= endRow) { @@ -764,7 +651,6 @@ public class ImportBackgroundService : BackgroundService existingScore.LastUpdatedAt = DateTime.Now; existingScore.LastUpdateUserId = job.UserId ?? ""; existingScore.LastUpdateFullName = job.FullName ?? "System Administrator"; - batchUpdates.Add(existingScore); batchCount++; } @@ -772,18 +658,15 @@ public class ImportBackgroundService : BackgroundService if (batchCount >= batchSize) { - await _context.BulkUpdateAsync(batchUpdates); - batchUpdates.Clear(); + await _context.SaveChangesAsync(); + _context.ChangeTracker.Clear(); + _context.Entry(rec_import).State = EntityState.Unchanged; batchCount = 0; } } - - // Process remaining records - if (batchUpdates.Count > 0) - { - await _context.BulkUpdateAsync(batchUpdates); - } } + + await _context.SaveChangesAsync(); } #endregion diff --git a/obj/project.assets.json b/obj/project.assets.json index 818122a..424a2d0 100644 --- a/obj/project.assets.json +++ b/obj/project.assets.json @@ -155,43 +155,6 @@ } } }, - "dotMorten.Microsoft.SqlServer.Types/1.4.0": { - "type": "package", - "dependencies": { - "System.Data.SqlClient": "4.8.3", - "System.Memory": "4.5.4" - }, - "compile": { - "lib/netstandard2.0/Microsoft.SqlServer.Types.dll": { - "related": ".pdb;.xml" - } - }, - "runtime": { - "lib/netstandard2.0/Microsoft.SqlServer.Types.dll": { - "related": ".pdb;.xml" - } - } - }, - "EFCore.BulkExtensions.MySql/6.7.16": { - "type": "package", - "dependencies": { - "EntityFrameworkCore.SqlServer.HierarchyId": "3.0.1", - "MedallionTopologicalSort": "1.0.0", - "Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite": "6.0.21", - "Pomelo.EntityFrameworkCore.MySql": "6.0.2", - "StrongNamer": "0.2.5" - }, - "compile": { - "lib/net6.0/EFCore.BulkExtensions.MySql.dll": { - "related": ".pdb;.xml" - } - }, - "runtime": { - "lib/net6.0/EFCore.BulkExtensions.MySql.dll": { - "related": ".pdb;.xml" - } - } - }, "Elasticsearch.Net/7.17.5": { "type": "package", "dependencies": { @@ -210,42 +173,6 @@ } } }, - "EntityFrameworkCore.SqlServer.HierarchyId/3.0.1": { - "type": "package", - "dependencies": { - "EntityFrameworkCore.SqlServer.HierarchyId.Abstractions": "3.0.1", - "Microsoft.EntityFrameworkCore.SqlServer": "6.0.1" - }, - "compile": { - "lib/net6.0/EntityFrameworkCore.SqlServer.HierarchyId.dll": { - "related": ".xml" - } - }, - "runtime": { - "lib/net6.0/EntityFrameworkCore.SqlServer.HierarchyId.dll": { - "related": ".xml" - } - }, - "build": { - "build/net6.0/_._": {} - } - }, - "EntityFrameworkCore.SqlServer.HierarchyId.Abstractions/3.0.1": { - "type": "package", - "dependencies": { - "dotMorten.Microsoft.SqlServer.Types": "1.4.0" - }, - "compile": { - "lib/netstandard2.0/EntityFrameworkCore.SqlServer.HierarchyId.Abstractions.dll": { - "related": ".xml" - } - }, - "runtime": { - "lib/netstandard2.0/EntityFrameworkCore.SqlServer.HierarchyId.Abstractions.dll": { - "related": ".xml" - } - } - }, "EPPlus/6.1.3": { "type": "package", "dependencies": { @@ -377,19 +304,6 @@ } } }, - "MedallionTopologicalSort/1.0.0": { - "type": "package", - "compile": { - "lib/netstandard2.0/MedallionTopologicalSort.dll": { - "related": ".xml" - } - }, - "runtime": { - "lib/netstandard2.0/MedallionTopologicalSort.dll": { - "related": ".xml" - } - } - }, "Microsoft.AspNetCore.Antiforgery/2.2.0": { "type": "package", "dependencies": { @@ -1668,27 +1582,6 @@ } } }, - "Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite/6.0.21": { - "type": "package", - "dependencies": { - "Microsoft.EntityFrameworkCore.SqlServer": "6.0.21", - "NetTopologySuite": "2.3.0", - "NetTopologySuite.IO.SqlServerBytes": "2.0.0" - }, - "compile": { - "lib/net6.0/Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite.dll": { - "related": ".xml" - } - }, - "runtime": { - "lib/net6.0/Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite.dll": { - "related": ".xml" - } - }, - "build": { - "build/net6.0/_._": {} - } - }, "Microsoft.EntityFrameworkCore.Tools/7.0.3": { "type": "package", "dependencies": { @@ -2734,38 +2627,6 @@ "System.Xml.XDocument": "4.3.0" } }, - "NetTopologySuite/2.3.0": { - "type": "package", - "dependencies": { - "System.Memory": "4.5.3" - }, - "compile": { - "lib/netstandard2.0/NetTopologySuite.dll": { - "related": ".xml" - } - }, - "runtime": { - "lib/netstandard2.0/NetTopologySuite.dll": { - "related": ".xml" - } - } - }, - "NetTopologySuite.IO.SqlServerBytes/2.0.0": { - "type": "package", - "dependencies": { - "NetTopologySuite": "[2.0.0, 3.0.0-A)" - }, - "compile": { - "lib/netstandard2.0/NetTopologySuite.IO.SqlServerBytes.dll": { - "related": ".xml" - } - }, - "runtime": { - "lib/netstandard2.0/NetTopologySuite.IO.SqlServerBytes.dll": { - "related": ".xml" - } - } - }, "Newtonsoft.Json/13.0.3": { "type": "package", "compile": { @@ -3475,12 +3336,6 @@ } } }, - "StrongNamer/0.2.5": { - "type": "package", - "build": { - "build/_._": {} - } - }, "Swashbuckle.AspNetCore/6.5.0": { "type": "package", "dependencies": { @@ -5522,38 +5377,6 @@ "lib/netstandard2.1/DnsClient.xml" ] }, - "dotMorten.Microsoft.SqlServer.Types/1.4.0": { - "sha512": "MYxVbuBguObk8QFNTuBZ+ZEC/m1zbvG774FbFvwiDZjc0RYq/co27THrHN5Dyd52ie0R5bt2uxSZj4tIb3lYFg==", - "type": "package", - "path": "dotmorten.microsoft.sqlserver.types/1.4.0", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "README.md", - "dotmorten.microsoft.sqlserver.types.1.4.0.nupkg.sha512", - "dotmorten.microsoft.sqlserver.types.nuspec", - "lib/netstandard2.0/Microsoft.SqlServer.Types.dll", - "lib/netstandard2.0/Microsoft.SqlServer.Types.pdb", - "lib/netstandard2.0/Microsoft.SqlServer.Types.xml" - ] - }, - "EFCore.BulkExtensions.MySql/6.7.16": { - "sha512": "XJbeYxAKeRrI/gVXw08Nx9ZJiP2aRDyqJgW7GRTmxIUUVyp8hgVqasLUaBh0LA6PmtQr+eCg8LzyUYvSF4U9hA==", - "type": "package", - "path": "efcore.bulkextensions.mysql/6.7.16", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "EFCoreBulk.png", - "LICENSE.txt", - "README.md", - "efcore.bulkextensions.mysql.6.7.16.nupkg.sha512", - "efcore.bulkextensions.mysql.nuspec", - "lib/net6.0/EFCore.BulkExtensions.MySql.dll", - "lib/net6.0/EFCore.BulkExtensions.MySql.pdb", - "lib/net6.0/EFCore.BulkExtensions.MySql.xml" - ] - }, "Elasticsearch.Net/7.17.5": { "sha512": "orChsQi1Ceho/NyIylNOn6y4vuGcsbCfMZnCueNN0fzqYEGQmQdPfcVmsR5+3fwpXTgxCdjTUVmqOwvHpCSB+Q==", "type": "package", @@ -5576,33 +5399,6 @@ "nuget-icon.png" ] }, - "EntityFrameworkCore.SqlServer.HierarchyId/3.0.1": { - "sha512": "NDN8PwDIyuWfOR6nV0muaCEvCDuAFYPEtKOku+/TIzOWMmvPNqqLF0WF0BQpk4cVuvQ2fr5/9F7aZk4DkVTq4g==", - "type": "package", - "path": "entityframeworkcore.sqlserver.hierarchyid/3.0.1", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "build/net6.0/EntityFrameworkCore.SqlServer.HierarchyId.targets", - "entityframeworkcore.sqlserver.hierarchyid.3.0.1.nupkg.sha512", - "entityframeworkcore.sqlserver.hierarchyid.nuspec", - "lib/net6.0/EntityFrameworkCore.SqlServer.HierarchyId.dll", - "lib/net6.0/EntityFrameworkCore.SqlServer.HierarchyId.xml" - ] - }, - "EntityFrameworkCore.SqlServer.HierarchyId.Abstractions/3.0.1": { - "sha512": "x0Y3QtTLd1oyQMHTpXmUnuXabAs44kZBlP0suVQjlreF76e55x2D9YDiXRwUekvtI+X+b0NLkYfsnAqf9W/58w==", - "type": "package", - "path": "entityframeworkcore.sqlserver.hierarchyid.abstractions/3.0.1", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "entityframeworkcore.sqlserver.hierarchyid.abstractions.3.0.1.nupkg.sha512", - "entityframeworkcore.sqlserver.hierarchyid.abstractions.nuspec", - "lib/netstandard2.0/EntityFrameworkCore.SqlServer.HierarchyId.Abstractions.dll", - "lib/netstandard2.0/EntityFrameworkCore.SqlServer.HierarchyId.Abstractions.xml" - ] - }, "EPPlus/6.1.3": { "sha512": "1NEgW7wMxHWz7k3hN6D7PPkCCKR24LK86EIIEwfKrBy+yyWQM/fsCrngt+DPAjVgGLOThVmXInSFJqD15X7OCQ==", "type": "package", @@ -5790,21 +5586,6 @@ "litedb.nuspec" ] }, - "MedallionTopologicalSort/1.0.0": { - "sha512": "dcAqM8TcyZQ/T466CvqNMUUn/G0FQE+4R7l62ngXH7hLFP9yA7yoP/ySsLgiXx3pGUQC3J+cUvXmJOOR/eC+oQ==", - "type": "package", - "path": "medalliontopologicalsort/1.0.0", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "lib/net45/MedallionTopologicalSort.dll", - "lib/net45/MedallionTopologicalSort.xml", - "lib/netstandard2.0/MedallionTopologicalSort.dll", - "lib/netstandard2.0/MedallionTopologicalSort.xml", - "medalliontopologicalsort.1.0.0.nupkg.sha512", - "medalliontopologicalsort.nuspec" - ] - }, "Microsoft.AspNetCore.Antiforgery/2.2.0": { "sha512": "fVQsSXNZz38Ysx8iKwwqfOLHhLrAeKEMBS5Ia3Lh7BJjOC2vPV28/yk08AovOMsB3SNQPGnE7bv+lsIBTmAkvw==", "type": "package", @@ -6901,21 +6682,6 @@ "microsoft.entityframeworkcore.sqlserver.nuspec" ] }, - "Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite/6.0.21": { - "sha512": "D6c+WWg6GIcpuHKxHVr6hJ5RFVfwPlgK/PQ0vV+z7f8haRY7l3tlK/xhJeYZvXCpPluyijM4KRRPi/qbUFxROA==", - "type": "package", - "path": "microsoft.entityframeworkcore.sqlserver.nettopologysuite/6.0.21", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "Icon.png", - "build/net6.0/Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite.targets", - "lib/net6.0/Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite.dll", - "lib/net6.0/Microsoft.EntityFrameworkCore.SqlServer.NetTopologySuite.xml", - "microsoft.entityframeworkcore.sqlserver.nettopologysuite.6.0.21.nupkg.sha512", - "microsoft.entityframeworkcore.sqlserver.nettopologysuite.nuspec" - ] - }, "Microsoft.EntityFrameworkCore.Tools/7.0.3": { "sha512": "yHFlYPZS3Jx7JMCQnGKfJzv95rJWVcmcUn/OW5cbCyWgQk81JJpTZ9Q6kkvwquYjFRfvYHBGuXNIYhAJokOBTQ==", "type": "package", @@ -8546,33 +8312,6 @@ "netstandard.library.nuspec" ] }, - "NetTopologySuite/2.3.0": { - "sha512": "Y+YOvA5um+75Wm9NKE+EhUNCvLYWiPhPW5Q5eBNi6kVNbxsX60/nvIJtve5iRcbrAl38W9h2w2yBjU81cjDO5g==", - "type": "package", - "path": "nettopologysuite/2.3.0", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "icon.png", - "lib/netstandard2.0/NetTopologySuite.dll", - "lib/netstandard2.0/NetTopologySuite.xml", - "nettopologysuite.2.3.0.nupkg.sha512", - "nettopologysuite.nuspec" - ] - }, - "NetTopologySuite.IO.SqlServerBytes/2.0.0": { - "sha512": "TuyMB0VSlRJx86UrWeQ+SGgOMudvhIL1qJJdWJw78nWsXIzKRP4ooQAhhhCCH7n8q1lvd0/NW3ByaLlHxxNSPQ==", - "type": "package", - "path": "nettopologysuite.io.sqlserverbytes/2.0.0", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "lib/netstandard2.0/NetTopologySuite.IO.SqlServerBytes.dll", - "lib/netstandard2.0/NetTopologySuite.IO.SqlServerBytes.xml", - "nettopologysuite.io.sqlserverbytes.2.0.0.nupkg.sha512", - "nettopologysuite.io.sqlserverbytes.nuspec" - ] - }, "Newtonsoft.Json/13.0.3": { "sha512": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ==", "type": "package", @@ -9411,29 +9150,6 @@ "snappier.nuspec" ] }, - "StrongNamer/0.2.5": { - "sha512": "1IWl8gYnsTC6NXHz63iDpXL8r0y5x0M/Cnq/Ju5uM17gTOQYSeclMkgQsvmGglJEqAwVxayY1sIUR3bb2MAy5Q==", - "type": "package", - "path": "strongnamer/0.2.5", - "files": [ - ".nupkg.metadata", - ".signature.p7s", - "build/SharedKey.snk", - "build/StrongNamer.targets", - "build/net461/Mono.Cecil.Mdb.dll", - "build/net461/Mono.Cecil.Pdb.dll", - "build/net461/Mono.Cecil.Rocks.dll", - "build/net461/Mono.Cecil.dll", - "build/net461/StrongNamer.dll", - "build/netcoreapp2.1/Mono.Cecil.Mdb.dll", - "build/netcoreapp2.1/Mono.Cecil.Pdb.dll", - "build/netcoreapp2.1/Mono.Cecil.Rocks.dll", - "build/netcoreapp2.1/Mono.Cecil.dll", - "build/netcoreapp2.1/StrongNamer.dll", - "strongnamer.0.2.5.nupkg.sha512", - "strongnamer.nuspec" - ] - }, "Swashbuckle.AspNetCore/6.5.0": { "sha512": "FK05XokgjgwlCI6wCT+D4/abtQkL1X1/B9Oas6uIwHFmYrIO9WUD5aLC9IzMs9GnHfUXOtXZ2S43gN1mhs5+aA==", "type": "package", @@ -13994,7 +13710,6 @@ "net7.0": [ "AWSSDK.S3 >= 3.7.103.35", "CoreAdmin >= 2.7.0", - "EFCore.BulkExtensions.MySql >= 6.7.16", "EPPlus >= 6.1.3", "Microsoft.AspNetCore.Authentication.JwtBearer >= 7.0.20", "Microsoft.AspNetCore.Mvc.NewtonsoftJson >= 7.0.3", @@ -14081,10 +13796,6 @@ "target": "Package", "version": "[2.7.0, )" }, - "EFCore.BulkExtensions.MySql": { - "target": "Package", - "version": "[6.7.16, )" - }, "EPPlus": { "target": "Package", "version": "[6.1.3, )" diff --git a/obj/project.nuget.cache b/obj/project.nuget.cache index e443564..e8d0055 100644 --- a/obj/project.nuget.cache +++ b/obj/project.nuget.cache @@ -1,6 +1,6 @@ { "version": 2, - "dgSpecHash": "XR3lYvVwNcQ=", + "dgSpecHash": "D0RTCj18huw=", "success": true, "projectFilePath": "/Users/suphonchaip/Develop/hrms/hrms-api-recruit/BMA.EHR.Recruit.csproj", "expectedPackageFiles": [ @@ -13,11 +13,7 @@ "/Users/suphonchaip/.nuget/packages/coreadmin/2.7.0/coreadmin.2.7.0.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/dapper/2.0.123/dapper.2.0.123.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/dnsclient/1.6.1/dnsclient.1.6.1.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/dotmorten.microsoft.sqlserver.types/1.4.0/dotmorten.microsoft.sqlserver.types.1.4.0.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/efcore.bulkextensions.mysql/6.7.16/efcore.bulkextensions.mysql.6.7.16.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/elasticsearch.net/7.17.5/elasticsearch.net.7.17.5.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/entityframeworkcore.sqlserver.hierarchyid/3.0.1/entityframeworkcore.sqlserver.hierarchyid.3.0.1.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/entityframeworkcore.sqlserver.hierarchyid.abstractions/3.0.1/entityframeworkcore.sqlserver.hierarchyid.abstractions.3.0.1.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/epplus/6.1.3/epplus.6.1.3.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/epplus.interfaces/6.1.1/epplus.interfaces.6.1.1.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/epplus.system.drawing/6.1.1/epplus.system.drawing.6.1.1.nupkg.sha512", @@ -27,7 +23,6 @@ "/Users/suphonchaip/.nuget/packages/k4os.compression.lz4.streams/1.2.6/k4os.compression.lz4.streams.1.2.6.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/k4os.hash.xxhash/1.0.6/k4os.hash.xxhash.1.0.6.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/litedb/5.0.11/litedb.5.0.11.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/medalliontopologicalsort/1.0.0/medalliontopologicalsort.1.0.0.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/microsoft.aspnetcore.antiforgery/2.2.0/microsoft.aspnetcore.antiforgery.2.2.0.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/microsoft.aspnetcore.authentication.abstractions/2.2.0/microsoft.aspnetcore.authentication.abstractions.2.2.0.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/microsoft.aspnetcore.authentication.core/2.2.0/microsoft.aspnetcore.authentication.core.2.2.0.nupkg.sha512", @@ -98,7 +93,6 @@ "/Users/suphonchaip/.nuget/packages/microsoft.entityframeworkcore.relational/7.0.3/microsoft.entityframeworkcore.relational.7.0.3.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/microsoft.entityframeworkcore.relational.design/1.1.1/microsoft.entityframeworkcore.relational.design.1.1.1.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/microsoft.entityframeworkcore.sqlserver/7.0.3/microsoft.entityframeworkcore.sqlserver.7.0.3.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/microsoft.entityframeworkcore.sqlserver.nettopologysuite/6.0.21/microsoft.entityframeworkcore.sqlserver.nettopologysuite.6.0.21.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/microsoft.entityframeworkcore.tools/7.0.3/microsoft.entityframeworkcore.tools.7.0.3.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/microsoft.extensions.apidescription.server/6.0.5/microsoft.extensions.apidescription.server.6.0.5.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/microsoft.extensions.caching.abstractions/7.0.0/microsoft.extensions.caching.abstractions.7.0.0.nupkg.sha512", @@ -157,8 +151,6 @@ "/Users/suphonchaip/.nuget/packages/mysqlconnector/2.2.5/mysqlconnector.2.2.5.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/nest/7.17.5/nest.7.17.5.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/netstandard.library/1.6.1/netstandard.library.1.6.1.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/nettopologysuite/2.3.0/nettopologysuite.2.3.0.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/nettopologysuite.io.sqlserverbytes/2.0.0/nettopologysuite.io.sqlserverbytes.2.0.0.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/newtonsoft.json/13.0.3/newtonsoft.json.13.0.3.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/newtonsoft.json.bson/1.0.2/newtonsoft.json.bson.1.0.2.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/nonfactors.grid.core.mvc6/7.1.0/nonfactors.grid.core.mvc6.7.1.0.nupkg.sha512", @@ -205,7 +197,6 @@ "/Users/suphonchaip/.nuget/packages/serilog.sinks.periodicbatching/3.1.0/serilog.sinks.periodicbatching.3.1.0.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/sharpcompress/0.30.1/sharpcompress.0.30.1.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/snappier/1.0.0/snappier.1.0.0.nupkg.sha512", - "/Users/suphonchaip/.nuget/packages/strongnamer/0.2.5/strongnamer.0.2.5.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/swashbuckle.aspnetcore/6.5.0/swashbuckle.aspnetcore.6.5.0.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/swashbuckle.aspnetcore.annotations/6.5.0/swashbuckle.aspnetcore.annotations.6.5.0.nupkg.sha512", "/Users/suphonchaip/.nuget/packages/swashbuckle.aspnetcore.swagger/6.5.0/swashbuckle.aspnetcore.swagger.6.5.0.nupkg.sha512",