diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/CarModel.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/CarModel.cs
new file mode 100644
index 0000000..33f6814
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/CarModel.cs
@@ -0,0 +1,15 @@
+namespace Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
+
+///
+/// 车型维度表
+///
+public class CarModel
+{
+ public Guid Id { get; set; } = Guid.NewGuid();
+ public string Name { get; set; } = null!;
+ public bool IsDeleted { get; set; } = false;
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ // Navigation Properties
+ public ICollection TestScenarios { get; set; } = [];
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/ForeignObject.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/ForeignObject.cs
new file mode 100644
index 0000000..302d2c2
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/ForeignObject.cs
@@ -0,0 +1,15 @@
+namespace Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
+
+///
+/// 异物类型维度表
+///
+public class ForeignObject
+{
+ public Guid Id { get; set; } = Guid.NewGuid();
+ public string Name { get; set; } = null!;
+ public bool IsDeleted { get; set; } = false;
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ // Navigation Properties
+ public ICollection TestScenarios { get; set; } = [];
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/Manufacturer.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/Manufacturer.cs
new file mode 100644
index 0000000..5104ae9
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/Manufacturer.cs
@@ -0,0 +1,15 @@
+namespace Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
+
+///
+/// 车厂维度表
+///
+public class Manufacturer
+{
+ public Guid Id { get; set; } = Guid.NewGuid();
+ public string Name { get; set; } = null!;
+ public bool IsDeleted { get; set; } = false;
+ public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+
+ // Navigation Properties
+ public ICollection TestScenarios { get; set; } = [];
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/TestScenario.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/TestScenario.cs
index 4efd970..df62804 100644
--- a/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/TestScenario.cs
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Domain/Local/TestScenario.cs
@@ -6,6 +6,12 @@ namespace Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
public class TestScenario
{
public Guid Id { get; set; } = Guid.NewGuid();
+
+ // 新增维度字段
+ public Guid ManufacturerId { get; set; }
+ public Guid CarModelId { get; set; }
+ public Guid ForeignObjectId { get; set; }
+
public Guid TxPanelId { get; set; }
public Guid TxHardwareId { get; set; }
public Guid TxSoftwareId { get; set; }
@@ -18,6 +24,11 @@ public class TestScenario
public bool IsDeleted { get; set; } = false;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
+ // Navigation Properties - 新增维度表
+ public Manufacturer Manufacturer { get; set; } = null!;
+ public CarModel CarModel { get; set; } = null!;
+ public ForeignObject ForeignObject { get; set; } = null!;
+
// Navigation Properties - 维度表
public TxPanel TxPanel { get; set; } = null!;
public TxHardware TxHardware { get; set; } = null!;
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Gpulse.WCT.DataAnalyzer.Core.csproj b/src/Gpulse.WCT.DataAnalyzer.Core/Gpulse.WCT.DataAnalyzer.Core.csproj
index 453200b..575999b 100644
--- a/src/Gpulse.WCT.DataAnalyzer.Core/Gpulse.WCT.DataAnalyzer.Core.csproj
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Gpulse.WCT.DataAnalyzer.Core.csproj
@@ -20,6 +20,9 @@
+
+
+
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/Extensions/ServiceCollectionExtensions.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/Extensions/ServiceCollectionExtensions.cs
index 682aeff..52e2f79 100644
--- a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/Extensions/ServiceCollectionExtensions.cs
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/Extensions/ServiceCollectionExtensions.cs
@@ -4,6 +4,7 @@ using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
using Gpulse.WCT.DataAnalyzer.Core.Application.Parsing;
using Gpulse.WCT.DataAnalyzer.Core.Application.Analysis;
using Gpulse.WCT.DataAnalyzer.Core.Application;
+using Gpulse.WCT.DataAnalyzer.Core.Application.Exporting;
namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.Extensions;
@@ -63,6 +64,9 @@ public static class ServiceCollectionExtensions
services.AddScoped();
services.AddScoped();
+ // Excel Export
+ services.AddScoped();
+
return services;
}
}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/CarModelConfiguration.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/CarModelConfiguration.cs
new file mode 100644
index 0000000..acbdc48
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/CarModelConfiguration.cs
@@ -0,0 +1,36 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
+
+namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData.Configurations;
+
+public class CarModelConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("car_model");
+
+ builder.HasKey(e => e.Id);
+ builder.Property(e => e.Id).HasColumnName("id");
+
+ builder.Property(e => e.Name)
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnName("name");
+
+ builder.Property(e => e.IsDeleted)
+ .IsRequired()
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ builder.Property(e => e.CreatedAt)
+ .IsRequired()
+ .HasDefaultValueSql("datetime('now')")
+ .HasColumnName("created_at");
+
+ builder.HasIndex(e => e.Name).IsUnique();
+
+ // 全局查询过滤器
+ builder.HasQueryFilter(e => !e.IsDeleted);
+ }
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/ForeignObjectConfiguration.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/ForeignObjectConfiguration.cs
new file mode 100644
index 0000000..ac3c9f3
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/ForeignObjectConfiguration.cs
@@ -0,0 +1,36 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
+
+namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData.Configurations;
+
+public class ForeignObjectConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("foreign_object");
+
+ builder.HasKey(e => e.Id);
+ builder.Property(e => e.Id).HasColumnName("id");
+
+ builder.Property(e => e.Name)
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnName("name");
+
+ builder.Property(e => e.IsDeleted)
+ .IsRequired()
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ builder.Property(e => e.CreatedAt)
+ .IsRequired()
+ .HasDefaultValueSql("datetime('now')")
+ .HasColumnName("created_at");
+
+ builder.HasIndex(e => e.Name).IsUnique();
+
+ // 全局查询过滤器
+ builder.HasQueryFilter(e => !e.IsDeleted);
+ }
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/ManufacturerConfiguration.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/ManufacturerConfiguration.cs
new file mode 100644
index 0000000..790828d
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/ManufacturerConfiguration.cs
@@ -0,0 +1,36 @@
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Metadata.Builders;
+using Gpulse.WCT.DataAnalyzer.Core.Domain.Local;
+
+namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData.Configurations;
+
+public class ManufacturerConfiguration : IEntityTypeConfiguration
+{
+ public void Configure(EntityTypeBuilder builder)
+ {
+ builder.ToTable("manufacturer");
+
+ builder.HasKey(e => e.Id);
+ builder.Property(e => e.Id).HasColumnName("id");
+
+ builder.Property(e => e.Name)
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnName("name");
+
+ builder.Property(e => e.IsDeleted)
+ .IsRequired()
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ builder.Property(e => e.CreatedAt)
+ .IsRequired()
+ .HasDefaultValueSql("datetime('now')")
+ .HasColumnName("created_at");
+
+ builder.HasIndex(e => e.Name).IsUnique();
+
+ // 全局查询过滤器
+ builder.HasQueryFilter(e => !e.IsDeleted);
+ }
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/TestScenarioConfiguration.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/TestScenarioConfiguration.cs
index eea344d..33e91c3 100644
--- a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/TestScenarioConfiguration.cs
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/Configurations/TestScenarioConfiguration.cs
@@ -13,6 +13,15 @@ public class TestScenarioConfiguration : IEntityTypeConfiguration
builder.HasKey(e => e.Id);
builder.Property(e => e.Id).HasColumnName("id");
+ // 新增维度字段
+ builder.Property(e => e.ManufacturerId)
+ .IsRequired()
+ .HasColumnName("manufacturer_id");
+
+ builder.Property(e => e.CarModelId)
+ .IsRequired()
+ .HasColumnName("car_model_id");
+
builder.Property(e => e.TxPanelId)
.IsRequired()
.HasColumnName("tx_panel_id");
@@ -29,6 +38,10 @@ public class TestScenarioConfiguration : IEntityTypeConfiguration
.IsRequired()
.HasColumnName("rx_type_id");
+ builder.Property(e => e.ForeignObjectId)
+ .IsRequired()
+ .HasColumnName("foreign_object_id");
+
builder.Property(e => e.TestPurpose)
.HasMaxLength(200)
.HasColumnName("test_purpose");
@@ -63,18 +76,31 @@ public class TestScenarioConfiguration : IEntityTypeConfiguration
.HasColumnName("created_at");
// 索引
+ builder.HasIndex(e => e.ManufacturerId).HasDatabaseName("idx_scenario_manufacturer");
+ builder.HasIndex(e => e.CarModelId).HasDatabaseName("idx_scenario_car_model");
builder.HasIndex(e => e.TxPanelId).HasDatabaseName("idx_scenario_tx_panel");
builder.HasIndex(e => e.TxHardwareId).HasDatabaseName("idx_scenario_tx_hardware");
builder.HasIndex(e => e.TxSoftwareId).HasDatabaseName("idx_scenario_tx_software");
builder.HasIndex(e => e.RxTypeId).HasDatabaseName("idx_scenario_rx_type");
+ builder.HasIndex(e => e.ForeignObjectId).HasDatabaseName("idx_scenario_foreign_object");
builder.HasIndex(e => e.TestDate).HasDatabaseName("idx_scenario_test_date");
- // 唯一约束
- builder.HasIndex(e => new { e.TxPanelId, e.TxHardwareId, e.TxSoftwareId, e.RxTypeId, e.TestPurpose, e.TestDate, e.TestSequence })
+ // 唯一约束 - 包含所有维度字段
+ builder.HasIndex(e => new { e.ManufacturerId, e.CarModelId, e.TxPanelId, e.TxHardwareId, e.TxSoftwareId, e.RxTypeId, e.ForeignObjectId, e.TestPurpose, e.TestDate, e.TestSequence })
.IsUnique()
.HasDatabaseName("uq_scenario");
- // 关系配置 - 维度表
+ // 关系配置 - 新增维度表
+ builder.HasOne(e => e.Manufacturer)
+ .WithMany(e => e.TestScenarios)
+ .HasForeignKey(e => e.ManufacturerId)
+ .OnDelete(DeleteBehavior.Restrict);
+
+ builder.HasOne(e => e.CarModel)
+ .WithMany(e => e.TestScenarios)
+ .HasForeignKey(e => e.CarModelId)
+ .OnDelete(DeleteBehavior.Restrict);
+
builder.HasOne(e => e.TxPanel)
.WithMany(e => e.TestScenarios)
.HasForeignKey(e => e.TxPanelId)
@@ -95,6 +121,11 @@ public class TestScenarioConfiguration : IEntityTypeConfiguration
.HasForeignKey(e => e.RxTypeId)
.OnDelete(DeleteBehavior.Restrict);
+ builder.HasOne(e => e.ForeignObject)
+ .WithMany(e => e.TestScenarios)
+ .HasForeignKey(e => e.ForeignObjectId)
+ .OnDelete(DeleteBehavior.Restrict);
+
// 关系配置 - 数据记录
builder.HasMany(e => e.QfodRecords)
.WithOne(e => e.Scenario)
@@ -109,4 +140,4 @@ public class TestScenarioConfiguration : IEntityTypeConfiguration
// 全局查询过滤器
builder.HasQueryFilter(e => !e.IsDeleted);
}
-}
\ No newline at end of file
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/DatabaseMigrationInitializer.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/DatabaseMigrationInitializer.cs
new file mode 100644
index 0000000..647711d
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/DatabaseMigrationInitializer.cs
@@ -0,0 +1,186 @@
+using Microsoft.EntityFrameworkCore;
+using System.Linq;
+
+namespace Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
+
+///
+/// 数据库迁移初始化器。
+/// 替代直接调用 EnsureCreated(),统一处理两类场景:
+///
+/// - 全新库:EF MigrateAsync() 按 InitialCreate 建全部表。
+/// - 既有 EnsureCreated() 库(无 __EFMigrationsHistory,但表已存在):
+/// 若直接 MigrateAsync() 会因 CREATE TABLE 撞既有表而失败,
+/// 故先检测既有表并补齐本变更新增的 3 张维度表 + test_scenario 的 3 列 NOT NULL FK,
+/// 再把 InitialCreate 标记为已应用(baseline),使后续迁移可增量执行。
+///
+///
+public static class DatabaseMigrationInitializer
+{
+ ///
+ /// 迁移或升级既有库。对 的库执行迁移;
+ /// 若检测到既有 EnsureCreated 库(无迁移历史但有表),执行基线升级后标记迁移已应用。
+ ///
+ public static async Task MigrateOrUpgradeAsync(TContext context)
+ where TContext : DbContext
+ {
+ var database = context.Database;
+
+ // 既有 EnsureCreated 库:无迁移历史表,但已有业务表 → 视为已建表但未迁移。
+ if (await IsLegacyEnsureCreatedDatabaseAsync(context))
+ {
+ await UpgradeLegacyDatabaseAsync(context);
+ await MarkAllMigrationsAsAppliedAsync(context);
+ }
+
+ // 常规迁移:全新库直接建表;已基线标记的既有库跳过已应用迁移。
+ await database.MigrateAsync();
+ }
+
+ ///
+ /// 判断是否为既有的 EnsureCreated 库:无 __EFMigrationsHistory 表,且至少有一张业务表。
+ ///
+ private static async Task IsLegacyEnsureCreatedDatabaseAsync(TContext context)
+ where TContext : DbContext
+ {
+ // 库不存在视为全新库,交给 MigrateAsync 建表
+ if (!await context.Database.CanConnectAsync())
+ return false;
+
+ // 存在迁移历史表 → 非 legacy,常规 Migrate
+ var historyCount = await context.Database.SqlQueryRaw(
+ "SELECT count(*) AS Value FROM sqlite_master WHERE type='table' AND name='__EFMigrationsHistory'").FirstOrDefaultAsync();
+ if (historyCount > 0)
+ return false;
+
+ // 无历史表但有任意业务表(非 __EFMigrationsHistory)→ EnsureCreated 既有库
+ var tableCount = await context.Database.SqlQueryRaw(
+ "SELECT count(*) AS Value FROM sqlite_master WHERE type='table' AND name NOT LIKE '__EF%'").FirstOrDefaultAsync();
+ return tableCount > 0;
+ }
+
+ ///
+ /// 升级既有库:补齐本变更新增的维度表与 test_scenario 的 NOT NULL FK 列。
+ /// 按 "可空加列 → 回填默认维度行 → 改 NOT NULL → 重建唯一索引" 顺序,
+ /// 兼容 SQLite 无法原地 ALTER COLUMN 的限制(新建默认维度行后回填既有场景)。
+ ///
+ private static async Task UpgradeLegacyDatabaseAsync(TContext context)
+ where TContext : DbContext
+ {
+ var db = context.Database;
+
+ // 仅处理 WctMinerDbContext(ReleaseDbContext 无本次 schema 变更)
+ if (context is not WctMinerDbContext)
+ return;
+
+ // 1. 若已升级(存在 foreign_object 表),直接返回,避免重复
+ var alreadyUpgraded = await TableExistsAsync(context, "foreign_object");
+ if (alreadyUpgraded)
+ return;
+
+ // 2. 建三张维度表(结构照搬 ManufacturerConfiguration 等:id/name/is_deleted/created_at + 唯一索引)
+ await db.ExecuteSqlRawAsync(@"
+ CREATE TABLE IF NOT EXISTS manufacturer (
+ id TEXT NOT NULL PRIMARY KEY,
+ name TEXT NOT NULL,
+ is_deleted INTEGER NOT NULL DEFAULT 0,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_manufacturer_name ON manufacturer (name) WHERE is_deleted = 0;
+
+ CREATE TABLE IF NOT EXISTS car_model (
+ id TEXT NOT NULL PRIMARY KEY,
+ name TEXT NOT NULL,
+ is_deleted INTEGER NOT NULL DEFAULT 0,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_car_model_name ON car_model (name) WHERE is_deleted = 0;
+
+ CREATE TABLE IF NOT EXISTS foreign_object (
+ id TEXT NOT NULL PRIMARY KEY,
+ name TEXT NOT NULL,
+ is_deleted INTEGER NOT NULL DEFAULT 0,
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
+ );
+ CREATE UNIQUE INDEX IF NOT EXISTS uq_foreign_object_name ON foreign_object (name) WHERE is_deleted = 0;
+ ");
+
+ // 3. 给 test_scenario 加可空 FK 列(若已存在则跳过 —— SQLite 不报错时静默)
+ await AddColumnIfNotExistsAsync(context, "test_scenario", "manufacturer_id", "TEXT");
+ await AddColumnIfNotExistsAsync(context, "test_scenario", "car_model_id", "TEXT");
+ await AddColumnIfNotExistsAsync(context, "test_scenario", "foreign_object_id", "TEXT");
+
+ // 4. 插入默认维度行("未知")并回填既有 test_scenario 行
+ await db.ExecuteSqlRawAsync(@"
+ INSERT OR IGNORE INTO manufacturer (id, name) VALUES (lower(hex(randomblob(16))), '未知');
+ INSERT OR IGNORE INTO car_model (id, name) VALUES (lower(hex(randomblob(16))), '未知');
+ INSERT OR IGNORE INTO foreign_object (id, name) VALUES (lower(hex(randomblob(16))), '未知');
+
+ UPDATE test_scenario
+ SET manufacturer_id = (SELECT id FROM manufacturer WHERE name = '未知')
+ WHERE manufacturer_id IS NULL;
+ UPDATE test_scenario
+ SET car_model_id = (SELECT id FROM car_model WHERE name = '未知')
+ WHERE car_model_id IS NULL;
+ UPDATE test_scenario
+ SET foreign_object_id = (SELECT id FROM foreign_object WHERE name = '未知')
+ WHERE foreign_object_id IS NULL;
+ ");
+
+ // 5. 重建唯一索引 uq_scenario 以包含新列(先删后建;既有索引名可能存在)
+ await db.ExecuteSqlRawAsync("DROP INDEX IF EXISTS uq_scenario;");
+ await db.ExecuteSqlRawAsync(@"
+ CREATE UNIQUE INDEX uq_scenario ON test_scenario (
+ manufacturer_id, car_model_id, tx_panel_id, tx_hardware_id, tx_software_id,
+ rx_type_id, foreign_object_id, test_purpose, test_date, test_sequence
+ ) WHERE is_deleted = 0;
+ ");
+
+ // 6. NOT NULL 约束:SQLite 无法原地 ALTER,EF 后续 Migrate 的 baseline 会跳过列重建,
+ // 此处通过重建 test_scenario 表加约束成本高且风险大;既有行已全部回填非空,
+ // 保留可空状态不影响查询正确性,由后续迁移统一收口。
+ // (如需严格 NOT NULL,需 SQLite 表重建,此处留注释说明取舍。)
+ }
+
+ /// 把当前 context 的所有迁移标记为已应用(写入迁移历史表,跳过执行)。
+ private static async Task MarkAllMigrationsAsAppliedAsync(TContext context)
+ where TContext : DbContext
+ {
+ var database = context.Database;
+
+ // 确保迁移历史表存在(既有 EnsureCreated 库尚无此表),否则 GetPendingMigrationsAsync 会抛
+ // "no such table: __EFMigrationsHistory"。
+ await database.ExecuteSqlRawAsync(@"
+ CREATE TABLE IF NOT EXISTS ""__EFMigrationsHistory"" (
+ ""MigrationId"" TEXT NOT NULL CONSTRAINT ""PK___EFMigrationsHistory"" PRIMARY KEY,
+ ""ProductVersion"" TEXT NOT NULL
+ );
+ ");
+
+ // 列出模型定义的全部迁移,逐条写入历史表(视为已应用),使后续 Migrate 跳过它们。
+ var migrations = database.GetMigrations().ToList();
+ var productVersion = typeof(DbContext).Assembly.GetName().Version?.ToString() ?? "8.0.0";
+ foreach (var migration in migrations)
+ {
+ await database.ExecuteSqlRawAsync(
+ "INSERT OR IGNORE INTO __EFMigrationsHistory (MigrationId, ProductVersion) VALUES ({0}, {1})",
+ migration, productVersion);
+ }
+ }
+
+ private static async Task TableExistsAsync(TContext context, string tableName)
+ where TContext : DbContext
+ {
+ var count = await context.Database.SqlQueryRaw(
+ $"SELECT count(*) AS Value FROM sqlite_master WHERE type='table' AND name = '{tableName}'").FirstOrDefaultAsync();
+ return count > 0;
+ }
+
+ private static async Task AddColumnIfNotExistsAsync(TContext context, string table, string column, string type)
+ where TContext : DbContext
+ {
+ var exists = (await context.Database.SqlQueryRaw(
+ $"SELECT count(*) AS Value FROM pragma_table_info('{table}') WHERE name = '{column}'").FirstOrDefaultAsync()) > 0;
+ if (!exists)
+ await context.Database.ExecuteSqlRawAsync($"ALTER TABLE {table} ADD COLUMN {column} {type};");
+ }
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/DbContextFactory.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/DbContextFactory.cs
index 8020743..f8f871f 100644
--- a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/DbContextFactory.cs
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/DbContextFactory.cs
@@ -45,9 +45,8 @@ public class DbContextFactory : IDbContextFactory
var context = new WctMinerDbContext(optionsBuilder.Options);
- // 确保数据库和表结构已创建(适用于 SQLite)
- context.Database.EnsureCreated();
-
+ // 迁移(Migrate)只在启动期由 Program.Main / CliTestHost.CreateAsync 触发,
+ // 不在每次 scope 解析时执行(避免每条命令都跑迁移)。
return context;
}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/WctMinerDbContext.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/WctMinerDbContext.cs
index 2743999..0006ca9 100644
--- a/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/WctMinerDbContext.cs
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Infrastructure/LocalData/WctMinerDbContext.cs
@@ -12,10 +12,13 @@ public class WctMinerDbContext : DbContext
: base(options) { }
// 维度表
+ public DbSet Manufacturers => Set();
+ public DbSet CarModels => Set();
public DbSet TxPanels => Set();
public DbSet TxHardwares => Set();
public DbSet TxSoftwares => Set();
public DbSet RxTypes => Set();
+ public DbSet ForeignObjects => Set();
// 事实表
public DbSet TestScenarios => Set();
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/20260821085318_InitialCreate.Designer.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/20260821085318_InitialCreate.Designer.cs
new file mode 100644
index 0000000..f998adf
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/20260821085318_InitialCreate.Designer.cs
@@ -0,0 +1,805 @@
+//
+using System;
+using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Gpulse.WCT.DataAnalyzer.Core.Migrations
+{
+ [DbContext(typeof(WctMinerDbContext))]
+ [Migration("20260821085318_InitialCreate")]
+ partial class InitialCreate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.CarModel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("car_model", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.ForeignObject", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("foreign_object", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.Manufacturer", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("manufacturer", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.PlossRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("DeltaP")
+ .HasColumnType("INTEGER")
+ .HasColumnName("delta_p");
+
+ b.Property("Field1")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_1");
+
+ b.Property("Field10")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("field_10");
+
+ b.Property("Field11")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_11");
+
+ b.Property("Field12")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_12");
+
+ b.Property("Field13")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_13");
+
+ b.Property("Field14")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("field_14");
+
+ b.Property("Field15")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("field_15");
+
+ b.Property("Field16")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("field_16");
+
+ b.Property("Field2")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_2");
+
+ b.Property("Field3")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_3");
+
+ b.Property("Field4")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_4");
+
+ b.Property("Field5")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_5");
+
+ b.Property("Field6")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_6");
+
+ b.Property("Field7")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_7");
+
+ b.Property("Field8")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_8");
+
+ b.Property("Field9")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("field_9");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("PowLoss")
+ .HasColumnType("INTEGER")
+ .HasColumnName("pow_loss");
+
+ b.Property("ScenarioId")
+ .HasColumnType("TEXT")
+ .HasColumnName("scenario_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeltaP")
+ .HasDatabaseName("idx_ploss_delta_p");
+
+ b.HasIndex("Field10")
+ .HasDatabaseName("idx_ploss_field_10");
+
+ b.HasIndex("Field2")
+ .HasDatabaseName("idx_ploss_field_2");
+
+ b.HasIndex("Field7")
+ .HasDatabaseName("idx_ploss_field_7");
+
+ b.HasIndex("Field8")
+ .HasDatabaseName("idx_ploss_field_8");
+
+ b.HasIndex("PowLoss")
+ .HasDatabaseName("idx_ploss_pow_loss");
+
+ b.HasIndex("ScenarioId")
+ .HasDatabaseName("idx_ploss_scenario");
+
+ b.ToTable("ploss_record", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.QfodRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("ChargerIndex")
+ .HasColumnType("INTEGER")
+ .HasColumnName("charger_index");
+
+ b.Property("CoilIndex")
+ .HasColumnType("INTEGER")
+ .HasColumnName("coil_index");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("CurrentQ")
+ .HasColumnType("REAL")
+ .HasColumnName("current_q");
+
+ b.Property("DeltaQ")
+ .HasColumnType("INTEGER")
+ .HasColumnName("delta_q");
+
+ b.Property("FieldF")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue((byte)0)
+ .HasColumnName("field_f");
+
+ b.Property("FodType")
+ .HasColumnType("INTEGER")
+ .HasColumnName("fod_type");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("RawQ")
+ .HasColumnType("REAL")
+ .HasColumnName("raw_q");
+
+ b.Property("ScenarioId")
+ .HasColumnType("TEXT")
+ .HasColumnName("scenario_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("DeltaQ")
+ .HasDatabaseName("idx_qfod_delta_q");
+
+ b.HasIndex("FodType")
+ .HasDatabaseName("idx_qfod_fod_type");
+
+ b.HasIndex("ScenarioId")
+ .HasDatabaseName("idx_qfod_scenario");
+
+ b.HasIndex("ChargerIndex", "CoilIndex")
+ .HasDatabaseName("idx_qfod_charger_coil");
+
+ b.ToTable("qfod_record", null, t =>
+ {
+ t.HasCheckConstraint("chk_qfod_charger", "charger_index IN (0, 1)");
+
+ t.HasCheckConstraint("chk_qfod_coil", "coil_index IN (0, 1, 2)");
+ });
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.RxType", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("rx_type", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TestScenario", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CarModelId")
+ .HasColumnType("TEXT")
+ .HasColumnName("car_model_id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("ForeignObjectId")
+ .HasColumnType("TEXT")
+ .HasColumnName("foreign_object_id");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("ManufacturerId")
+ .HasColumnType("TEXT")
+ .HasColumnName("manufacturer_id");
+
+ b.Property("PlossCount")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("ploss_count");
+
+ b.Property("QfodCount")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("qfod_count");
+
+ b.Property("RxTypeId")
+ .HasColumnType("TEXT")
+ .HasColumnName("rx_type_id");
+
+ b.Property("TestDate")
+ .HasColumnType("TEXT")
+ .HasColumnName("test_date");
+
+ b.Property("TestPurpose")
+ .HasMaxLength(200)
+ .HasColumnType("TEXT")
+ .HasColumnName("test_purpose");
+
+ b.Property("TestSequence")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(1)
+ .HasColumnName("test_sequence");
+
+ b.Property("TxHardwareId")
+ .HasColumnType("TEXT")
+ .HasColumnName("tx_hardware_id");
+
+ b.Property("TxPanelId")
+ .HasColumnType("TEXT")
+ .HasColumnName("tx_panel_id");
+
+ b.Property("TxSoftwareId")
+ .HasColumnType("TEXT")
+ .HasColumnName("tx_software_id");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CarModelId")
+ .HasDatabaseName("idx_scenario_car_model");
+
+ b.HasIndex("ForeignObjectId")
+ .HasDatabaseName("idx_scenario_foreign_object");
+
+ b.HasIndex("ManufacturerId")
+ .HasDatabaseName("idx_scenario_manufacturer");
+
+ b.HasIndex("RxTypeId")
+ .HasDatabaseName("idx_scenario_rx_type");
+
+ b.HasIndex("TestDate")
+ .HasDatabaseName("idx_scenario_test_date");
+
+ b.HasIndex("TxHardwareId")
+ .HasDatabaseName("idx_scenario_tx_hardware");
+
+ b.HasIndex("TxPanelId")
+ .HasDatabaseName("idx_scenario_tx_panel");
+
+ b.HasIndex("TxSoftwareId")
+ .HasDatabaseName("idx_scenario_tx_software");
+
+ b.HasIndex("ManufacturerId", "CarModelId", "TxPanelId", "TxHardwareId", "TxSoftwareId", "RxTypeId", "ForeignObjectId", "TestPurpose", "TestDate", "TestSequence")
+ .IsUnique()
+ .HasDatabaseName("uq_scenario");
+
+ b.ToTable("test_scenario", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxHardware", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Version")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("version");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Version")
+ .IsUnique();
+
+ b.ToTable("tx_hardware", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxPanel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("tx_panel", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxSoftware", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Version")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("version");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Version")
+ .IsUnique();
+
+ b.ToTable("tx_software", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Release.ChargingParameterRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CarFactory")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT")
+ .HasColumnName("car_factory");
+
+ b.Property("CarModel")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT")
+ .HasColumnName("car_model");
+
+ b.Property("CreatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at");
+
+ b.Property("PhoneBrand")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT")
+ .HasColumnName("phone_brand");
+
+ b.Property("PhoneModel")
+ .IsRequired()
+ .HasMaxLength(128)
+ .HasColumnType("TEXT")
+ .HasColumnName("phone_model");
+
+ b.Property("Power1000mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_1000mw");
+
+ b.Property("Power1250mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_1250mw");
+
+ b.Property("Power1500mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_1500mw");
+
+ b.Property("Power1750mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_1750mw");
+
+ b.Property("Power2000mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_2000mw");
+
+ b.Property("Power2250mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_2250mw");
+
+ b.Property("Power350mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_350mw");
+
+ b.Property("Power500mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_500mw");
+
+ b.Property("Power750mW")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("power_750mw");
+
+ b.Property("PqCoefficient")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("pq_coefficient");
+
+ b.Property("QBaseValue")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("q_base_value");
+
+ b.Property("QValue")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("q_value");
+
+ b.Property("ResonanceFrequency")
+ .HasPrecision(18, 6)
+ .HasColumnType("REAL")
+ .HasColumnName("resonance_frequency");
+
+ b.Property("UpdatedAt")
+ .HasColumnType("TEXT")
+ .HasColumnName("updated_at");
+
+ b.HasKey("Id");
+
+ b.HasIndex("CarFactory", "CarModel", "PhoneBrand", "PhoneModel")
+ .IsUnique()
+ .HasDatabaseName("uq_charging_parameter_business_key");
+
+ b.ToTable("charging_parameter", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.PlossRecord", b =>
+ {
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TestScenario", "Scenario")
+ .WithMany("PlossRecords")
+ .HasForeignKey("ScenarioId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Scenario");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.QfodRecord", b =>
+ {
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TestScenario", "Scenario")
+ .WithMany("QfodRecords")
+ .HasForeignKey("ScenarioId")
+ .OnDelete(DeleteBehavior.Cascade)
+ .IsRequired();
+
+ b.Navigation("Scenario");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TestScenario", b =>
+ {
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.CarModel", "CarModel")
+ .WithMany("TestScenarios")
+ .HasForeignKey("CarModelId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.ForeignObject", "ForeignObject")
+ .WithMany("TestScenarios")
+ .HasForeignKey("ForeignObjectId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.Manufacturer", "Manufacturer")
+ .WithMany("TestScenarios")
+ .HasForeignKey("ManufacturerId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.RxType", "RxType")
+ .WithMany("TestScenarios")
+ .HasForeignKey("RxTypeId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxHardware", "TxHardware")
+ .WithMany("TestScenarios")
+ .HasForeignKey("TxHardwareId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxPanel", "TxPanel")
+ .WithMany("TestScenarios")
+ .HasForeignKey("TxPanelId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.HasOne("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxSoftware", "TxSoftware")
+ .WithMany("TestScenarios")
+ .HasForeignKey("TxSoftwareId")
+ .OnDelete(DeleteBehavior.Restrict)
+ .IsRequired();
+
+ b.Navigation("CarModel");
+
+ b.Navigation("ForeignObject");
+
+ b.Navigation("Manufacturer");
+
+ b.Navigation("RxType");
+
+ b.Navigation("TxHardware");
+
+ b.Navigation("TxPanel");
+
+ b.Navigation("TxSoftware");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.CarModel", b =>
+ {
+ b.Navigation("TestScenarios");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.ForeignObject", b =>
+ {
+ b.Navigation("TestScenarios");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.Manufacturer", b =>
+ {
+ b.Navigation("TestScenarios");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.RxType", b =>
+ {
+ b.Navigation("TestScenarios");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TestScenario", b =>
+ {
+ b.Navigation("PlossRecords");
+
+ b.Navigation("QfodRecords");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxHardware", b =>
+ {
+ b.Navigation("TestScenarios");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxPanel", b =>
+ {
+ b.Navigation("TestScenarios");
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.TxSoftware", b =>
+ {
+ b.Navigation("TestScenarios");
+ });
+#pragma warning restore 612, 618
+ }
+ }
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/20260821085318_InitialCreate.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/20260821085318_InitialCreate.cs
new file mode 100644
index 0000000..806e739
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/20260821085318_InitialCreate.cs
@@ -0,0 +1,463 @@
+using System;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+#nullable disable
+
+namespace Gpulse.WCT.DataAnalyzer.Core.Migrations
+{
+ ///
+ public partial class InitialCreate : Migration
+ {
+ ///
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.CreateTable(
+ name: "car_model",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ name = table.Column(type: "TEXT", maxLength: 50, nullable: false),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_car_model", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "charging_parameter",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ car_factory = table.Column(type: "TEXT", maxLength: 128, nullable: false),
+ car_model = table.Column(type: "TEXT", maxLength: 128, nullable: false),
+ phone_brand = table.Column(type: "TEXT", maxLength: 128, nullable: false),
+ phone_model = table.Column(type: "TEXT", maxLength: 128, nullable: false),
+ power_350mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ power_500mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ power_750mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ power_1000mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ power_1250mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ power_1500mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ power_1750mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ power_2000mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ power_2250mw = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ q_value = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ q_base_value = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ pq_coefficient = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ resonance_frequency = table.Column(type: "REAL", precision: 18, scale: 6, nullable: true),
+ created_at = table.Column(type: "TEXT", nullable: false),
+ updated_at = table.Column(type: "TEXT", nullable: false)
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_charging_parameter", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "foreign_object",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ name = table.Column(type: "TEXT", maxLength: 50, nullable: false),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_foreign_object", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "manufacturer",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ name = table.Column(type: "TEXT", maxLength: 100, nullable: false),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_manufacturer", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "rx_type",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ name = table.Column(type: "TEXT", maxLength: 100, nullable: false),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_rx_type", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "tx_hardware",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ version = table.Column(type: "TEXT", maxLength: 50, nullable: false),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_tx_hardware", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "tx_panel",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ name = table.Column(type: "TEXT", maxLength: 50, nullable: false),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_tx_panel", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "tx_software",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ version = table.Column(type: "TEXT", maxLength: 50, nullable: false),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_tx_software", x => x.id);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "test_scenario",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ manufacturer_id = table.Column(type: "TEXT", nullable: false),
+ car_model_id = table.Column(type: "TEXT", nullable: false),
+ foreign_object_id = table.Column(type: "TEXT", nullable: false),
+ tx_panel_id = table.Column(type: "TEXT", nullable: false),
+ tx_hardware_id = table.Column(type: "TEXT", nullable: false),
+ tx_software_id = table.Column(type: "TEXT", nullable: false),
+ rx_type_id = table.Column(type: "TEXT", nullable: false),
+ test_purpose = table.Column(type: "TEXT", maxLength: 200, nullable: true),
+ test_date = table.Column(type: "TEXT", nullable: false),
+ test_sequence = table.Column(type: "INTEGER", nullable: false, defaultValue: 1),
+ qfod_count = table.Column(type: "INTEGER", nullable: false, defaultValue: 0),
+ ploss_count = table.Column(type: "INTEGER", nullable: false, defaultValue: 0),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_test_scenario", x => x.id);
+ table.ForeignKey(
+ name: "FK_test_scenario_car_model_car_model_id",
+ column: x => x.car_model_id,
+ principalTable: "car_model",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_test_scenario_foreign_object_foreign_object_id",
+ column: x => x.foreign_object_id,
+ principalTable: "foreign_object",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_test_scenario_manufacturer_manufacturer_id",
+ column: x => x.manufacturer_id,
+ principalTable: "manufacturer",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_test_scenario_rx_type_rx_type_id",
+ column: x => x.rx_type_id,
+ principalTable: "rx_type",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_test_scenario_tx_hardware_tx_hardware_id",
+ column: x => x.tx_hardware_id,
+ principalTable: "tx_hardware",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_test_scenario_tx_panel_tx_panel_id",
+ column: x => x.tx_panel_id,
+ principalTable: "tx_panel",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Restrict);
+ table.ForeignKey(
+ name: "FK_test_scenario_tx_software_tx_software_id",
+ column: x => x.tx_software_id,
+ principalTable: "tx_software",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Restrict);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "ploss_record",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ scenario_id = table.Column(type: "TEXT", nullable: false),
+ pow_loss = table.Column(type: "INTEGER", nullable: true),
+ delta_p = table.Column(type: "INTEGER", nullable: true),
+ field_1 = table.Column(type: "INTEGER", nullable: false),
+ field_2 = table.Column(type: "INTEGER", nullable: false),
+ field_3 = table.Column(type: "INTEGER", nullable: false),
+ field_4 = table.Column(type: "INTEGER", nullable: false),
+ field_5 = table.Column(type: "INTEGER", nullable: false),
+ field_6 = table.Column(type: "INTEGER", nullable: false),
+ field_7 = table.Column(type: "INTEGER", nullable: false),
+ field_8 = table.Column(type: "INTEGER", nullable: false),
+ field_9 = table.Column(type: "INTEGER", nullable: false, defaultValue: 0),
+ field_10 = table.Column(type: "INTEGER", nullable: false, defaultValue: 0),
+ field_11 = table.Column(type: "INTEGER", nullable: false),
+ field_12 = table.Column(type: "INTEGER", nullable: false),
+ field_13 = table.Column(type: "INTEGER", nullable: false),
+ field_14 = table.Column(type: "INTEGER", nullable: false, defaultValue: 0),
+ field_15 = table.Column(type: "INTEGER", nullable: false, defaultValue: 0),
+ field_16 = table.Column(type: "INTEGER", nullable: false, defaultValue: 0),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_ploss_record", x => x.id);
+ table.ForeignKey(
+ name: "FK_ploss_record_test_scenario_scenario_id",
+ column: x => x.scenario_id,
+ principalTable: "test_scenario",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateTable(
+ name: "qfod_record",
+ columns: table => new
+ {
+ id = table.Column(type: "TEXT", nullable: false),
+ scenario_id = table.Column(type: "TEXT", nullable: false),
+ charger_index = table.Column(type: "INTEGER", nullable: false),
+ coil_index = table.Column(type: "INTEGER", nullable: false),
+ delta_q = table.Column(type: "INTEGER", nullable: false),
+ current_q = table.Column(type: "REAL", nullable: false),
+ raw_q = table.Column(type: "REAL", nullable: false),
+ fod_type = table.Column(type: "INTEGER", nullable: false),
+ field_f = table.Column(type: "INTEGER", nullable: false, defaultValue: (byte)0),
+ is_deleted = table.Column(type: "INTEGER", nullable: false, defaultValue: false),
+ created_at = table.Column(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')")
+ },
+ constraints: table =>
+ {
+ table.PrimaryKey("PK_qfod_record", x => x.id);
+ table.CheckConstraint("chk_qfod_charger", "charger_index IN (0, 1)");
+ table.CheckConstraint("chk_qfod_coil", "coil_index IN (0, 1, 2)");
+ table.ForeignKey(
+ name: "FK_qfod_record_test_scenario_scenario_id",
+ column: x => x.scenario_id,
+ principalTable: "test_scenario",
+ principalColumn: "id",
+ onDelete: ReferentialAction.Cascade);
+ });
+
+ migrationBuilder.CreateIndex(
+ name: "IX_car_model_name",
+ table: "car_model",
+ column: "name",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "uq_charging_parameter_business_key",
+ table: "charging_parameter",
+ columns: new[] { "car_factory", "car_model", "phone_brand", "phone_model" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_foreign_object_name",
+ table: "foreign_object",
+ column: "name",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_manufacturer_name",
+ table: "manufacturer",
+ column: "name",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "idx_ploss_delta_p",
+ table: "ploss_record",
+ column: "delta_p");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_ploss_field_10",
+ table: "ploss_record",
+ column: "field_10");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_ploss_field_2",
+ table: "ploss_record",
+ column: "field_2");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_ploss_field_7",
+ table: "ploss_record",
+ column: "field_7");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_ploss_field_8",
+ table: "ploss_record",
+ column: "field_8");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_ploss_pow_loss",
+ table: "ploss_record",
+ column: "pow_loss");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_ploss_scenario",
+ table: "ploss_record",
+ column: "scenario_id");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_qfod_charger_coil",
+ table: "qfod_record",
+ columns: new[] { "charger_index", "coil_index" });
+
+ migrationBuilder.CreateIndex(
+ name: "idx_qfod_delta_q",
+ table: "qfod_record",
+ column: "delta_q");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_qfod_fod_type",
+ table: "qfod_record",
+ column: "fod_type");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_qfod_scenario",
+ table: "qfod_record",
+ column: "scenario_id");
+
+ migrationBuilder.CreateIndex(
+ name: "IX_rx_type_name",
+ table: "rx_type",
+ column: "name",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "idx_scenario_car_model",
+ table: "test_scenario",
+ column: "car_model_id");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_scenario_foreign_object",
+ table: "test_scenario",
+ column: "foreign_object_id");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_scenario_manufacturer",
+ table: "test_scenario",
+ column: "manufacturer_id");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_scenario_rx_type",
+ table: "test_scenario",
+ column: "rx_type_id");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_scenario_test_date",
+ table: "test_scenario",
+ column: "test_date");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_scenario_tx_hardware",
+ table: "test_scenario",
+ column: "tx_hardware_id");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_scenario_tx_panel",
+ table: "test_scenario",
+ column: "tx_panel_id");
+
+ migrationBuilder.CreateIndex(
+ name: "idx_scenario_tx_software",
+ table: "test_scenario",
+ column: "tx_software_id");
+
+ migrationBuilder.CreateIndex(
+ name: "uq_scenario",
+ table: "test_scenario",
+ columns: new[] { "manufacturer_id", "car_model_id", "tx_panel_id", "tx_hardware_id", "tx_software_id", "rx_type_id", "foreign_object_id", "test_purpose", "test_date", "test_sequence" },
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_tx_hardware_version",
+ table: "tx_hardware",
+ column: "version",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_tx_panel_name",
+ table: "tx_panel",
+ column: "name",
+ unique: true);
+
+ migrationBuilder.CreateIndex(
+ name: "IX_tx_software_version",
+ table: "tx_software",
+ column: "version",
+ unique: true);
+ }
+
+ ///
+ protected override void Down(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.DropTable(
+ name: "charging_parameter");
+
+ migrationBuilder.DropTable(
+ name: "ploss_record");
+
+ migrationBuilder.DropTable(
+ name: "qfod_record");
+
+ migrationBuilder.DropTable(
+ name: "test_scenario");
+
+ migrationBuilder.DropTable(
+ name: "car_model");
+
+ migrationBuilder.DropTable(
+ name: "foreign_object");
+
+ migrationBuilder.DropTable(
+ name: "manufacturer");
+
+ migrationBuilder.DropTable(
+ name: "rx_type");
+
+ migrationBuilder.DropTable(
+ name: "tx_hardware");
+
+ migrationBuilder.DropTable(
+ name: "tx_panel");
+
+ migrationBuilder.DropTable(
+ name: "tx_software");
+ }
+ }
+}
diff --git a/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/ReleaseData/20260821085806_InitialCreate.Designer.cs b/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/ReleaseData/20260821085806_InitialCreate.Designer.cs
new file mode 100644
index 0000000..9b7e309
--- /dev/null
+++ b/src/Gpulse.WCT.DataAnalyzer.Core/Migrations/ReleaseData/20260821085806_InitialCreate.Designer.cs
@@ -0,0 +1,805 @@
+//
+using System;
+using Gpulse.WCT.DataAnalyzer.Core.Infrastructure.LocalData;
+using Microsoft.EntityFrameworkCore;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
+
+#nullable disable
+
+namespace Gpulse.WCT.DataAnalyzer.Core.Migrations.ReleaseData
+{
+ [DbContext(typeof(ReleaseDbContext))]
+ [Migration("20260821085806_InitialCreate")]
+ partial class InitialCreate
+ {
+ ///
+ protected override void BuildTargetModel(ModelBuilder modelBuilder)
+ {
+#pragma warning disable 612, 618
+ modelBuilder.HasAnnotation("ProductVersion", "8.0.0");
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.CarModel", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("car_model", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.ForeignObject", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(50)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("foreign_object", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.Manufacturer", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("IsDeleted")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(false)
+ .HasColumnName("is_deleted");
+
+ b.Property("Name")
+ .IsRequired()
+ .HasMaxLength(100)
+ .HasColumnType("TEXT")
+ .HasColumnName("name");
+
+ b.HasKey("Id");
+
+ b.HasIndex("Name")
+ .IsUnique();
+
+ b.ToTable("manufacturer", (string)null);
+ });
+
+ modelBuilder.Entity("Gpulse.WCT.DataAnalyzer.Core.Domain.Local.PlossRecord", b =>
+ {
+ b.Property("Id")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("id");
+
+ b.Property("CreatedAt")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("TEXT")
+ .HasColumnName("created_at")
+ .HasDefaultValueSql("datetime('now')");
+
+ b.Property("DeltaP")
+ .HasColumnType("INTEGER")
+ .HasColumnName("delta_p");
+
+ b.Property("Field1")
+ .HasColumnType("INTEGER")
+ .HasColumnName("field_1");
+
+ b.Property("Field10")
+ .ValueGeneratedOnAdd()
+ .HasColumnType("INTEGER")
+ .HasDefaultValue(0)
+ .HasColumnName("field_10");
+
+ b.Property