Files
ctbjrj/.trae/documents/箱号内部ID真隔离重构方案.md
T

151 lines
9.6 KiB
Markdown
Raw 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.
# 箱号内部ID 真隔离重构方案
## Context(为什么改)
**用户问题**:从方案库往报价页面重复插入"同一种箱号和图号"时,多个箱号实例会共用同一张元件表,元件行混在一起无法区分(串数据)。需求是"完全隔离不要串数据"。
**根因(已逐行核对代码确认)**:
1. [主窗口.菜单与库.cs:4066](file:///c:/Users/099978/Documents/SharpDevelop%20Projects/成套报价软件/成套报价软件/主窗口.菜单与库.cs#L4066) `renamedRow["内部ID"] = actualXhName` — 内部ID 直接赋值为重命名后的箱号显示名,毫无隔离作用
2. [主窗口.菜单与库.cs:4073](file:///c:/Users/099978/Documents/SharpDevelop%20Projects/成套报价软件/成套报价软件/主窗口.菜单与库.cs#L4073) `LookupTableByIndex("元件", targetTuhao, actualXhName, dstConn)` 命中已有元件表时复用,元件追加到旧表 → **串数据入口**
3. [扒图\Pdf\Form3.cs:4171](file:///c:/Users/099978/Documents/SharpDevelop%20Projects/成套报价软件/成套报价软件/扒图/Pdf/Form3.cs#L4171) `cmd.Parameters.AddWithValue("@nid", xianghao)` — 扒图代码同样把内部ID 写成箱号显示名(注释"主窗口同款约定")
4. [扒图\Pdf\Form3.cs:4193](file:///c:/Users/099978/Documents/SharpDevelop%20Projects/成套报价软件/成套报价软件/扒图/Pdf/Form3.cs#L4193) 注释"双键(2026-09-21 残留根治)"显示之前已有人尝试用"双键删除"治标,但未根治
**目标**:内部ID 改用 GUID 真隔离,箱号显示名只用于显示,所有读写元件表/箱号表的代码统一用内部ID。新插入永不复用旧元件表。
## 设计原则
1. **内部ID =** **`Guid.NewGuid().ToString("N")`**(32 位无连字符,跨库复制安全,全局唯一不撞)
2. **箱号表 + 图号表**新增 `内部ID TEXT` 列(扒图代码已有;主窗口代码已有图号表的,箱号表需确认)
3. **元件表索引的"箱号"字段**统一存内部ID(GUID)
4. **方案库插入时永远创建新元件表**,永不复用(即使 GUID 撞了也只是空表,安全)
5. **向后兼容**:老数据无"内部ID"列 → EnsureColumns 启动时补列;已有行内部ID 为空时**兜底为箱号显示名**(保证旧数据可用,新插入不再产生串数据)
6. **扒图代码双键删除逻辑保留**:老数据按显示名登记、新数据按 GUID 登记,删除时两种键都查(防孤儿)
## 实施阶段
### 阶段 1:方案库→项目库路径(用户场景精确入口)
**文件**:`成套报价软件\主窗口.菜单与库.cs`
* **`FakXianghao_Insert_Click`** **L3888-L4184**(方案→报价:箱号右键插入)
* L4037-4056 箱号显示名重命名(保留,避免 UI 显示重名)
* L4066 改:`renamedRow["内部ID"] = Guid.NewGuid().ToString("N")` (新 GUID,**不再等于 actualXhName**)
* L4073-4089 改:**删除复用判断**,无条件 `CreateFakYuanjianTable` + 新登记表索引(箱号字段写新 GUID)
* L4085 改:元件表索引的"箱号"参数从 `actualXhName` 改为新 GUID
* **`FakTuhao_Insert_Click`** **L3442-L3660**(方案→报价:图号右键插入)
* L3596 改:图号内部ID = 新 GUID(替换 `actualTh`)
* L3621 改:`r["内部ID"] = Guid.NewGuid().ToString("N")` — 每条箱号行重新生成 GUID,**不复用方案库原内部ID**
* L3657 改:元件表索引"箱号"参数从 `xhName` 改为对应箱号的新 GUID
* **`FakRead内部ID`** **L5187**:保留 fallback 到"箱号"列(旧数据兜底);新数据走 GUID
* **`EnsureColumns`** **调用**:L4034、L3180、L1130 在所有箱号表 EnsureColumns 列表里追加 `"内部ID"`(幂等补列)
### 阶段 2:扒图路径(双窗口同步改)
**文件**:`成套报价软件\扒图\Pdf\Form3.cs` 和 `成套报价软件\扒图\Cad\Form3.cs`(结构对称,同样改法;行号以 Pdf 版为准)
* **`CreateXianghaoTable`** **L1368**:CREATE TABLE 已含"内部ID"列 ✓ 不动;**`EnsureXianghaoTableExists`** **L1110** 同步确认 EnsureColumns 含"内部ID"
* **`InsertXianghao`** **L4139-L4181**:
* L4171 改:`cmd.Parameters.AddWithValue("@nid", Guid.NewGuid().ToString("N"))` (新 GUID,删除"内部ID=创建时的箱号名"注释)
* 方法签名增加 `out string newInternalId` 或返回 GUID,让调用方 `PaXianghaoInsert` 拿到新 GUID
* **`PaXianghaoInsert`** **L1872-L1960**:
* L1912 `lastInsertedXianghao` 改为接收 InsertXianghao 返回的 GUID
* L1954 `_currentXianghao = lastInsertedXianghao`(现在存 GUID)
* L1955 `SelectRowByValue(..., "内部ID", GUID)` — 列名从"箱号"改"内部ID"
* L1957 `ReloadYuanjianByXianghao` 传 GUID
* **`InsertTuhao`** **L4046**(图号插入):L4068 `@nid` 改新 GUID
* **`GetXianghaoFrameHandle L2437`** **/** **`GetXianghaoFrameHandleByTextHandle L2395`** **/** **`XianghaoExists L2386`**:参数语义改为内部ID,WHERE 改 `内部ID=@nid`;`GetXianghaoFrameHandleByTextHandle` 保留按"文本Handle"查的能力(同文本实体去重)
* **`DeleteXianghao`** **L4183**:双键删除逻辑保留(防孤儿),新增优先按内部ID 删
* **`ReloadYuanjianByXianghao`** 和 **`ReloadXianghaoByTuhao`**:内部按内部ID 查元件表索引;`ReloadXianghaoByTuhao` 加载后用第一行"内部ID"列设 `_currentXianghao`
* **`advancedDataGridView4_SelectionChanged`** **L5433**:L5439 改读"内部ID"列设置 `_currentXianghao`
* **`RecalcAllFromDatabase`** **L4991**:遍历箱号行用内部ID 作键查元件表
* **`CollectFrameHandlesForXianghao`** **L5674**:WHERE 改 `内部ID=@nid`;L5705 `GetYuanjianTableByIndex` 传内部ID
### 阶段 3:主窗口读取端 + AI 接口
**文件**:`成套报价软件\主窗口.cs` 和 `成套报价软件\AI助手接口.cs`
* **`主窗口.cs LoadSelectionDetail`** **L859-L1036**:
* L921 元件索引 SQL 天然按内部ID 查(阶段 1 改后元件索引"箱号"字段已为 GUID)✓
* L993 `取箱号图纸(conn, th, xh, ...)` 中 xh 现在是内部ID,需对应改 `取箱号图纸` 内部 SQL
* **`主窗口.cs 取箱号图纸`** **L1041 附近**:WHERE 改 `内部ID=@nid`,参数语义对应
* **`AI助手接口.cs L252-320`**:保留"双键兼容"逻辑(旧数据兜底),优先内部ID;新数据全部走 GUID 后显示名分支逐步成为空集
* **`主窗口.菜单与库.cs SelectRowByValue 调用`** L4165/L4167:已用"内部ID"列 ✓ 不动
## 数据库迁移策略
在 `EnsureQuoteDbExists` 入口(主窗口.菜单与库.cs L399、主窗口.cs L2787 附近的 .source)幂等执行:
1. **图号表**:`EnsureColumns(conn, TuhaoTableName, {"内部ID"})`(已有)
2. **所有现存箱号表**:遍历 `表索引 WHERE 类型='箱号'`,对每张实际表 `EnsureColumns(..., {"内部ID"})`
3. **回填老数据**:对每张箱号表 `UPDATE "<表>" SET 内部ID=箱号 WHERE 内部ID IS NULL OR 内部ID=''`;图号表 `UPDATE "<图号表>" SET 内部ID=图号 WHERE 内部ID IS NULL OR 内部ID=''`
4. **表索引回填**(可选):把元件索引的"箱号"字段从显示名升级为内部ID(不回填也不影响新逻辑,因新逻辑按 GUID 查;保留显示名作 fallback)
5. **GUID 不强求回填老数据**:老数据用显示名做内部ID 不会撞(除非同名重复,但同名重复本身就是历史 BUG 数据,新插入不再产生)
## 验证方法
1. **复现测试**:方案库 A 图号下"X1 箱号"(含 5 条元件)插入到报价项目"图号 T1"两次
* 期望:两个箱号行(显示名 X1、X1\_2),各自独立元件表,每张 5 条元件,互不污染
2. **数据库直查**:`SELECT 箱号, 内部ID FROM "xh_1"` — 两行的内部ID 应为不同的 32 位 GUID
3. **扒图回归**:扒一个新箱号 → `_currentXianghao` 应为 GUID;再扒同名箱号(同一文本实体)→ 应触发"重复删除"路径而非新增
4. **选型页**:选型明细按元件名称+规格匹配,"行ID"按 rowid 锁定单条物理行(已实现),不会因内部ID 变更而误并
5. **删箱号**:删除箱号 → 元件表 DROP + 索引行 DELETE;无孤儿表残留
6. **AI 接口**:调用 `/api/xuanxing` 确认活元件表列表正确排除孤儿
## 编译与合入策略
按项目记忆约定编译:`taskkill //F //IM 成套报价软件.exe` 后 `csc.exe "@../tools/verify_build.rsp"`(PowerShell 引号包响应文件),零输出为通过。
阶段 1+2+3 **建议一次性合入**,避免中间态 GUID/显示名混用导致联调错乱。
## 关键文件清单
* `成套报价软件\主窗口.菜单与库.cs` — 阶段 1 核心
* `成套报价软件\扒图\Pdf\Form3.cs` — 阶段 2
* `成套报价软件\扒图\Cad\Form3.cs` — 阶段 2(与 Pdf 版对称)
* `成套报价软件\主窗口.cs` — 阶段 3 读取端
* `成套报价软件\AI助手接口.cs` — 阶段 3 兼容性
## 风险与注意事项
1. **跨库复制的 GUID 唯一性**:方案库→项目库复制时**新生成 GUID**(不复用方案库原内部ID),避免方案库被多次插入到同一项目库时 GUID 撞车(虽然 GUID 撞概率极低,但新生成更安全)
2. **扒图双键删除保留**:L4193 的双键删除逻辑(显示名 + 内部ID)保留,防老数据孤儿;新数据天然走内部ID 单键
3. **AI 接口兼容**:保留"双键兼容"读取逻辑,旧数据库升级后立即生效,无需强制迁移
4. **`_currentXianghao`** **语义变更**:从"箱号显示名"改为"内部ID GUID"。所有比较相等的地方(如 `if (xh == _currentXianghao)`)需审查是否仍按显示名比较;建议增加 `_currentXianghaoName` 字段存显示名,仅用于 UI 显示