using Application.Abstractions.Data; using Application.Abstractions.Messaging; using Microsoft.EntityFrameworkCore; using SharedKernel.Results; using GameLanguageEntity = Domain.Entities.Store.GameLanguage; namespace Application.Features.Admin.Store.GameLanguage.Save; public sealed class Handler(IAppDbContext db) : ICommandHandler> { public async Task> Handle(Command request, CancellationToken ct) { var items = request.Items; var dbRows = await db.GameLanguage.ToListAsync(ct); var dbByID = dbRows.ToDictionary(c => c.ID); var requestIDs = items.Where(c => c.ID.HasValue && c.ID.Value > 0).Select(c => c.ID!.Value).ToHashSet(); var deleteTargets = dbRows.Where(x => !requestIDs.Contains(x.ID)).ToList(); if (deleteTargets.Count > 0) { var deleteIDs = deleteTargets.Select(c => c.ID).ToList(); var inUse = await db.GameLanguageSupport.AsNoTracking().AnyAsync(c => deleteIDs.Contains(c.GameLanguageID), ct); if (inUse) { return Result.Failure(Error.Conflict("GameLanguage.InUse", "게임에 연결된 언어는 삭제할 수 없습니다. 먼저 해당 게임에서 언어 선택을 해제하세요.")); } db.GameLanguage.RemoveRange(deleteTargets); } ushort inserted = 0; ushort updated = 0; // 이미 사용 중인 한글명(트림 기준) → 소유 ID. 삭제 대상 제외. 배치 내 중복까지 인메모리로 차단 (per-row AnyAsync 제거) var deleteSet = deleteTargets.ToHashSet(); var takenNames = new Dictionary(StringComparer.OrdinalIgnoreCase); foreach (var x in dbRows) { if (!deleteSet.Contains(x)) { takenNames[x.KorName.Trim()] = x.ID; } } foreach (var row in items) { if (string.IsNullOrWhiteSpace(row.KorName)) { return Result.Failure(Error.Problem("GameLanguage.KorNameRequired", "언어 한글명은 필수입니다.")); } var name = row.KorName.Trim(); // 신규 추가 if (!row.ID.HasValue || row.ID.Value <= 0) { if (takenNames.ContainsKey(name)) { return Result.Failure(Error.Conflict("GameLanguage.DuplicateKorName", $"`{name}`는 이미 등록된 언어입니다.")); } db.GameLanguage.Add(GameLanguageEntity.Create(row.KorName, row.EngName, row.Order, row.IsActive)); takenNames[name] = 0; // 배치 내 신규(ID 미정) — 후속 행의 중복 차단용 inserted++; continue; } // 수정 if (!dbByID.TryGetValue(row.ID.Value, out var existing)) { return Result.Failure(Error.NotFound("GameLanguage.NotFound", $"존재하지 않는 ID: {row.ID.Value}")); } if (takenNames.TryGetValue(name, out var owner) && owner != row.ID.Value) { return Result.Failure(Error.Conflict("GameLanguage.DuplicateKorName", $"`{name}`는 이미 등록된 언어입니다.")); } var oldName = existing.KorName.Trim(); if (!string.Equals(oldName, name, StringComparison.OrdinalIgnoreCase)) { takenNames.Remove(oldName); takenNames[name] = row.ID.Value; } existing.Update(row.KorName, row.EngName, row.Order, row.IsActive); updated++; } var deleted = (ushort)deleteTargets.Count; await db.SaveChangesAsync(ct); return Result.Success(new Response(inserted, updated, deleted)); } }