EF Core无跟踪查询遇循环包含错误,如何实现动态计算字段?
问题场景
我尝试通过EF Core的无跟踪查询获取Invoice实体及关联数据,查询代码如下:
await _invoiceReadSet.AsNoTracking() .Include(i => i.Currency) .Include(i => i.Customer) .Include(i => i.Salesman) .Include(i => i.InvoiceItems).ThenInclude(ii => ii.Item) .Include(i => i.InvoiceItems).ThenInclude(ii => ii.Invoice) .SingleOrDefaultAsync(c => c.Guid == guid, cancellationToken);
这么做是为了在InvoiceItems的Dto中动态计算Price和Total字段,避免持久化这两个值,Dto代码如下:
public Guid InvoiceGuid { get; set; } public InvoiceDto Invoice { get; set; } public Guid ItemGuid { get; set; } public ItemDto Item { get; set; } public decimal Price => Invoice.Customer.PriceChoice == PriceChoice.Retail ? Item.RetailPrice : Item.WholesalePrice; public int Quantity { get; set; } public decimal Discount { get; set; } public decimal Total => Price * Quantity * Discount
执行时出现错误:
The Include path 'InvoiceItems->Invoice' results in a cycle. Cycles are not allowed in no-tracking queries; either use a tracking query or remove the cycle.
问题原因
你添加的.Include(i => i.InvoiceItems).ThenInclude(ii => ii.Invoice)属于冗余配置:EF Core在加载Invoice的InvoiceItems集合时,会自动将每个InvoiceItem的Invoice反向导航属性指向父Invoice实体,手动Include这个反向导航会形成Invoice -> InvoiceItems -> Invoice的循环引用,而无跟踪查询明确禁止这种循环Include配置。
解决方案
方案一:移除循环Include(优先推荐)
直接删除.Include(i => i.InvoiceItems).ThenInclude(ii => ii.Invoice)这一行,修改后的查询代码如下:
await _invoiceReadSet.AsNoTracking() .Include(i => i.Currency) .Include(i => i.Customer) .Include(i => i.Salesman) .Include(i => i.InvoiceItems).ThenInclude(ii => ii.Item) .SingleOrDefaultAsync(c => c.Guid == guid, cancellationToken);
修改后,每个InvoiceItem的Invoice导航属性依然会被EF Core自动赋值,完全不影响你在Dto中通过Invoice.Customer.PriceChoice动态计算Price字段。
方案二:改用跟踪查询(不推荐,仅作备选)
如果一定要保留这个循环Include(无实际必要),可以移除AsNoTracking()改用跟踪查询:
await _invoiceReadSet .Include(i => i.Currency) .Include(i => i.Customer) .Include(i => i.Salesman) .Include(i => i.InvoiceItems).ThenInclude(ii => ii.Item) .Include(i => i.InvoiceItems).ThenInclude(ii => ii.Invoice) .SingleOrDefaultAsync(c => c.Guid == guid, cancellationToken);
但跟踪查询会让实体进入变更追踪状态,对于仅用于映射Dto的场景,无跟踪查询的性能更优,因此优先选择方案一。
额外优化建议
你的InvoiceItemDto中同时保留了InvoiceGuid和Invoice导航属性,存在冗余,可根据实际使用场景保留其中一个即可;动态计算的Price和Total字段写法没问题,若后续需扩展逻辑,可考虑封装为私有方法或使用计算属性的完整写法(当前的表达式体写法已经足够简洁)。
内容的提问来源于stack exchange,提问作者Mohamad Al Jawad

