Files
WCTDataMiner/docs/data-model/关系说明.md
ssss 28cb2533a7 docs: 完善数据模型设计
- 重命名 log_file 为 test_scenario(测试场景)
- 拆分维度表:tx_panel、tx_hardware、tx_software、rx_type
- 采用星型模型设计,消除数据冗余
- 为所有表添加软删除字段 is_deleted
- 更新关系说明,包含全局查询过滤器配置
- 更新 ER 图和查询示例

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 15:53:30 +08:00

10 KiB
Raw Permalink Blame History

关系说明

所属模块:数据模型概览


1. 关系概览

维度表 → 场景表

关系 类型 说明
tx_panel → test_scenario 1:N 一个面板类型被多个场景引用
tx_hardware → test_scenario 1:N 一个硬件版本被多个场景引用
tx_software → test_scenario 1:N 一个软件版本被多个场景引用
rx_type → test_scenario 1:N 一个RX类型被多个场景引用

场景表 → 数据表

关系 类型 说明
test_scenario → qfod_record 1:N 一个场景包含多条 Qfod 记录
test_scenario → ploss_record 1:N 一个场景包含多条 Ploss 记录

2. 关系详情

2.1 维度表 → test_scenario

erDiagram
    tx_panel ||--o{ test_scenario : "引用"
    tx_hardware ||--o{ test_scenario : "引用"
    tx_software ||--o{ test_scenario : "引用"
    rx_type ||--o{ test_scenario : "引用"
属性 说明
类型 一对多1:N
外键 场景表存储维度表的外键
删除行为 RESTRICT维度被引用时不能删除
业务含义 维度值可被多个场景复用,消除冗余

2.2 test_scenario → qfod_record

erDiagram
    test_scenario ||--o{ qfod_record : "包含"
属性 说明
类型 一对多1:N
外键 qfod_record.scenario_id → test_scenario.id必需
删除行为 CASCADE删除场景时同步删除关联记录
业务含义 一个测试场景产生多条 Qfod 检测数据

2.3 test_scenario → ploss_record

erDiagram
    test_scenario ||--o{ ploss_record : "包含"
属性 说明
类型 一对多1:N
外键 ploss_record.scenario_id → test_scenario.id必需
删除行为 CASCADE删除场景时同步删除关联记录
业务含义 一个测试场景产生多条 Ploss FOD 数据

3. EF Core 配置

// Data/WctMinerDbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 维度表 → 场景表 关系配置
    modelBuilder.Entity<TestScenario>(entity =>
    {
        // TX面板
        entity.HasOne(e => e.TxPanel)
            .WithMany(p => p.TestScenarios)
            .HasForeignKey(e => e.TxPanelId)
            .OnDelete(DeleteBehavior.Restrict);  // 维度被引用时不能删除
        
        // TX硬件版本
        entity.HasOne(e => e.TxHardware)
            .WithMany(h => h.TestScenarios)
            .HasForeignKey(e => e.TxHardwareId)
            .OnDelete(DeleteBehavior.Restrict);
        
        // TX软件版本
        entity.HasOne(e => e.TxSoftware)
            .WithMany(s => s.TestScenarios)
            .HasForeignKey(e => e.TxSoftwareId)
            .OnDelete(DeleteBehavior.Restrict);
        
        // RX类型
        entity.HasOne(e => e.RxType)
            .WithMany(r => r.TestScenarios)
            .HasForeignKey(e => e.RxTypeId)
            .OnDelete(DeleteBehavior.Restrict);
        
        // 唯一约束
        entity.HasIndex(e => new { 
            e.TxPanelId, 
            e.TxHardwareId, 
            e.TxSoftwareId, 
            e.RxTypeId, 
            e.TestPurpose, 
            e.TestDate, 
            e.TestSequence 
        }).IsUnique();
    });
    
    // 场景表 → QfodRecord 关系配置
    modelBuilder.Entity<QfodRecord>(entity =>
    {
        entity.HasOne(e => e.Scenario)
            .WithMany(s => s.QfodRecords)
            .HasForeignKey(e => e.ScenarioId)
            .OnDelete(DeleteBehavior.Cascade);  // 删除场景时同步删除记录
    });
    
    // 场景表 → PlossRecord 关系配置
    modelBuilder.Entity<PlossRecord>(entity =>
    {
        entity.HasOne(e => e.Scenario)
            .WithMany(s => s.PlossRecords)
            .HasForeignKey(e => e.ScenarioId)
            .OnDelete(DeleteBehavior.Cascade);
    });
}

4. 查询示例

基础查询

// 查询场景及其所有维度属性
var scenario = await context.TestScenarios
    .Include(s => s.TxPanel)
    .Include(s => s.TxHardware)
    .Include(s => s.TxSoftware)
    .Include(s => s.RxType)
    .FirstOrDefaultAsync(s => s.Id == scenarioId);

// 输出维度属性
Console.WriteLine($"面板: {scenario.TxPanel.Name}");
Console.WriteLine($"硬件: {scenario.TxHardware.Version}");
Console.WriteLine($"软件: {scenario.TxSoftware.Version}");
Console.WriteLine($"RX: {scenario.RxType.Name}");

按维度筛选

// 查询指定面板类型的所有场景
var scenarios = await context.TestScenarios
    .Include(s => s.TxPanel)
    .Where(s => s.TxPanel.Name == "single-mold")
    .ToListAsync();

// 查询指定硬件版本的所有场景
var scenarios = await context.TestScenarios
    .Include(s => s.TxHardware)
    .Where(s => s.TxHardware.Version == "V1.0")
    .ToListAsync();

按维度分组统计

// 按面板类型统计 Qfod 记录数
var statsByPanel = await context.QfodRecords
    .Include(r => r.Scenario)
        .ThenInclude(s => s.TxPanel)
    .GroupBy(r => r.Scenario.TxPanel.Name)
    .Select(g => new { TxPanel = g.Key, Count = g.Count() })
    .ToListAsync();

// 按硬件版本统计场景数
var statsByHardware = await context.TestScenarios
    .Include(s => s.TxHardware)
    .GroupBy(s => s.TxHardware.Version)
    .Select(g => new { TxHardware = g.Key, ScenarioCount = g.Count() })
    .ToListAsync();

// 按RX类型统计 Ploss 记录数
var statsByRx = await context.PlossRecords
    .Include(r => r.Scenario)
        .ThenInclude(s => s.RxType)
    .GroupBy(r => r.Scenario.RxType.Name)
    .Select(g => new { RxType = g.Key, Count = g.Count() })
    .ToListAsync();

多维度联合查询

// 查询指定面板+硬件+软件的所有场景
var scenarios = await context.TestScenarios
    .Include(s => s.TxPanel)
    .Include(s => s.TxHardware)
    .Include(s => s.TxSoftware)
    .Include(s => s.RxType)
    .Where(s => 
        s.TxPanel.Name == "single-mold" &&
        s.TxHardware.Version == "V1.0" &&
        s.TxSoftware.Version == "0x1234")
    .ToListAsync();

// 查询指定维度组合的所有 Qfod 记录
var records = await context.QfodRecords
    .Include(r => r.Scenario)
        .ThenInclude(s => s.TxPanel)
    .Include(r => r.Scenario)
        .ThenInclude(s => s.TxHardware)
    .Where(r => 
        r.Scenario.TxPanel.Name == "single-mold" &&
        r.Scenario.TxHardware.Version == "V1.0")
    .ToListAsync();

5. 软删除设计

全局查询过滤器

// Data/WctMinerDbContext.cs
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 维度表 → 场景表 关系配置
    modelBuilder.Entity<TestScenario>(entity =>
    {
        // ... 其他配置
        
        // 唯一约束(排除已删除记录)
        entity.HasIndex(e => new { 
            e.TxPanelId, 
            e.TxHardwareId, 
            e.TxSoftwareId, 
            e.RxTypeId, 
            e.TestPurpose, 
            e.TestDate, 
            e.TestSequence 
        }).HasFilter("is_deleted = false").IsUnique();
    });
    
    // 全局查询过滤器(自动过滤已删除记录)
    modelBuilder.Entity<TxPanel>().HasQueryFilter(e => !e.IsDeleted);
    modelBuilder.Entity<TxHardware>().HasQueryFilter(e => !e.IsDeleted);
    modelBuilder.Entity<TxSoftware>().HasQueryFilter(e => !e.IsDeleted);
    modelBuilder.Entity<RxType>().HasQueryFilter(e => !e.IsDeleted);
    modelBuilder.Entity<TestScenario>().HasQueryFilter(e => !e.IsDeleted);
    modelBuilder.Entity<QfodRecord>().HasQueryFilter(e => !e.IsDeleted);
    modelBuilder.Entity<PlossRecord>().HasQueryFilter(e => !e.IsDeleted);
}

删除行为说明

操作 行为 说明
删除维度 软删除 设置 is_deleted = true不物理删除
删除场景 软删除 设置 is_deleted = true关联记录保留
删除记录 软删除 设置 is_deleted = true不物理删除

注意:软删除后,关联记录不会被级联删除,因为这是软删除而非物理删除。

软删除操作示例

// 软删除维度
public async Task SoftDeleteTxPanelAsync(Guid txPanelId)
{
    var entity = await context.TxPanels
        .IgnoreQueryFilters()  // 包含已删除记录
        .FirstOrDefaultAsync(t => t.Id == txPanelId);
    
    if (entity != null && !entity.IsDeleted)
    {
        entity.IsDeleted = true;
        await context.SaveChangesAsync();
    }
}

// 软删除场景及其关联记录
public async Task SoftDeleteScenarioWithRecordsAsync(Guid scenarioId)
{
    var scenario = await context.TestScenarios.FindAsync(scenarioId);
    if (scenario != null)
    {
        scenario.IsDeleted = true;
        
        // 同时软删除关联记录
        var qfodRecords = await context.QfodRecords
            .Where(r => r.ScenarioId == scenarioId)
            .ToListAsync();
        foreach (var record in qfodRecords)
        {
            record.IsDeleted = true;
        }
        
        var plossRecords = await context.PlossRecords
            .Where(r => r.ScenarioId == scenarioId)
            .ToListAsync();
        foreach (var record in plossRecords)
        {
            record.IsDeleted = true;
        }
        
        await context.SaveChangesAsync();
    }
}

// 恢复已删除记录
public async Task RestoreAsync(Guid id)
{
    var entity = await context.TxPanels
        .IgnoreQueryFilters()
        .FirstOrDefaultAsync(t => t.Id == id);
    
    if (entity != null && entity.IsDeleted)
    {
        entity.IsDeleted = false;
        await context.SaveChangesAsync();
    }
}

查询已删除记录

// 包含已删除记录的查询
var allRecords = await context.TxPanels
    .IgnoreQueryFilters()
    .ToListAsync();

// 只查询已删除记录
var deletedRecords = await context.TxPanels
    .IgnoreQueryFilters()
    .Where(t => t.IsDeleted)
    .ToListAsync();

// 统计已删除记录数
var deletedCount = await context.TxPanels
    .IgnoreQueryFilters()
    .CountAsync(t => t.IsDeleted);