feat: 实现 FOD 日志数据采集 CLI 应用
This commit is contained in:
@@ -14,22 +14,24 @@
|
||||
|
||||
## 2. 字段定义
|
||||
|
||||
> **类型修正说明**:需求文档中 rx_power, tx_power, vcoil, vin, isns, pwm_duty 为 `uint16`(0-65535),但 SQLite 不支持无符号类型。实际数据如 vcoil=35377 超出了 SMALLINT(-32768~32767)范围,因此这些字段使用 INT 类型存储。
|
||||
|
||||
| 列名 | 类型 | 约束 | 说明 |
|
||||
|------|------|------|------|
|
||||
| id | UUID | PK, NOT NULL | 主键 |
|
||||
| scenario_id | UUID | FK, NOT NULL | 关联测试场景 |
|
||||
| rx_type | TINYINT | NOT NULL | 接收端类型标识 |
|
||||
| rx_power | SMALLINT | NOT NULL | 接收端功率(mW) |
|
||||
| tx_power | SMALLINT | NOT NULL | 发射端功率(mW) |
|
||||
| vcoil | SMALLINT | NOT NULL | 发射线圈电压 |
|
||||
| vin | SMALLINT | NOT NULL | 输入电压 |
|
||||
| isns | SMALLINT | NOT NULL | 输入电流采样值 |
|
||||
| rx_power | INT | NOT NULL | 接收端功率(mW),原 uint16 |
|
||||
| tx_power | INT | NOT NULL | 发射端功率(mW),原 uint16 |
|
||||
| vcoil | INT | NOT NULL | 发射线圈电压,原 uint16 |
|
||||
| vin | INT | NOT NULL | 输入电压,原 uint16 |
|
||||
| isns | INT | NOT NULL | 输入电流采样值,原 uint16 |
|
||||
| ploss | INT | NOT NULL | **核心值**:计算损耗(负数=安全) |
|
||||
| threshold | INT | NOT NULL | **安全红线**:当前功率段阈值 |
|
||||
| trigger_count | SMALLINT | NOT NULL, DEFAULT 0 | 触发计数器 |
|
||||
| fod_result | SMALLINT | NOT NULL, DEFAULT 0 | FOD判定结果 |
|
||||
| trigger_count | INT | NOT NULL, DEFAULT 0 | 触发计数器 |
|
||||
| fod_result | INT | NOT NULL, DEFAULT 0 | FOD判定结果 |
|
||||
| protocol_type | TINYINT | NOT NULL | 充电协议类型 |
|
||||
| pwm_duty | SMALLINT | NOT NULL | PWM占空比 |
|
||||
| pwm_duty | INT | NOT NULL | PWM占空比,原 uint16 |
|
||||
| coil_index | TINYINT | NOT NULL | 充电线圈索引 |
|
||||
| is_deleted | BOOLEAN | NOT NULL, DEFAULT FALSE | 软删除标记 |
|
||||
| created_at | TIMESTAMP | NOT NULL, DEFAULT NOW() | 创建时间 |
|
||||
@@ -52,6 +54,8 @@
|
||||
|------|----------|------|
|
||||
| margin | threshold - ploss | 安全余量(>0 安全,<0 危险) |
|
||||
|
||||
> **使用限制**:`Margin` 属性通过 EF Core `Ignore()` 排除映射,仅用于内存计算。不可在数据库查询中直接使用(如 `WHERE Margin > 0`),需改用 `WHERE (threshold - ploss) > 0` 或先加载到内存再计算。
|
||||
|
||||
---
|
||||
|
||||
## 5. 索引
|
||||
@@ -68,100 +72,151 @@
|
||||
|
||||
## 6. EF Core 模型
|
||||
|
||||
### 实体类
|
||||
|
||||
```csharp
|
||||
// Models/PlossRecord.cs
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WCTDataMiner.Models;
|
||||
|
||||
[Table("ploss_record")]
|
||||
[Index(nameof(ScenarioId), Name = "idx_ploss_scenario")]
|
||||
[Index(nameof(RxPower), Name = "idx_ploss_rx_power")]
|
||||
[Index(nameof(Ploss), Name = "idx_ploss_ploss")]
|
||||
[Index(nameof(Threshold), Name = "idx_ploss_threshold")]
|
||||
[Index(nameof(FodResult), Name = "idx_ploss_fod_result")]
|
||||
public class PlossRecord
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[Column("scenario_id")]
|
||||
public Guid ScenarioId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("rx_type")]
|
||||
public byte RxType { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("rx_power")]
|
||||
public short RxPower { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("tx_power")]
|
||||
public short TxPower { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("vcoil")]
|
||||
public short Vcoil { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("vin")]
|
||||
public short Vin { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("isns")]
|
||||
public short Isns { get; set; }
|
||||
|
||||
/// <summary>核心判定值:负数表示安全</summary>
|
||||
[Required]
|
||||
[Column("ploss")]
|
||||
public int Ploss { get; set; }
|
||||
|
||||
/// <summary>安全红线:当前功率段阈值</summary>
|
||||
[Required]
|
||||
[Column("threshold")]
|
||||
public int Threshold { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("trigger_count")]
|
||||
public short TriggerCount { get; set; } = 0;
|
||||
|
||||
[Required]
|
||||
[Column("fod_result")]
|
||||
public short FodResult { get; set; } = 0;
|
||||
|
||||
[Required]
|
||||
[Column("protocol_type")]
|
||||
public byte ProtocolType { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("pwm_duty")]
|
||||
public short PwmDuty { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("coil_index")]
|
||||
public byte CoilIndex { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("is_deleted")]
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
|
||||
[Required]
|
||||
[Column("created_at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[ForeignKey(nameof(ScenarioId))]
|
||||
// Navigation Properties
|
||||
public TestScenario Scenario { get; set; } = null!;
|
||||
|
||||
/// <summary>派生属性:安全余量</summary>
|
||||
[NotMapped]
|
||||
/// <summary>派生属性:安全余量(不落库)</summary>
|
||||
public int Margin => Threshold - Ploss;
|
||||
}
|
||||
```
|
||||
|
||||
### 配置类
|
||||
|
||||
```csharp
|
||||
// Data/Configurations/PlossRecordConfiguration.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Data.Configurations;
|
||||
|
||||
public class PlossRecordConfiguration : IEntityTypeConfiguration<PlossRecord>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PlossRecord> builder)
|
||||
{
|
||||
builder.ToTable("ploss_record");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
builder.Property(e => e.ScenarioId)
|
||||
.IsRequired()
|
||||
.HasColumnName("scenario_id");
|
||||
|
||||
builder.Property(e => e.RxType)
|
||||
.IsRequired()
|
||||
.HasColumnName("rx_type");
|
||||
|
||||
builder.Property(e => e.RxPower)
|
||||
.IsRequired()
|
||||
.HasColumnName("rx_power");
|
||||
|
||||
builder.Property(e => e.TxPower)
|
||||
.IsRequired()
|
||||
.HasColumnName("tx_power");
|
||||
|
||||
builder.Property(e => e.Vcoil)
|
||||
.IsRequired()
|
||||
.HasColumnName("vcoil");
|
||||
|
||||
builder.Property(e => e.Vin)
|
||||
.IsRequired()
|
||||
.HasColumnName("vin");
|
||||
|
||||
builder.Property(e => e.Isns)
|
||||
.IsRequired()
|
||||
.HasColumnName("isns");
|
||||
|
||||
builder.Property(e => e.Ploss)
|
||||
.IsRequired()
|
||||
.HasColumnName("ploss");
|
||||
|
||||
builder.Property(e => e.Threshold)
|
||||
.IsRequired()
|
||||
.HasColumnName("threshold");
|
||||
|
||||
builder.Property(e => e.TriggerCount)
|
||||
.IsRequired()
|
||||
.HasDefaultValue((short)0)
|
||||
.HasColumnName("trigger_count");
|
||||
|
||||
builder.Property(e => e.FodResult)
|
||||
.IsRequired()
|
||||
.HasDefaultValue((short)0)
|
||||
.HasColumnName("fod_result");
|
||||
|
||||
builder.Property(e => e.ProtocolType)
|
||||
.IsRequired()
|
||||
.HasColumnName("protocol_type");
|
||||
|
||||
builder.Property(e => e.PwmDuty)
|
||||
.IsRequired()
|
||||
.HasColumnName("pwm_duty");
|
||||
|
||||
builder.Property(e => e.CoilIndex)
|
||||
.IsRequired()
|
||||
.HasColumnName("coil_index");
|
||||
|
||||
builder.Property(e => e.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
builder.Property(e => e.CreatedAt)
|
||||
.IsRequired()
|
||||
.HasDefaultValueSql("NOW()")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
// 派生属性不落库
|
||||
builder.Ignore(e => e.Margin);
|
||||
|
||||
// 索引
|
||||
builder.HasIndex(e => e.ScenarioId).HasDatabaseName("idx_ploss_scenario");
|
||||
builder.HasIndex(e => e.RxPower).HasDatabaseName("idx_ploss_rx_power");
|
||||
builder.HasIndex(e => e.Ploss).HasDatabaseName("idx_ploss_ploss");
|
||||
builder.HasIndex(e => e.Threshold).HasDatabaseName("idx_ploss_threshold");
|
||||
builder.HasIndex(e => e.FodResult).HasDatabaseName("idx_ploss_fod_result");
|
||||
|
||||
// 关系配置
|
||||
builder.HasOne(e => e.Scenario)
|
||||
.WithMany(e => e.PlossRecords)
|
||||
.HasForeignKey(e => e.ScenarioId)
|
||||
.IsRequired();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 动态阈值表
|
||||
|
||||
@@ -55,69 +55,112 @@
|
||||
| chk_qfod_charger | CHECK | charger_index IN (0, 1) |
|
||||
| chk_qfod_coil | CHECK | coil_index IN (0, 1, 2) |
|
||||
|
||||
> **数据库兼容性**:`IN (0, 1)` 语法在 SQLite、PostgreSQL、MySQL 中均可使用。若使用其他数据库,需验证 CHECK 约束语法兼容性。
|
||||
|
||||
---
|
||||
|
||||
## 6. EF Core 模型
|
||||
|
||||
### 实体类
|
||||
|
||||
```csharp
|
||||
// Models/QfodRecord.cs
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WCTDataMiner.Models;
|
||||
|
||||
[Table("qfod_record")]
|
||||
[Index(nameof(ScenarioId), Name = "idx_qfod_scenario")]
|
||||
[Index(nameof(ChargerIndex), nameof(CoilIndex), Name = "idx_qfod_charger_coil")]
|
||||
[Index(nameof(FodType), Name = "idx_qfod_fod_type")]
|
||||
[Index(nameof(DeltaQ), Name = "idx_qfod_delta_q")]
|
||||
public class QfodRecord
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[Column("scenario_id")]
|
||||
public Guid ScenarioId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("charger_index")]
|
||||
public byte ChargerIndex { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("coil_index")]
|
||||
public byte CoilIndex { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("delta_q")]
|
||||
public int DeltaQ { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("current_q")]
|
||||
public float CurrentQ { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("raw_q")]
|
||||
public float RawQ { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("fod_type")]
|
||||
public byte FodType { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("is_deleted")]
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
|
||||
[Required]
|
||||
[Column("created_at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
[ForeignKey(nameof(ScenarioId))]
|
||||
// Navigation Properties
|
||||
public TestScenario Scenario { get; set; } = null!;
|
||||
}
|
||||
```
|
||||
|
||||
### 配置类
|
||||
|
||||
```csharp
|
||||
// Data/Configurations/QfodRecordConfiguration.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Data.Configurations;
|
||||
|
||||
public class QfodRecordConfiguration : IEntityTypeConfiguration<QfodRecord>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<QfodRecord> builder)
|
||||
{
|
||||
builder.ToTable("qfod_record");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
builder.Property(e => e.ScenarioId)
|
||||
.IsRequired()
|
||||
.HasColumnName("scenario_id");
|
||||
|
||||
builder.Property(e => e.ChargerIndex)
|
||||
.IsRequired()
|
||||
.HasColumnName("charger_index");
|
||||
|
||||
builder.Property(e => e.CoilIndex)
|
||||
.IsRequired()
|
||||
.HasColumnName("coil_index");
|
||||
|
||||
builder.Property(e => e.DeltaQ)
|
||||
.IsRequired()
|
||||
.HasColumnName("delta_q");
|
||||
|
||||
builder.Property(e => e.CurrentQ)
|
||||
.IsRequired()
|
||||
.HasColumnName("current_q");
|
||||
|
||||
builder.Property(e => e.RawQ)
|
||||
.IsRequired()
|
||||
.HasColumnName("raw_q");
|
||||
|
||||
builder.Property(e => e.FodType)
|
||||
.IsRequired()
|
||||
.HasColumnName("fod_type");
|
||||
|
||||
builder.Property(e => e.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
builder.Property(e => e.CreatedAt)
|
||||
.IsRequired()
|
||||
.HasDefaultValueSql("NOW()")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
// 索引
|
||||
builder.HasIndex(e => e.ScenarioId).HasDatabaseName("idx_qfod_scenario");
|
||||
builder.HasIndex(e => new { e.ChargerIndex, e.CoilIndex }).HasDatabaseName("idx_qfod_charger_coil");
|
||||
builder.HasIndex(e => e.FodType).HasDatabaseName("idx_qfod_fod_type");
|
||||
builder.HasIndex(e => e.DeltaQ).HasDatabaseName("idx_qfod_delta_q");
|
||||
|
||||
// CHECK 约束
|
||||
builder.HasCheckConstraint("chk_qfod_charger", "charger_index IN (0, 1)");
|
||||
builder.HasCheckConstraint("chk_qfod_coil", "coil_index IN (0, 1, 2)");
|
||||
|
||||
// 关系配置
|
||||
builder.HasOne(e => e.Scenario)
|
||||
.WithMany(e => e.QfodRecords)
|
||||
.HasForeignKey(e => e.ScenarioId)
|
||||
.IsRequired();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 检测通道组合
|
||||
|
||||
@@ -34,31 +34,17 @@
|
||||
|
||||
## 4. EF Core 模型
|
||||
|
||||
### 实体类
|
||||
|
||||
```csharp
|
||||
// Models/RxType.cs
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WCTDataMiner.Models;
|
||||
|
||||
[Table("rx_type")]
|
||||
public class RxType
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
[Column("name")]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[Column("is_deleted")]
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
|
||||
[Required]
|
||||
[Column("created_at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation Properties
|
||||
@@ -66,6 +52,45 @@ public class RxType
|
||||
}
|
||||
```
|
||||
|
||||
### 配置类
|
||||
|
||||
```csharp
|
||||
// Data/Configurations/RxTypeConfiguration.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Data.Configurations;
|
||||
|
||||
public class RxTypeConfiguration : IEntityTypeConfiguration<RxType>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<RxType> builder)
|
||||
{
|
||||
builder.ToTable("rx_type");
|
||||
|
||||
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("NOW()")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
builder.HasIndex(e => e.Name).IsUnique();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 使用说明
|
||||
|
||||
@@ -25,31 +25,17 @@
|
||||
|
||||
## 3. EF Core 模型
|
||||
|
||||
### 实体类
|
||||
|
||||
```csharp
|
||||
// Models/TxHardware.cs
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WCTDataMiner.Models;
|
||||
|
||||
[Table("tx_hardware")]
|
||||
public class TxHardware
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
[Column("version")]
|
||||
public string Version { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[Column("is_deleted")]
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
|
||||
[Required]
|
||||
[Column("created_at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation Properties
|
||||
@@ -57,6 +43,45 @@ public class TxHardware
|
||||
}
|
||||
```
|
||||
|
||||
### 配置类
|
||||
|
||||
```csharp
|
||||
// Data/Configurations/TxHardwareConfiguration.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Data.Configurations;
|
||||
|
||||
public class TxHardwareConfiguration : IEntityTypeConfiguration<TxHardware>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TxHardware> builder)
|
||||
{
|
||||
builder.ToTable("tx_hardware");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
builder.Property(e => e.Version)
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("version");
|
||||
|
||||
builder.Property(e => e.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
builder.Property(e => e.CreatedAt)
|
||||
.IsRequired()
|
||||
.HasDefaultValueSql("NOW()")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
builder.HasIndex(e => e.Version).IsUnique();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 使用说明
|
||||
|
||||
@@ -39,31 +39,17 @@
|
||||
|
||||
## 4. EF Core 模型
|
||||
|
||||
### 实体类
|
||||
|
||||
```csharp
|
||||
// Models/TxPanel.cs
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WCTDataMiner.Models;
|
||||
|
||||
[Table("tx_panel")]
|
||||
public class TxPanel
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
[Column("name")]
|
||||
public string Name { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[Column("is_deleted")]
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
|
||||
[Required]
|
||||
[Column("created_at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation Properties
|
||||
@@ -71,6 +57,45 @@ public class TxPanel
|
||||
}
|
||||
```
|
||||
|
||||
### 配置类
|
||||
|
||||
```csharp
|
||||
// Data/Configurations/TxPanelConfiguration.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Data.Configurations;
|
||||
|
||||
public class TxPanelConfiguration : IEntityTypeConfiguration<TxPanel>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TxPanel> builder)
|
||||
{
|
||||
builder.ToTable("tx_panel");
|
||||
|
||||
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("NOW()")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
builder.HasIndex(e => e.Name).IsUnique();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 初始化数据
|
||||
|
||||
@@ -25,31 +25,17 @@
|
||||
|
||||
## 3. EF Core 模型
|
||||
|
||||
### 实体类
|
||||
|
||||
```csharp
|
||||
// Models/TxSoftware.cs
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WCTDataMiner.Models;
|
||||
|
||||
[Table("tx_software")]
|
||||
public class TxSoftware
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
[Column("version")]
|
||||
public string Version { get; set; } = null!;
|
||||
|
||||
[Required]
|
||||
[Column("is_deleted")]
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
|
||||
[Required]
|
||||
[Column("created_at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation Properties
|
||||
@@ -57,6 +43,45 @@ public class TxSoftware
|
||||
}
|
||||
```
|
||||
|
||||
### 配置类
|
||||
|
||||
```csharp
|
||||
// Data/Configurations/TxSoftwareConfiguration.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Data.Configurations;
|
||||
|
||||
public class TxSoftwareConfiguration : IEntityTypeConfiguration<TxSoftware>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TxSoftware> builder)
|
||||
{
|
||||
builder.ToTable("tx_software");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
builder.Property(e => e.Version)
|
||||
.IsRequired()
|
||||
.HasMaxLength(50)
|
||||
.HasColumnName("version");
|
||||
|
||||
builder.Property(e => e.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
builder.Property(e => e.CreatedAt)
|
||||
.IsRequired()
|
||||
.HasDefaultValueSql("NOW()")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
builder.HasIndex(e => e.Version).IsUnique();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 使用说明
|
||||
|
||||
@@ -60,84 +60,42 @@
|
||||
|--------|----------|------|
|
||||
| uq_scenario | (tx_panel_id, tx_hardware_id, tx_software_id, rx_type_id, test_purpose, test_date, test_sequence) | 相同属性组合不重复创建 |
|
||||
|
||||
> **关于 test_purpose 的 NULL 处理**:由于 `test_purpose` 是可空字段,含 NULL 的唯一约束在不同数据库中行为不同:
|
||||
> - **PostgreSQL**:默认允许多个 NULL(被视为不同值),可通过 `NULLS NOT DISTINCT` 约束行为
|
||||
> - **SQL Server**:只允许一个 NULL
|
||||
> - **SQLite/MySQL**:允多个 NULL
|
||||
>
|
||||
> 若需严格幂等(无论 test_purpose 是否为 NULL 都不重复),建议使用 filtered index 或将空值转为默认字符串(如 `(empty)`)。
|
||||
|
||||
---
|
||||
|
||||
## 6. EF Core 模型
|
||||
|
||||
### 实体类
|
||||
|
||||
```csharp
|
||||
// Models/TestScenario.cs
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
namespace WCTDataMiner.Models;
|
||||
|
||||
[Table("test_scenario")]
|
||||
[Index(nameof(TxPanelId), Name = "idx_scenario_tx_panel")]
|
||||
[Index(nameof(TxHardwareId), Name = "idx_scenario_tx_hardware")]
|
||||
[Index(nameof(TxSoftwareId), Name = "idx_scenario_tx_software")]
|
||||
[Index(nameof(RxTypeId), Name = "idx_scenario_rx_type")]
|
||||
[Index(nameof(TestDate), Name = "idx_scenario_test_date")]
|
||||
public class TestScenario
|
||||
{
|
||||
[Key]
|
||||
[Column("id")]
|
||||
public Guid Id { get; set; } = Guid.NewGuid();
|
||||
|
||||
[Required]
|
||||
[Column("tx_panel_id")]
|
||||
public Guid TxPanelId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("tx_hardware_id")]
|
||||
public Guid TxHardwareId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("tx_software_id")]
|
||||
public Guid TxSoftwareId { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("rx_type_id")]
|
||||
public Guid RxTypeId { get; set; }
|
||||
|
||||
[MaxLength(200)]
|
||||
[Column("test_purpose")]
|
||||
public string? TestPurpose { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("test_date")]
|
||||
public DateOnly TestDate { get; set; }
|
||||
|
||||
[Required]
|
||||
[Column("test_sequence")]
|
||||
public int TestSequence { get; set; } = 1;
|
||||
|
||||
[Required]
|
||||
[Column("qfod_count")]
|
||||
public int QfodCount { get; set; } = 0;
|
||||
|
||||
[Required]
|
||||
[Column("ploss_count")]
|
||||
public int PlossCount { get; set; } = 0;
|
||||
|
||||
[Required]
|
||||
[Column("is_deleted")]
|
||||
public bool IsDeleted { get; set; } = false;
|
||||
|
||||
[Required]
|
||||
[Column("created_at")]
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
// Navigation Properties - 维度表
|
||||
[ForeignKey(nameof(TxPanelId))]
|
||||
public TxPanel TxPanel { get; set; } = null!;
|
||||
|
||||
[ForeignKey(nameof(TxHardwareId))]
|
||||
public TxHardware TxHardware { get; set; } = null!;
|
||||
|
||||
[ForeignKey(nameof(TxSoftwareId))]
|
||||
public TxSoftware TxSoftware { get; set; } = null!;
|
||||
|
||||
[ForeignKey(nameof(RxTypeId))]
|
||||
public RxType RxType { get; set; } = null!;
|
||||
|
||||
// Navigation Properties - 数据记录
|
||||
@@ -146,6 +104,118 @@ public class TestScenario
|
||||
}
|
||||
```
|
||||
|
||||
### 配置类
|
||||
|
||||
```csharp
|
||||
// Data/Configurations/TestScenarioConfiguration.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Data.Configurations;
|
||||
|
||||
public class TestScenarioConfiguration : IEntityTypeConfiguration<TestScenario>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<TestScenario> builder)
|
||||
{
|
||||
builder.ToTable("test_scenario");
|
||||
|
||||
builder.HasKey(e => e.Id);
|
||||
builder.Property(e => e.Id).HasColumnName("id");
|
||||
|
||||
builder.Property(e => e.TxPanelId)
|
||||
.IsRequired()
|
||||
.HasColumnName("tx_panel_id");
|
||||
|
||||
builder.Property(e => e.TxHardwareId)
|
||||
.IsRequired()
|
||||
.HasColumnName("tx_hardware_id");
|
||||
|
||||
builder.Property(e => e.TxSoftwareId)
|
||||
.IsRequired()
|
||||
.HasColumnName("tx_software_id");
|
||||
|
||||
builder.Property(e => e.RxTypeId)
|
||||
.IsRequired()
|
||||
.HasColumnName("rx_type_id");
|
||||
|
||||
builder.Property(e => e.TestPurpose)
|
||||
.HasMaxLength(200)
|
||||
.HasColumnName("test_purpose");
|
||||
|
||||
builder.Property(e => e.TestDate)
|
||||
.IsRequired()
|
||||
.HasColumnName("test_date");
|
||||
|
||||
builder.Property(e => e.TestSequence)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(1)
|
||||
.HasColumnName("test_sequence");
|
||||
|
||||
builder.Property(e => e.QfodCount)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("qfod_count");
|
||||
|
||||
builder.Property(e => e.PlossCount)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(0)
|
||||
.HasColumnName("ploss_count");
|
||||
|
||||
builder.Property(e => e.IsDeleted)
|
||||
.IsRequired()
|
||||
.HasDefaultValue(false)
|
||||
.HasColumnName("is_deleted");
|
||||
|
||||
builder.Property(e => e.CreatedAt)
|
||||
.IsRequired()
|
||||
.HasDefaultValueSql("NOW()")
|
||||
.HasColumnName("created_at");
|
||||
|
||||
// 索引
|
||||
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.TestDate).HasDatabaseName("idx_scenario_test_date");
|
||||
|
||||
// 唯一约束
|
||||
builder.HasIndex(e => new { e.TxPanelId, e.TxHardwareId, e.TxSoftwareId, e.RxTypeId, e.TestPurpose, e.TestDate, e.TestSequence })
|
||||
.IsUnique()
|
||||
.HasDatabaseName("uq_scenario");
|
||||
|
||||
// 关系配置
|
||||
builder.HasOne(e => e.TxPanel)
|
||||
.WithMany(e => e.TestScenarios)
|
||||
.HasForeignKey(e => e.TxPanelId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasOne(e => e.TxHardware)
|
||||
.WithMany(e => e.TestScenarios)
|
||||
.HasForeignKey(e => e.TxHardwareId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasOne(e => e.TxSoftware)
|
||||
.WithMany(e => e.TestScenarios)
|
||||
.HasForeignKey(e => e.TxSoftwareId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasOne(e => e.RxType)
|
||||
.WithMany(e => e.TestScenarios)
|
||||
.HasForeignKey(e => e.RxTypeId)
|
||||
.IsRequired();
|
||||
|
||||
builder.HasMany(e => e.QfodRecords)
|
||||
.WithOne(e => e.Scenario)
|
||||
.HasForeignKey(e => e.ScenarioId);
|
||||
|
||||
builder.HasMany(e => e.PlossRecords)
|
||||
.WithOne(e => e.Scenario)
|
||||
.HasForeignKey(e => e.ScenarioId);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 场景创建流程
|
||||
@@ -154,45 +224,41 @@ public class TestScenario
|
||||
|
||||
```csharp
|
||||
// Services/ScenarioService.cs
|
||||
public async Task<TestScenario> GetOrCreateFromFileNameAsync(string fileName)
|
||||
public async Task<TestScenario> GetOrCreateScenarioAsync(ScenarioInfo info)
|
||||
{
|
||||
// 1. 解析文件名
|
||||
var parts = ParseFileName(fileName);
|
||||
// 格式: TX面板-TX硬件-TX软件-RX类型-测试目的-年月日-测试次数.log
|
||||
// 1. 获取或创建各维度(通过 DimensionService)
|
||||
var txPanel = await _dimensionService.GetOrCreateTxPanelAsync(info.TxPanel);
|
||||
var txHardware = await _dimensionService.GetOrCreateTxHardwareAsync(info.TxHardware);
|
||||
var txSoftware = await _dimensionService.GetOrCreateTxSoftwareAsync(info.TxSoftware);
|
||||
var rxType = await _dimensionService.GetOrCreateRxTypeAsync(info.RxType);
|
||||
|
||||
// 2. 获取或创建各维度
|
||||
var txPanel = await txPanelService.GetOrCreateAsync(parts.TxPanel);
|
||||
var txHardware = await txHardwareService.GetOrCreateAsync(parts.TxHardware);
|
||||
var txSoftware = await txSoftwareService.GetOrCreateAsync(parts.TxSoftware);
|
||||
var rxType = await rxTypeService.GetOrCreateAsync(parts.RxType);
|
||||
|
||||
// 3. 检查场景是否已存在
|
||||
var existing = await context.TestScenarios
|
||||
// 2. 检查场景是否已存在(利用唯一约束)
|
||||
var existing = await _context.TestScenarios
|
||||
.FirstOrDefaultAsync(s =>
|
||||
s.TxPanelId == txPanel.Id &&
|
||||
s.TxHardwareId == txHardware.Id &&
|
||||
s.TxSoftwareId == txSoftware.Id &&
|
||||
s.RxTypeId == rxType.Id &&
|
||||
s.TestPurpose == parts.TestPurpose &&
|
||||
s.TestDate == parts.TestDate &&
|
||||
s.TestSequence == parts.TestSequence);
|
||||
s.TestPurpose == info.TestPurpose &&
|
||||
s.TestDate == info.TestDate &&
|
||||
s.TestSequence == info.TestSequence);
|
||||
|
||||
if (existing != null) return existing;
|
||||
|
||||
// 4. 创建新场景
|
||||
// 3. 创建新场景
|
||||
var scenario = new TestScenario
|
||||
{
|
||||
TxPanelId = txPanel.Id,
|
||||
TxHardwareId = txHardware.Id,
|
||||
TxSoftwareId = txSoftware.Id,
|
||||
RxTypeId = rxType.Id,
|
||||
TestPurpose = parts.TestPurpose,
|
||||
TestDate = parts.TestDate,
|
||||
TestSequence = parts.TestSequence
|
||||
TestPurpose = info.TestPurpose,
|
||||
TestDate = info.TestDate,
|
||||
TestSequence = info.TestSequence
|
||||
};
|
||||
|
||||
context.TestScenarios.Add(scenario);
|
||||
await context.SaveChangesAsync();
|
||||
_context.TestScenarios.Add(scenario);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return scenario;
|
||||
}
|
||||
|
||||
@@ -182,6 +182,60 @@ public async Task SoftDeleteAsync(Guid id)
|
||||
}
|
||||
```
|
||||
|
||||
### 实体配置方式
|
||||
|
||||
采用 `IEntityTypeConfiguration<T>` Fluent API 配置,实体类保持纯净 POCO:
|
||||
|
||||
```csharp
|
||||
// Data/WctMinerDbContext.cs
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using WCTDataMiner.Models;
|
||||
|
||||
namespace WCTDataMiner.Data;
|
||||
|
||||
public class WctMinerDbContext : DbContext
|
||||
{
|
||||
public WctMinerDbContext(DbContextOptions<WctMinerDbContext> options)
|
||||
: base(options) { }
|
||||
|
||||
public DbSet<TxPanel> TxPanels => Set<TxPanel>();
|
||||
public DbSet<TxHardware> TxHardwares => Set<TxHardware>();
|
||||
public DbSet<TxSoftware> TxSoftwares => Set<TxSoftware>();
|
||||
public DbSet<RxType> RxTypes => Set<RxType>();
|
||||
public DbSet<TestScenario> TestScenarios => Set<TestScenario>();
|
||||
public DbSet<QfodRecord> QfodRecords => Set<QfodRecord>();
|
||||
public DbSet<PlossRecord> PlossRecords => Set<PlossRecord>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// 自动应用所有 IEntityTypeConfiguration<T> 配置
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(WctMinerDbContext).Assembly);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
> **数据库兼容性说明**:
|
||||
> - `HasDefaultValueSql("NOW()")` 适用于 PostgreSQL
|
||||
> - 若使用 SQLite,需改为 `HasDefaultValueSql("datetime('now')")`
|
||||
> - 若使用 MySQL,需改为 `HasDefaultValueSql("NOW()")`(MySQL 也支持)
|
||||
> - 若使用 SQL Server,需改为 `HasDefaultValueSql("GETDATE()")`
|
||||
|
||||
**配置类列表**:
|
||||
|
||||
| 配置类 | 文件路径 | 说明 |
|
||||
|--------|----------|------|
|
||||
| TxPanelConfiguration | Data/Configurations/TxPanelConfiguration.cs | TX面板维度 |
|
||||
| TxHardwareConfiguration | Data/Configurations/TxHardwareConfiguration.cs | TX硬件版本维度 |
|
||||
| TxSoftwareConfiguration | Data/Configurations/TxSoftwareConfiguration.cs | TX软件版本维度 |
|
||||
| RxTypeConfiguration | Data/Configurations/RxTypeConfiguration.cs | RX类型维度 |
|
||||
| TestScenarioConfiguration | Data/Configurations/TestScenarioConfiguration.cs | 测试场景 |
|
||||
| QfodRecordConfiguration | Data/Configurations/QfodRecordConfiguration.cs | Qfod记录 |
|
||||
| PlossRecordConfiguration | Data/Configurations/PlossRecordConfiguration.cs | Ploss记录 |
|
||||
|
||||
> **迁移注意事项**:从 DataAnnotations `DateTime.UtcNow` 迁移到 `HasDefaultValueSql("NOW()")` 后,测试代码需注意:未显式设置 `CreatedAt` 时,值由数据库生成而非 C# 运行时。单元测试中需 mock 数据库或显式设置值。
|
||||
|
||||
### 系统日志处理
|
||||
|
||||
解析过程中的错误日志通过 Serilog 记录到系统日志文件,不存入业务数据库。
|
||||
|
||||
Reference in New Issue
Block a user