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

360 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 关系说明
> 所属模块:[数据模型概览](../概览.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<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. 查询示例
### 基础查询
```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<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不物理删除 |
> **注意**:软删除后,关联记录不会被级联删除,因为这是软删除而非物理删除。
### 软删除操作示例
```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);
```