# 关系说明 > 所属模块:[数据模型概览](../概览.md) --- ## 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 ```mermaid 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 ```mermaid erDiagram test_scenario ||--o{ qfod_record : "包含" ``` | 属性 | 说明 | |------|------| | 类型 | 一对多(1:N) | | 外键 | qfod_record.scenario_id → test_scenario.id(必需) | | 删除行为 | CASCADE(删除场景时同步删除关联记录) | | 业务含义 | 一个测试场景产生多条 Qfod 检测数据 | ### 2.3 test_scenario → ploss_record ```mermaid erDiagram test_scenario ||--o{ ploss_record : "包含" ``` | 属性 | 说明 | |------|------| | 类型 | 一对多(1:N) | | 外键 | ploss_record.scenario_id → test_scenario.id(必需) | | 删除行为 | CASCADE(删除场景时同步删除关联记录) | | 业务含义 | 一个测试场景产生多条 Ploss FOD 数据 | --- ## 3. EF Core 配置 ```csharp // Data/WctMinerDbContext.cs protected override void OnModelCreating(ModelBuilder modelBuilder) { // 维度表 → 场景表 关系配置 modelBuilder.Entity(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(entity => { entity.HasOne(e => e.Scenario) .WithMany(s => s.QfodRecords) .HasForeignKey(e => e.ScenarioId) .OnDelete(DeleteBehavior.Cascade); // 删除场景时同步删除记录 }); // 场景表 → PlossRecord 关系配置 modelBuilder.Entity(entity => { entity.HasOne(e => e.Scenario) .WithMany(s => s.PlossRecords) .HasForeignKey(e => e.ScenarioId) .OnDelete(DeleteBehavior.Cascade); }); } ``` --- ## 4. 查询示例 ### 基础查询 ```csharp // 查询场景及其所有维度属性 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}"); ``` ### 按维度筛选 ```csharp // 查询指定面板类型的所有场景 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(); ``` ### 按维度分组统计 ```csharp // 按面板类型统计 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(); ``` ### 多维度联合查询 ```csharp // 查询指定面板+硬件+软件的所有场景 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. 软删除设计 ### 全局查询过滤器 ```csharp // Data/WctMinerDbContext.cs protected override void OnModelCreating(ModelBuilder modelBuilder) { // 维度表 → 场景表 关系配置 modelBuilder.Entity(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().HasQueryFilter(e => !e.IsDeleted); modelBuilder.Entity().HasQueryFilter(e => !e.IsDeleted); modelBuilder.Entity().HasQueryFilter(e => !e.IsDeleted); modelBuilder.Entity().HasQueryFilter(e => !e.IsDeleted); modelBuilder.Entity().HasQueryFilter(e => !e.IsDeleted); modelBuilder.Entity().HasQueryFilter(e => !e.IsDeleted); modelBuilder.Entity().HasQueryFilter(e => !e.IsDeleted); } ``` ### 删除行为说明 | 操作 | 行为 | 说明 | |------|------|------| | 删除维度 | 软删除 | 设置 is_deleted = true,不物理删除 | | 删除场景 | 软删除 | 设置 is_deleted = true,关联记录保留 | | 删除记录 | 软删除 | 设置 is_deleted = true,不物理删除 | > **注意**:软删除后,关联记录不会被级联删除,因为这是软删除而非物理删除。 ### 软删除操作示例 ```csharp // 软删除维度 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(); } } ``` ### 查询已删除记录 ```csharp // 包含已删除记录的查询 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); ```