diff --git a/src/Core/Domain/Entities/ExperionEntities.cs b/src/Core/Domain/Entities/ExperionEntities.cs index d172c4c..7d28fca 100644 --- a/src/Core/Domain/Entities/ExperionEntities.cs +++ b/src/Core/Domain/Entities/ExperionEntities.cs @@ -1,4 +1,5 @@ using System.ComponentModel.DataAnnotations.Schema; +using Microsoft.EntityFrameworkCore; namespace ExperionCrawler.Core.Domain.Entities; @@ -7,6 +8,7 @@ public class ExperionTag { public int Id { get; set; } public string NodeId { get; set; } = string.Empty; + public string TagName { get; set; } = string.Empty; public string DisplayName { get; set; } = string.Empty; public string? Value { get; set; } public string? DataType { get; set; } @@ -94,3 +96,32 @@ public class ExperionStatusCodeInfo public ulong Decimal { get; set; } public string Description { get; set; } = string.Empty; } + +/// fastSession — 데이터 수집 세션 메타 +[Table("fast_session")] +public class FastSession +{ + [Column("id")] public int Id { get; set; } + [Column("name")] public string Name { get; set; } = string.Empty; + [Column("started_at")] public DateTime StartedAt { get; set; } + [Column("ended_at")] public DateTime? EndedAt { get; set; } + [Column("status")] public string Status { get; set; } = "Pending"; + // Status 허용값: Pending / Running / Completed / Cancelled / Failed / RowLimitReached + [Column("sampling_ms")] public int SamplingMs { get; set; } + [Column("duration_sec")] public int DurationSec { get; set; } + [Column("tag_list")] public string TagList { get; set; } = "[]"; // JSONB → string[] 직렬화 + [Column("row_count")] public int RowCount { get; set; } + [Column("retention_days")] public int? RetentionDays { get; set; } // null = 무한 보관 + [Column("pinned")] public bool Pinned { get; set; } +} + +/// fastRecord — 시계열 데이터 (Long 포맷: 태그 1행/시점) +[Table("fast_record")] +public class FastRecord +{ + [NotMapped] public int Id { get; set; } + [Column("session_id")] public int SessionId { get; set; } + [Column("recorded_at")] public DateTime RecordedAt { get; set; } + [Column("tagname")] public string TagName { get; set; } = string.Empty; + [Column("value")] public string? Value { get; set; } +} diff --git a/src/Infrastructure/Database/ExperionDbContext.cs b/src/Infrastructure/Database/ExperionDbContext.cs index 557b0f8..f353dd6 100644 --- a/src/Infrastructure/Database/ExperionDbContext.cs +++ b/src/Infrastructure/Database/ExperionDbContext.cs @@ -68,9 +68,8 @@ public class ExperionDbContext : DbContext modelBuilder.Entity(e => { - e.HasKey(x => x.Id); + e.HasKey(x => new { x.SessionId, x.RecordedAt, x.TagName }); e.HasIndex(x => x.SessionId); - e.HasIndex(x => new { x.SessionId, x.TagName, x.RecordedAt }); }); } } @@ -113,17 +112,17 @@ public class ExperionDbService : IExperionDbService await _ctx.Database.ExecuteSqlRawAsync(""" CREATE TABLE IF NOT EXISTS fast_record ( - id SERIAL PRIMARY KEY, session_id INTEGER NOT NULL REFERENCES fast_session(id) ON DELETE CASCADE, recorded_at TIMESTAMPTZ NOT NULL, tagname TEXT NOT NULL, - value TEXT + value TEXT, + PRIMARY KEY (session_id, recorded_at, tagname) ) """); // TimescaleDB hypertable 생성 (recorded_at 기준, chunk_interval = 1 day) await _ctx.Database.ExecuteSqlRawAsync(""" - SELECT create_hypertable('fast_record', 'recorded_at', if_not_exists => TRUE) + SELECT create_hypertable('fast_record', 'recorded_at', if_not_exists => TRUE, migrate_data => TRUE) """); await _ctx.Database.ExecuteSqlRawAsync("""