Files
WCTDataMiner/docs/data-model/entities/测试场景.md

9.6 KiB
Raw Permalink Blame History

test_scenario 实体

所属模块:数据模型概览


1. 表定义

表名test_scenario

描述存储测试场景通过外键关联各维度表test_purpose 直接存储


2. 字段定义

列名 类型 约束 说明
id UUID PK, NOT NULL 主键,自动生成
tx_panel_id UUID FK, NOT NULL 关联TX面板维度
tx_hardware_id UUID FK, NOT NULL 关联TX硬件版本维度
tx_software_id UUID FK, NOT NULL 关联TX软件版本维度
rx_type_id UUID FK, NOT NULL 关联RX类型维度
test_purpose VARCHAR(200) NULL 测试目的(直接存储)
test_date DATE NOT NULL 测试日期
test_sequence INT NOT NULL, DEFAULT 1 测试次数序号
qfod_count INT NOT NULL, DEFAULT 0 包含的 Qfod 记录数
ploss_count INT NOT NULL, DEFAULT 0 包含的 Ploss 记录数
is_deleted BOOLEAN NOT NULL, DEFAULT FALSE 软删除标记
created_at TIMESTAMP NOT NULL, DEFAULT NOW() 入库时间

3. 外键

外键 引用表 说明
tx_panel_id tx_panel.id 关联TX面板维度必需
tx_hardware_id tx_hardware.id 关联TX硬件版本维度必需
tx_software_id tx_software.id 关联TX软件版本维度必需
rx_type_id rx_type.id 关联RX类型维度必需

4. 索引

索引名 字段 用途
idx_scenario_tx_panel tx_panel_id 按面板类型筛选
idx_scenario_tx_hardware tx_hardware_id 按硬件版本筛选
idx_scenario_tx_software tx_software_id 按软件版本筛选
idx_scenario_rx_type rx_type_id 按RX类型筛选
idx_scenario_test_date test_date 按测试日期查询

5. 唯一约束

约束名 字段组合 说明
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 模型

实体类

// Models/TestScenario.cs
namespace WCTDataMiner.Models;

public class TestScenario
{
    public Guid Id { get; set; } = Guid.NewGuid();
    public Guid TxPanelId { get; set; }
    public Guid TxHardwareId { get; set; }
    public Guid TxSoftwareId { get; set; }
    public Guid RxTypeId { get; set; }
    public string? TestPurpose { get; set; }
    public DateOnly TestDate { get; set; }
    public int TestSequence { get; set; } = 1;
    public int QfodCount { get; set; } = 0;
    public int PlossCount { get; set; } = 0;
    public bool IsDeleted { get; set; } = false;
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;

    // Navigation Properties - 维度表
    public TxPanel TxPanel { get; set; } = null!;
    public TxHardware TxHardware { get; set; } = null!;
    public TxSoftware TxSoftware { get; set; } = null!;
    public RxType RxType { get; set; } = null!;

    // Navigation Properties - 数据记录
    public ICollection<QfodRecord> QfodRecords { get; set; } = [];
    public ICollection<PlossRecord> PlossRecords { get; set; } = [];
}

配置类

// 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. 场景创建流程

从文件名解析

// Services/ScenarioService.cs
public async Task<TestScenario> GetOrCreateScenarioAsync(ScenarioInfo info)
{
    // 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 existing = await _context.TestScenarios
        .FirstOrDefaultAsync(s => 
            s.TxPanelId == txPanel.Id &&
            s.TxHardwareId == txHardware.Id &&
            s.TxSoftwareId == txSoftware.Id &&
            s.RxTypeId == rxType.Id &&
            s.TestPurpose == info.TestPurpose &&
            s.TestDate == info.TestDate &&
            s.TestSequence == info.TestSequence);
    
    if (existing != null) return existing;
    
    // 3. 创建新场景
    var scenario = new TestScenario
    {
        TxPanelId = txPanel.Id,
        TxHardwareId = txHardware.Id,
        TxSoftwareId = txSoftware.Id,
        RxTypeId = rxType.Id,
        TestPurpose = info.TestPurpose,
        TestDate = info.TestDate,
        TestSequence = info.TestSequence
    };
    
    _context.TestScenarios.Add(scenario);
    await _context.SaveChangesAsync();
    
    return scenario;
}

手动创建

public async Task<TestScenario> CreateAsync(CreateScenarioRequest request)
{
    // 1. 验证维度是否存在
    var txPanel = await context.TxPanels.FindAsync(request.TxPanelId)
        ?? throw new NotFoundException("TX面板不存在");
    var txHardware = await context.TxHardwares.FindAsync(request.TxHardwareId)
        ?? throw new NotFoundException("TX硬件版本不存在");
    // ... 其他维度验证
    
    // 2. 创建场景
    var scenario = new TestScenario
    {
        TxPanelId = request.TxPanelId,
        TxHardwareId = request.TxHardwareId,
        TxSoftwareId = request.TxSoftwareId,
        RxTypeId = request.RxTypeId,
        TestPurpose = request.TestPurpose,
        TestDate = request.TestDate,
        TestSequence = request.TestSequence
    };
    
    context.TestScenarios.Add(scenario);
    await context.SaveChangesAsync();
    
    return scenario;
}