Handler.cs 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. using Application.Abstractions.Data;
  2. using Application.Abstractions.Messaging;
  3. using Microsoft.EntityFrameworkCore;
  4. namespace Application.Features.Api.Stocks.GetList;
  5. internal sealed class Handler(IAppDbContext db) : IQueryHandler<Query, Response>
  6. {
  7. private const ushort MaxPerPage = 100;
  8. public async Task<Response> Handle(Query request, CancellationToken ct)
  9. {
  10. var page = request.Page < 1 ? 1 : request.Page;
  11. var perPage = request.PerPage is 0 or > MaxPerPage ? (ushort)20 : request.PerPage;
  12. var query = db.Stock.AsNoTracking().Where(c => c.IsActive);
  13. if (request.Market.HasValue)
  14. {
  15. query = query.Where(c => c.Market == request.Market.Value);
  16. }
  17. var q = request.Q?.Trim();
  18. if (!string.IsNullOrEmpty(q))
  19. {
  20. query = query.Where(c => c.Name.Contains(q) || c.Code.StartsWith(q));
  21. }
  22. var total = await query.CountAsync(ct);
  23. // 방향 — 미지정/asc 외에는 내림차순 (숫자열 기본 desc, 기본 정렬도 desc)
  24. var desc = !string.Equals(request.Order, "asc", StringComparison.OrdinalIgnoreCase);
  25. // 정렬 — 기본 시가총액 내림차순. 거래대금(value)은 최근 거래일 StockDailyPrice 상관 서브쿼리 기준
  26. IOrderedQueryable<Domain.Entities.Stocks.Stock> ordered = request.Sort?.ToLowerInvariant() switch
  27. {
  28. "name" => desc ? query.OrderByDescending(c => c.Name) : query.OrderBy(c => c.Name),
  29. "price" => desc ? query.OrderByDescending(c => c.LastClosePrice) : query.OrderBy(c => c.LastClosePrice),
  30. "change" => desc ? query.OrderByDescending(c => c.LastChangeRate) : query.OrderBy(c => c.LastChangeRate),
  31. "value" => desc
  32. ? query.OrderByDescending(c => db.StockDailyPrice.Where(p => p.StockID == c.ID && p.TradingDate == c.LastTradingDate).Select(p => (long?)p.TradingValue).FirstOrDefault())
  33. : query.OrderBy(c => db.StockDailyPrice.Where(p => p.StockID == c.ID && p.TradingDate == c.LastTradingDate).Select(p => (long?)p.TradingValue).FirstOrDefault()),
  34. _ => desc ? query.OrderByDescending(c => c.MarketCap) : query.OrderBy(c => c.MarketCap)
  35. };
  36. var list = await ordered
  37. .ThenBy(c => c.Code)
  38. .Skip((page - 1) * perPage)
  39. .Take(perPage)
  40. .Select(c => new
  41. {
  42. c.Code,
  43. c.Name,
  44. c.Market,
  45. c.TradingStatus,
  46. c.LastTradingDate,
  47. c.LastClosePrice,
  48. c.LastChangeRate,
  49. c.MarketCap,
  50. // 전일 거래량·거래대금 — Stock denorm 에 없어 최근 거래일(LastTradingDate)의 StockDailyPrice 를 상관 서브쿼리로 조회
  51. Volume = db.StockDailyPrice.Where(p => p.StockID == c.ID && p.TradingDate == c.LastTradingDate).Select(p => (long?)p.Volume).FirstOrDefault(),
  52. TradingValue = db.StockDailyPrice.Where(p => p.StockID == c.ID && p.TradingDate == c.LastTradingDate).Select(p => (long?)p.TradingValue).FirstOrDefault()
  53. })
  54. .ToListAsync(ct);
  55. return new Response
  56. {
  57. Total = total,
  58. List = [..list.Select(c => new Response.Row
  59. {
  60. Code = c.Code,
  61. Name = c.Name,
  62. Market = c.Market.ToString(),
  63. TradingStatus = c.TradingStatus.ToString(),
  64. LastTradingDate = c.LastTradingDate,
  65. ClosePrice = c.LastClosePrice,
  66. ChangeRate = c.LastChangeRate,
  67. MarketCap = c.MarketCap,
  68. Volume = c.Volume,
  69. TradingValue = c.TradingValue
  70. })]
  71. };
  72. }
  73. }