173 lines
6.7 KiB
Markdown
173 lines
6.7 KiB
Markdown
# STEP 6 — DB 서비스 구현: FastSession/FastRecord 메서드 추가
|
|
|
|
## 사전 확인 (작업 전 반드시 수행)
|
|
|
|
1. `src/Infrastructure/Database/ExperionDbContext.cs` 파일을 열어 `ExperionDbService` 클래스를 찾는다.
|
|
2. 아래 항목을 확인하고 기록한다:
|
|
- [x] STEP 5가 완료되어 `IExperionDbService`에 Fast 메서드가 선언되어 있는가?
|
|
- [x] `ExperionDbService`가 `IExperionDbService`를 구현하는가?
|
|
- [x] 파일 상단에 `using System.Text.Json;` import가 있는가? → 없으면 추가
|
|
- [x] `CreateFastSessionAsync` 구현이 이미 있는가? → 있으면 해당 메서드 건너뜀
|
|
- [x] `BatchInsertFastRecordsAsync` 구현이 이미 있는가?
|
|
- [x] `ExportFastRecordsToCsvAsync` 구현이 이미 있는가?
|
|
|
|
---
|
|
|
|
## 작업 내용
|
|
|
|
**파일**: `src/Infrastructure/Database/ExperionDbContext.cs`
|
|
**위치**: `ExperionDbService` 클래스 내부 마지막 메서드 아래
|
|
|
|
```csharp
|
|
// ── FastSession / FastRecord ─────────────────────────────────────────────────
|
|
|
|
public async Task<FastSession> CreateFastSessionAsync(FastSessionCreateRequest request)
|
|
{
|
|
var session = new FastSession
|
|
{
|
|
Name = request.Name,
|
|
SamplingMs = request.SamplingMs,
|
|
DurationSec = request.DurationSec,
|
|
TagList = JsonSerializer.Serialize(request.TagList), // string[] → JSONB
|
|
StartedAt = DateTime.UtcNow,
|
|
Status = "Pending",
|
|
RowCount = 0,
|
|
RetentionDays = request.RetentionDays,
|
|
Pinned = false
|
|
};
|
|
_ctx.FastSessions.Add(session);
|
|
await _ctx.SaveChangesAsync();
|
|
return session;
|
|
}
|
|
|
|
public async Task UpdateFastSessionStatusAsync(int sessionId, string status)
|
|
{
|
|
var session = await _ctx.FastSessions.FindAsync(sessionId);
|
|
if (session == null) return;
|
|
session.Status = status;
|
|
if (status is "Completed" or "Cancelled" or "Failed" or "RowLimitReached")
|
|
session.EndedAt = DateTime.UtcNow;
|
|
await _ctx.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task UpdateFastSessionRowCountAsync(int sessionId, int rowCount)
|
|
{
|
|
var session = await _ctx.FastSessions.FindAsync(sessionId);
|
|
if (session == null) return;
|
|
session.RowCount = rowCount;
|
|
await _ctx.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task UpdateFastSessionPinnedAsync(int sessionId, bool pinned)
|
|
{
|
|
var session = await _ctx.FastSessions.FindAsync(sessionId);
|
|
if (session == null) return;
|
|
session.Pinned = pinned;
|
|
await _ctx.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task<FastSession?> GetFastSessionAsync(int sessionId)
|
|
=> await _ctx.FastSessions.FindAsync(sessionId);
|
|
|
|
public async Task<IEnumerable<FastSession>> GetFastSessionsAsync()
|
|
=> await _ctx.FastSessions.OrderBy(x => x.StartedAt).ToListAsync();
|
|
|
|
public async Task DeleteFastSessionAsync(int sessionId)
|
|
{
|
|
var session = await _ctx.FastSessions.FindAsync(sessionId);
|
|
if (session == null) return;
|
|
_ctx.FastSessions.Remove(session);
|
|
await _ctx.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task<IEnumerable<FastSession>> GetExpiredFastSessionsAsync()
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
return await _ctx.FastSessions
|
|
.Where(x => x.EndedAt != null
|
|
&& !x.Pinned
|
|
&& x.RetentionDays.HasValue
|
|
&& x.EndedAt.Value.AddDays(x.RetentionDays.Value) < now)
|
|
.OrderBy(x => x.EndedAt)
|
|
.ToListAsync();
|
|
}
|
|
|
|
public async Task<FastQueryResult> GetFastRecordsAsync(int sessionId, DateTime? from, DateTime? to)
|
|
{
|
|
var query = _ctx.FastRecords.Where(x => x.SessionId == sessionId);
|
|
if (from.HasValue) query = query.Where(x => x.RecordedAt >= from.Value);
|
|
if (to.HasValue) query = query.Where(x => x.RecordedAt <= to.Value);
|
|
|
|
var records = await query.OrderBy(x => x.RecordedAt).ToListAsync();
|
|
var tagNames = records.Select(x => x.TagName).Distinct().OrderBy(x => x).ToArray();
|
|
|
|
return new FastQueryResult(
|
|
SessionId: sessionId,
|
|
From: from ?? records.MinBy(x => x.RecordedAt)?.RecordedAt ?? DateTime.UtcNow,
|
|
To: to ?? records.MaxBy(x => x.RecordedAt)?.RecordedAt ?? DateTime.UtcNow,
|
|
TagNames: tagNames,
|
|
Items: records,
|
|
TotalCount: records.Count
|
|
);
|
|
}
|
|
|
|
public async Task BatchInsertFastRecordsAsync(IEnumerable<FastRecord> records)
|
|
{
|
|
var list = records.ToList();
|
|
if (list.Count == 0) return;
|
|
_ctx.FastRecords.AddRange(list);
|
|
await _ctx.SaveChangesAsync();
|
|
}
|
|
|
|
public async Task ExportFastRecordsToCsvAsync(int sessionId, Stream stream, DateTime? from, DateTime? to)
|
|
{
|
|
var result = await GetFastRecordsAsync(sessionId, from, to);
|
|
using var writer = new StreamWriter(stream, leaveOpen: true);
|
|
|
|
var header = "recorded_at," + string.Join(",", result.TagNames.Select(t => $"\"{t}\""));
|
|
await writer.WriteLineAsync(header);
|
|
|
|
var grouped = result.Items
|
|
.GroupBy(x => x.RecordedAt)
|
|
.OrderBy(x => x.Key)
|
|
.Select(g => new { Time = g.Key, Values = g.ToDictionary(r => r.TagName, r => r.Value) });
|
|
|
|
foreach (var g in grouped)
|
|
{
|
|
var row = g.Time.ToString("o") + ","
|
|
+ string.Join(",", result.TagNames.Select(t =>
|
|
g.Values.TryGetValue(t, out var v) ? $"\"{v}\"" : ""));
|
|
await writer.WriteLineAsync(row);
|
|
}
|
|
await writer.FlushAsync();
|
|
}
|
|
|
|
public async Task<string?> GetNodeIdByTagNameAsync(string tagName)
|
|
=> await _ctx.RealtimePoints
|
|
.Where(x => x.TagName == tagName)
|
|
.Select(x => x.NodeId)
|
|
.FirstOrDefaultAsync();
|
|
```
|
|
|
|
---
|
|
|
|
## 사후 확인 (작업 후 반드시 수행)
|
|
|
|
1. `ExperionDbContext.cs` 파일을 다시 열어 추가된 메서드 목록을 읽는다.
|
|
2. 아래 항목을 하나씩 확인한다:
|
|
- [x] `CreateFastSessionAsync` — `JsonSerializer.Serialize(request.TagList)` 사용하는가?
|
|
- [x] `UpdateFastSessionStatusAsync` — `EndedAt` 자동 설정 로직이 있는가?
|
|
- [x] `GetExpiredFastSessionsAsync` — `!x.Pinned` 조건이 있는가?
|
|
- [x] `GetFastRecordsAsync` — 반환 타입이 `FastQueryResult`인가?
|
|
- [x] `BatchInsertFastRecordsAsync` — 빈 리스트 early return이 있는가?
|
|
- [x] `ExportFastRecordsToCsvAsync` — PIVOT 그룹핑 로직이 있는가?
|
|
- [x] `GetNodeIdByTagNameAsync` — `_ctx.RealtimePoints` 에서 조회하는가?
|
|
- [x] 파일 상단에 `using System.Text.Json;` 가 있는가?
|
|
3. `dotnet build src/Web` 실행 → 에러 2개 (ExperionOpcClient 구현 미완료, STEP 7에서 해결)
|
|
4. ExperionOpcClient 구현 에러는 예상된 결과 (인터페이스만 추가한 단계)
|
|
|
|
---
|
|
|
|
## 완료 조건
|
|
- `dotnet build src/Web` 결과: 에러 2개 (ExperionOpcClient 구현 미완료, STEP 7에서 해결)
|
|
- Fast 관련 DB 메서드 12개 모두 구현됨 |