You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

DevExpress EFCore审计模块UpdateDefaultString空引用异常

问题描述

我的PurchaseOrder业务对象定义如下:

public int? ReceivedByStaffId { get; set; }
[ForeignKey("ReceivedByStaffId")]
public virtual Staff ReceivedBy { get; set; }

项目中使用DevExpress Audit审计模块,控制器调用objectSpace.CommitChanges()提交数据时抛出空引用异常,异常堆栈核心触发点为DevExpress.Persistent.BaseImpl.EFCore.AuditTrail.AuditDataItem.UpdateDefaultString方法,完整异常信息如下:

System.Exception: SaveChanges: inner: 
 ex:System.NullReferenceException: Object reference not set to an instance of an object.
   at DevExpress.Persistent.BaseImpl.EFCore.AuditTrail.AuditDataItem.UpdateDefaultString(IEFCoreWeakReference objectForUpdate, EntityEntry entityEntry, Object key)
   at DevExpress.Persistent.BaseImpl.EFCore.AuditTrail.AuditDataItem.UpdatePersistentData(List`1 weakReferences)
   at DevExpress.Persistent.BaseImpl.EFCore.AuditTrail.AuditDataContainer.AttachAuditData(DbContext dbContext)
   at DevExpress.Persistent.BaseImpl.EFCore.AuditTrail.AuditDataContainer.SaveAuditData(DbContext dbContext)
   at DevExpress.Persistent.BaseImpl.EFCore.AuditTrail.AuditTrailService.AuditedDbContext_SavedChanges(Object sender, SavedChangesEventArgs e)
   at Microsoft.EntityFrameworkCore.DbContext.SaveChanges(Boolean acceptAllChangesOnSuccess)
   at MyApp2.Module.BusinessObjects.MyAppDbContext.SaveChanges() in D:\dev\MyApp2\MyApp2.Module\BusinessObjects\MyAppDbContext.cs:line 462 
  stacktrace:    at System.Environment.get_StackTrace()
   at MyApp2.Module.BusinessObjects.MyAppDbContext.SaveChanges() in D:\dev\MyApp2\MyApp2.Module\BusinessObjects\MyAppDbContext.cs:line 462
   at DevExpress.ExpressApp.EFCore.EFCoreObjectSpace.DoCommit()
   at DevExpress.EntityFrameworkCore.Security.SecuredEFCoreObjectSpace.DoCommit()
   at DevExpress.ExpressApp.BaseObjectSpace.CommitChanges()
   at MyApp2.Module.Win.Features.PurchFeature.POStateViewController.actPurchStateMenu_Execute(Object sender, SingleChoiceActionExecuteEventArgs args) in D:\dev\MyApp2\MyApp2.Module.Win\Features\PurchFeature\POStateViewController.cs:line 207
   at DevExpress.ExpressApp.Actions.ActionBase.ExecuteCore(Delegate handler, ActionBaseEventArgs eventArgs)
   at DevExpress.ExpressApp.Actions.SingleChoiceAction.DoExecute(ChoiceActionItem selectedItem)
   at DevExpress.XtraBars.Templates.ActionControls.Binding.SingleChoiceActionBinding.ActionControl_Execute(Object sender, SingleChoiceActionControlExecuteEventArgs e)
   at DevExpress.XtraBars.BarItem.OnClick(BarItemLink link)
   at DevExpress.XtraBars.BarItemLink.OnLinkClick()
   at DevExpress.XtraBars.BarButtonItemLink.OnLinkAction(BarLinkAction action, Object actionArgs)
   at DevExpress.XtraBars.ViewInfo.BarSelectionInfo.UnPressLink(BarItemLink link)
   at DevExpress.XtraBars.Controls.CustomLinksControl.OnMouseUp(MouseEventArgs e)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at DevExpress.XtraBars.Controls.CustomControl.WndProc(Message& msg)
   at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam)
   at Interop.User32.DispatchMessageW(MSG& msg)
   at Interop.User32.DispatchMessageW(MSG& msg)
   at System.Windows.Forms.Application.ComponentManager.Interop.Mso.IMsoComponentManager.FPushMessageLoop(UIntPtr dwComponentID, msoloop uReason, Void* pvLoopData)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(msoloop reason, ApplicationContext context)
   at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(msoloop reason, ApplicationContext context)
   at System.Windows.Forms.Application.Run()
   at DevExpress.ExpressApp.Win.WinApplication.Start()
   at MyApp2.Win.Program.Main() in D:\dev\MyApp2\MyApp2.Win\Program.cs:line 112 
   at MyApp2.Module.BusinessObjects.MyAppDbContext.SaveChanges() in D:\dev\MyApp2\MyApp2.Module\BusinessObjects\MyAppDbContext.cs:line 468
   at DevExpress.ExpressApp.EFCore.EFCoreObjectSpace.DoCommit()
   at DevExpress.EntityFrameworkCore.Security.SecuredEFCoreObjectSpace.DoCommit()
   at DevExpress.ExpressApp.BaseObjectSpace.CommitChanges()
   at MyApp2.Module.Win.Features.PurchFeature.POStateViewController.actPurchStateMenu_Execute(Object sender, SingleChoiceActionExecuteEventArgs args) in D:\dev\MyApp2\MyApp2.Module.Win\Features\PurchFeature\POStateViewController.cs:line 207
产生原因
  • 核心问题是可空外键对应的导航属性为null时,EF Core审计模块的UpdateDefaultString方法未做空值校验:该方法逻辑是读取关联实体的默认展示属性(即实体类标记的DefaultProperty,比如Staff的姓名、工号字段)生成审计记录的展示文本,当ReceivedBy导航属性为null时,直接访问实例属性就会触发空引用。
  • 常见触发场景有两类:一是更新采购单时只给外键字段ReceivedByStaffId赋了值,没有同步加载、赋值对应的ReceivedBy导航属性,当前DbContext跟踪缓存中也没有对应主键的Staff实体;二是ReceivedByStaffId本身为null,ReceivedBy导航属性未显式赋值为null,处于不确定的跟踪状态。
  • 22.2及更早版本的DevExpress组件存在这个已知bug,审计模块处理可空外键导航属性的逻辑缺失空判断分支。
解决方案

按落地成本从低到高排列:

  • 升级DevExpress组件到23.1及以上稳定版本,官方已经在新版本中修复了可空导航属性的空值处理逻辑,不需要额外修改业务代码。
  • 暂时无法升级版本时,提交变更前保证导航属性状态明确:不要只给外键字段赋值,同步给导航属性赋值,关联存在时显式查询对应Staff实体赋值,外键为null时直接给导航属性赋值null,避免属性处于未跟踪的不确定状态,示例代码:
    if(po.ReceivedByStaffId.HasValue){
        po.ReceivedBy = objectSpace.GetObjectByKey<Staff>(po.ReceivedByStaffId.Value);
    }else{
        po.ReceivedBy = null;
    }
    objectSpace.CommitChanges();
    
  • 自定义审计服务逻辑:继承默认的AuditTrailService,重写审计数据保存逻辑,在读取导航属性展示值前先判断关联实体是否为null,为null时直接存储空字符串或外键值,替换容器中默认的审计服务注册即可。
  • 不需要审计该关联字段变更的场景,可以直接在审计配置中添加ReceivedBy属性的忽略规则,跳过该字段的审计记录生成。

内容的提问来源于stack exchange,提问作者Kirsten

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 10:09:46