求助:出现‘Another process is updating "table", Records will be lost’报错
这个报错在Acumatica开发里太常见了,本质是同一数据库记录被两个独立的操作上下文(主线程和PXLongOperation启动的后台线程)同时修改,触发了缓存的版本冲突检查——Acumatica这么做是为了防止你的修改意外覆盖掉其他操作的变更。结合你描述的场景:上传Excel后在DetailsForm的RowInserted事件更新字段,点击发布按钮又通过PXLongOperation执行二次更新,我给你几个针对性的解决方案:
核心原因分析
PXLongOperation.StartOperation会启动一个全新的后台线程,这个线程会创建独立的PXGraph实例和缓存上下文。如果主线程(比如你在PreLoadData函数里的操作)已经修改了缓存里的记录但还没同步到数据库,后台线程再去更新同一记录时,就会因为缓存版本不匹配触发这个错误——系统会误认为有另一个外部进程在修改这条记录,直接更新会导致数据丢失。
解决方案
1. 让后台线程基于最新数据库记录更新
不要让后台线程直接复用主线程缓存里的记录,而是在后台线程中重新从数据库获取最新版本的记录,再执行更新。这样能确保后台操作基于当前数据库的最新状态,彻底避免版本冲突:
// 主线程启动长操作时,只传递记录ID即可 PXLongOperation.StartOperation(this, delegate () { using (var transactionScope = new PXTransactionScope()) { // 创建独立的Graph实例,完全隔离主线程缓存 var targetGraph = PXGraph.CreateInstance<YourDetailsFormGraph>(); // 用记录ID查询数据库里的最新版本 ISLoanRegister targetDoc = targetGraph.Document.Search<ISLoanRegister.loanRegisterID>(doc.loanRegisterID); if (targetDoc != null) { // 在这里执行发布后的更新逻辑 targetDoc.TargetField = "UpdatedValue"; targetGraph.Document.Update(targetDoc); targetGraph.Actions.PressSave(); } transactionScope.Complete(); } });
2. 主线程修改后先同步到数据库
如果主线程RowInserted事件的更新必须在长操作前完成,那在启动PXLongOperation之前,先把主线程缓存的修改保存到数据库,让后台线程能拿到最新的记录:
private void PublishButton_Click(PXCache cache, PXRowSelectedEventArgs e) { ISLoanRegister doc = this.Document.Current; // 先保存主线程的修改(比如RowInserted里的字段更新) if (this.Document.Cache.GetStatus(doc) != PXEntryStatus.Notchanged) { this.Document.Save.Press(); } // 再启动长操作执行后续更新 PXLongOperation.StartOperation(this, delegate () { // 后台更新逻辑 }); }
3. 合并重复的更新逻辑
检查RowInserted事件的更新和发布按钮的更新是否有重复部分,如果其中一个更新可以整合到另一个操作里,就去掉重复步骤。比如把RowInserted里的字段更新逻辑移到发布的长操作中,统一在后台处理,从根源避免并发冲突。
4. 数据库层面锁定记录(可选)
如果你的场景需要严格保证记录不被其他操作干扰,可以在后台线程中用PXSelectForUpdate查询记录,这样会在数据库层面锁定这条记录,直到后台操作完成:
ISLoanRegister targetDoc = PXSelectForUpdate<ISLoanRegister, Where<ISLoanRegister.loanRegisterID, Equal<Required<ISLoanRegister.loanRegisterID>>>> .Select(targetGraph, doc.loanRegisterID);
额外提醒
永远记住:PXLongOperation的后台线程和主线程是完全独立的上下文,不要共享缓存对象。所有后台操作需要的数据,要么通过参数传递基本类型(比如ID),要么在后台线程中重新查询获取,不要直接传递主线程的缓存实体。
内容的提问来源于stack exchange,提问作者Eds

