EF Core 5 含SUM逻辑的Gross计算属性导致应用启动无报错退出
EF Core 5 实体计算属性引发应用无提示崩溃问题排查
问题背景
在基于EF Core 5开发的销售单模块中,定义了带计算属性的销售单、销售单明细实体,配置完成后出现应用启动即直接退出、Visual Studio未弹出任何错误提示的异常。移除SalesOrder实体中的Gross计算属性后,应用可正常运行,现有查询逻辑也可正常执行。
涉及的实体定义代码
销售单明细实体
public class SalesOrderItem { public int SalesOrderItemId { get; set; } public int? Qty { get; set; } public decimal? UnitPrice { get; set; } public string Description { get; set; } [ForeignKey("SalesOrderId")] public int SalesOrderId { get; set; } [NotMapped] public decimal LineTotal => (Qty == null ? 0 : Qty.Value) * (UnitPrice == null ? 0 : UnitPrice.Value); }
销售单实体
public class SalesOrder { public int SalesOrderId { get; set; } public int VatCodeId { get; set; } public virtual IEnumerable<SalesOrderItem> SalesOrderItems { get; set; } [NotMapped] public decimal? Net { get { if (SalesOrderItems == null) return null; return SalesOrderItems.Sum(p => p.LineTotal); } } [NotMapped] public decimal? Tax { get { if (Net == null) return null; return (Net.Value * VatCode.Percentage) / 100; } } [NotMapped] public decimal? Gross { get { if (Net == null || VatCode == null) return null; return Net.Value + Gross.Value; } } }
数据查询逻辑
var salesOrder = await _context.SalesOrder .Include(p => p.VatCode) .Include(i => i.SalesOrderItems) .SingleOrDefaultAsync(i => i.SalesOrderId == salesOrderId);
崩溃根因
- 核心触发点:Gross属性无限递归导致栈溢出
Gross属性的getter逻辑中出现笔误,将返回值写为Net.Value + Gross.Value,相当于每次读取Gross属性时,都会再次触发自身的getter执行,形成无终止的自我递归调用,快速占满进程调用栈,触发栈溢出异常。
栈溢出是.NET运行时层面的严重异常,无法通过普通try-catch逻辑捕获,触发后会直接终止进程;如果Visual Studio未配置针对栈溢出的异常调试断点,就会表现为应用无任何错误提示直接退出。 - 附带的潜在运行隐患(非本次崩溃的直接原因,但会引发后续业务异常)
Tax属性的getter直接访问VatCode.Percentage,未对VatCode做空值判断,当VatCode未完成加载、实体处于断连状态时,会直接触发空引用异常SalesOrderItems集合定义为IEnumerable<SalesOrderItem>,虽然EF Core可以正常完成数据填充,但后续业务场景中对集合的新增、删除操作会出现兼容问题,更推荐使用ICollection<SalesOrderItem>类型定义导航属性
修复方案
- 修正
Gross属性的递归逻辑,按照业务语义,含税总额应为净额加税额,而非自身加自身 - 为
Tax属性补充VatCode的空值判断,避免空引用 - 优化导航属性的类型定义
修正后的核心计算属性代码:
[NotMapped] public decimal? Tax { get { if (Net == null || VatCode == null) return null; return (Net.Value * VatCode.Percentage) / 100; } } [NotMapped] public decimal? Gross { get { if (Net == null || Tax == null) return null; return Net.Value + Tax.Value; } }
内容的提问来源于stack exchange,提问作者steve_c123
相关产品推荐
相关产品推荐

