问题根源
eLockViolation:layerTable是以OpenMode.ForRead打开,UpgradeOpen失败,事务内读打开的LayerTable不能直接UpgradeOpen。
错误代码片段:
LayerTablelayerTable=(LayerTable)tr.GetObject(db.LayerTableId,OpenMode.ForRead);if(!layerTable.Has(layerName)){LayerTableRecordlayerRec=newLayerTableRecord{Name=layerName,Color=color};layerTable.UpgradeOpen();// 报错!layerTable.Add(layerRec);tr.AddNewlyCreatedDBObject(layerRec,true);}ZWCAD/Autocad .NET规则:
事务中,LayerTable用ForRead打开之后,不支持UpgradeOpen,不像BlockTable。
两种修复办法:
方案1:拿到LayerTable直接用OpenMode.ForWrite打开(推荐,最简单)
修改后的EnsureLayerExist代码(只替换这段逻辑)
LayerTablelayerTable=(LayerTable)tr.GetObject(db.LayerTableId,OpenMode.ForWrite);if(!layerTable.Has(layerName)){LayerTableRecordlayerRec=newLayerTableRecord{Name=layerName,Color=color};layerTable.Add(layerRec);tr.AddNewlyCreatedDBObject(layerRec,true);}直接去掉layerTable.UpgradeOpen();这一行,打开模式改为OpenMode.ForWrite。
简要说明
- 删除
layerTable.UpgradeOpen();,这行是报错元凶 - GetObject的时候直接
OpenMode.ForWrite打开LayerTable - 不用先ForRead再升级,LayerTable不支持UpgradeOpen,BlockTable才支持
备选方案(如果你不想全程ForWrite)
先ForRead判断是否存在;不存在,重新GetObject ForWrite:
LayerTablelayerTableRead=(LayerTable)tr.GetObject(db.LayerTableId,OpenMode.ForRead);if(!layerTableRead.Has(layerName)){layerTableRead.Dispose();LayerTablelayerTableWrite=(LayerTable)tr.GetObject(db.LayerTableId,OpenMode.ForWrite);LayerTableRecordlayerRec=newLayerTableRecord{Name=layerName,Color=color};layerTableWrite.Add(layerRec);tr.AddNewlyCreatedDBObject(layerRec,true);}推荐优先用方案1,代码简洁,管线插件创建图层场景几乎无性能影响。
顺带一个注意点(你之前OnStartDrawElement)
你现在是单独开事务做图层检查:
using(vartrCheckLayer=db.TransactionManager.StartTransaction()){if(!LayerHelper.IsLayerExists(trCheckLayer,db,targetLayerName)){LayerHelper.EnsureLayerExist(trCheckLayer,db,targetLayerName,layerColor);}trCheckLayer.Commit();}这个写法本身没问题,只要EnsureLayerExist内部按上面修复即可。
补充:eLockViolation另一个常见坑:WinForm窗体事件(TreeView双击)里操作CAD数据库,必须确保没有其他事务占用数据库锁,当前你是独立事务,修复LayerHelper代码就能解决。
你直接替换LayerHelper里面这段代码即可。
核心结论
你这段EnsureLayerExist在别的地方能用,不是代码本身错,是【调用时机+线程/消息上下文】不一样,TreeView双击WinForm事件里触发CAD数据库操作,会触发这个锁冲突,不是LayerTable的通用bug。
根本原因
ZWCAD .NET API 铁则:WinForm原生控件事件(TreeView.MouseDoubleClick、Button.Click这类UI消息回调),不属于CAD命令的事务上下文,UI消息抢占了CAD主数据库锁,UpgradeOpen就容易报eLockViolation
你其他地方调用EnsureLayerExist成功的场景:
✅ 从CAD命令行命令(CommandMethod标记的方法)内部执行:此时处于CAD主执行上下文,数据库锁是允许升级的,ForRead→UpgradeOpen正常工作。
❌ 当前场景:TreeView双击(WinForm窗口消息回调)
这个双击事件是Windows消息驱动,不在CAD命令执行栈内;此时数据库处于CAD主程序占用状态,你在事务里打开LayerTable为ForRead,尝试UpgradeOpen,直接锁冲突eLockViolation。
不是
layerTable.UpgradeOpen()函数写法错误,是上下文环境变了。
同样代码,放在[CommandMethod]方法里跑没问题;放在悬浮WinForm控件的双击事件里跑,大概率锁异常。
两种修复方案(不改你原有EnsureLayerExist内部逻辑!保留你原来的函数代码,别处还能继续正常用)
方案1(推荐,最简单):把CAD绘制逻辑丢到CAD命令上下文执行,不在Tree双击事件直接操作db
TreeView双击只发起CAD命令,数据库操作全部移到[CommandMethod]标记的函数里面。
- 双击事件里面,只把选中的
codeDefine暂存,调用ed.PostCommand()或者SendStringToExecute,触发CAD命令。 [CommandMethod]函数里面再执行绘制+图层创建,这里的上下文就是标准CAD命令环境,你的老EnsureLayerExist原封不动直接跑,不会锁报错。
示例示意(Tree双击事件只触发命令,不碰db)
privatevoidTreeView_MouseDoubleClick(objectsender,MouseEventArgse){varhit=_treeView.HitTest(e.Location);if(hit.Node==null||hit.Node.Nodes.Count>0)return;varcodeDef=hit.Node.TagasCodeDefine;if(codeDef==null)return;// 暂存选中要素_selectedDrawCode=codeDef;// 推送命令到CAD命令队列,切换到CAD安全上下文执行绘制HostApplication.WorkingEditor.SendStringToExecute("DRAW_PIPE_ELEM ",true,false,true);}// CAD命令,这里上下文正常,你的EnsureLayerExist可以原样调用不会锁异常[CommandMethod("DRAW_PIPE_ELEM")]publicvoidDrawPipeElementCmd(){if(_selectedDrawCode==null)return;OnStartDrawElement(_selectedDrawCode);}方案2:如果不想新增命令,临时适配(不推荐长期用)
UI事件里操作CAD数据库,不要复用你现在分开独立事务去创建图层,把图层检查创建合并到后面插入块的同一个事务中,减少多事务交替抢锁。
也就是:删掉OnStartDrawElement里面单独的
trCheckLayer图层事务,图层判断+创建放到后面添加块引用的trAddBlock事务里面,合并成单个事务,减少锁竞争。
一句话总结
你的
EnsureLayerExist代码完全没问题,在CAD命令方法内调用一切正常。
问题出在:悬浮WinForm的TreeView双击事件(Windows消息回调)直接操作ZwCAD数据库,上下文不同导致UpgradeOpen锁冲突。
解决思路:把CAD数据库操作逻辑挪到CommandMethod标记的CAD命令中执行。
补充小坑
悬浮WinForm属于独立窗口,UI消息线程和CAD主线程消息循环混杂,哪怕是不同事务,极易出现数据库对象锁异常;这种悬浮面板操作CAD实体,行业标准做法就是:UI仅选择参数,实际CAD数据库操作全部交给CommandMethod命令。
你看,是采用方案1,把绘制逻辑移到CommandMethod,还是我们尝试合并事务?