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

9.6 KiB
Raw Blame History

箱号内部ID 真隔离重构方案

Context(为什么改)

用户问题:从方案库往报价页面重复插入"同一种箱号和图号"时,多个箱号实例会共用同一张元件表,元件行混在一起无法区分(串数据)。需求是"完全隔离不要串数据"。

根因(已逐行核对代码确认):

  1. 主窗口.菜单与库.cs:4066 renamedRow["内部ID"] = actualXhName — 内部ID 直接赋值为重命名后的箱号显示名,毫无隔离作用
  2. 主窗口.菜单与库.cs:4073 LookupTableByIndex("元件", targetTuhao, actualXhName, dstConn) 命中已有元件表时复用,元件追加到旧表 → 串数据入口
  3. 扒图\Pdf\Form3.cs:4171 cmd.Parameters.AddWithValue("@nid", xianghao) — 扒图代码同样把内部ID 写成箱号显示名(注释"主窗口同款约定")
  4. 扒图\Pdf\Form3.cs:4193 注释"双键(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 显示