SeibroMarkers.cs 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859
  1. using Application.Abstractions.Data;
  2. using Domain.Entities.Stocks;
  3. using Microsoft.EntityFrameworkCore;
  4. namespace Infrastructure.StockData;
  5. /// <summary>
  6. /// SEIBro 수집 마커(SeibroFetchMarker rolling · SeibroSweptDate date-sweep) 조회/upsert 헬퍼 — 리뷰 결함 #1 해소 배선의 공용 경로.
  7. /// 각 SyncService 가 완료 판정을 "데이터 행 존재"에서 "조회 시도(0행 포함) 마커 존재"로 바꾸는 데 쓴다.
  8. /// • rolling: LoadFetchMarkers 로 (키→마지막조회시각) 사전을 만들어 rolling 대상을 정렬(미조회 최우선→오래된 순), fetch 후 TouchFetchMarker.
  9. /// • date-sweep: IsSwept 로 "데이터 존재 OR 마커 존재" 판정(기존 적재분 재조회도 회피), fetch(0행 포함) 후 MarkSwept.
  10. /// EntityKey 는 문자열 — 복합키는 배치가 구분자로 결합해 전달한다. JobKey 는 job 상수.
  11. /// </summary>
  12. public static class SeibroMarkers
  13. {
  14. /// <summary>해당 job 의 (EntityKey → LastCheckedAt) 사전 로드 — rolling 대상 정렬 기준.</summary>
  15. public static async Task<Dictionary<string, DateTime>> LoadFetchMarkersAsync(IAppDbContext db, string jobKey, CancellationToken ct)
  16. {
  17. return await db.SeibroFetchMarker.AsNoTracking().Where(c => c.JobKey == jobKey).ToDictionaryAsync(c => c.EntityKey, c => c.LastCheckedAt, ct);
  18. }
  19. /// <summary>조회 시도(0행 포함) 후 rolling 마커 upsert — 있으면 Touch, 없으면 Create. 즉시 SaveChanges.</summary>
  20. public static async Task TouchFetchMarkerAsync(IAppDbContext db, string jobKey, string entityKey, CancellationToken ct)
  21. {
  22. var marker = await db.SeibroFetchMarker.FirstOrDefaultAsync(c => c.JobKey == jobKey && c.EntityKey == entityKey, ct);
  23. if (marker is null)
  24. {
  25. await db.SeibroFetchMarker.AddAsync(SeibroFetchMarker.Create(jobKey, entityKey), ct);
  26. }
  27. else
  28. {
  29. marker.Touch();
  30. }
  31. await db.SaveChangesAsync(ct);
  32. }
  33. /// <summary>해당 (job, 날짜[, 축])이 이미 조회 완료(0행 포함)로 기록됐는지.</summary>
  34. public static async Task<bool> IsSweptAsync(IAppDbContext db, string jobKey, DateOnly date, string? discriminator, CancellationToken ct)
  35. {
  36. var disc = string.IsNullOrWhiteSpace(discriminator) ? string.Empty : discriminator.Trim();
  37. return await db.SeibroSweptDate.AsNoTracking().AnyAsync(c => c.JobKey == jobKey && c.Date == date && c.Discriminator == disc, ct);
  38. }
  39. /// <summary>조회 완료(0행 포함) 후 날짜 스윕 마커 기록 — 이미 있으면 no-op. 즉시 SaveChanges.</summary>
  40. public static async Task MarkSweptAsync(IAppDbContext db, string jobKey, DateOnly date, string? discriminator, CancellationToken ct)
  41. {
  42. var disc = string.IsNullOrWhiteSpace(discriminator) ? string.Empty : discriminator.Trim();
  43. if (await db.SeibroSweptDate.AnyAsync(c => c.JobKey == jobKey && c.Date == date && c.Discriminator == disc, ct))
  44. {
  45. return;
  46. }
  47. await db.SeibroSweptDate.AddAsync(SeibroSweptDate.Create(jobKey, date, disc), ct);
  48. await db.SaveChangesAsync(ct);
  49. }
  50. }