EF Core静态函数无需Include获取数据的实现及问题解析
EF Core 无Include下实现关联数据计算的方案
一、为什么静态函数无Include时返回0?
这是因为EF Core的导航属性默认是延迟加载,但延迟加载生效需要满足三个前提:
- 实体处于DbContext的跟踪状态
- DbContext在访问导航属性时仍然活跃
- 已启用延迟加载(默认不启用代理延迟加载,需额外配置)
你的Service查询完Drawdown实体后,如果DbContext已被释放(比如用using包裹DbContext,或Scoped生命周期的DbContext在Service方法结束后被回收),Mapper中访问Funding.DrawdownFundingCosts时,导航属性要么是null,要么是空集合,静态函数计算总和自然返回0。
二、不使用Include时,用静态函数整洁实现的思路
1. 投影查询+预计算数据
在Service层通过EF Core的Select投影,直接在数据库层面计算ExpenditureToDate,只获取需要的字段,再将预计算的值传递给静态函数处理:
// Service中的查询逻辑 public async Task<DrawdownDto> GetByIdAsync(int drawdownId) { var rawData = await _dbContext.Drawdowns .Where(d => d.Id == drawdownId) .Select(d => new { // 取出Drawdown的所有字段 Drawdown = d, // 关联Service下的Funding数据,同时预计算总和 Fundings = d.Service.Fundings.Select(f => new { Funding = f, Costs = f.DrawdownFundingCosts, PreCalculatedExpenditure = f.DrawdownFundingCosts.Sum(c => c.Cost) }) }) .FirstOrDefaultAsync(); // 调用Mapper的静态函数,传入预计算的值 return DrawdownMapper.MapToDto(rawData.Drawdown, rawData.Fundings); } // Mapper中的静态函数 public static DrawdownDto MapToDto(Drawdown drawdown, IEnumerable<dynamic> fundingData) { var dto = new DrawdownDto { // 映射Drawdown字段 Id = drawdown.Id, // ...其他Drawdown字段 Fundings = fundingData.Select(f => new FundingDto { // 映射Funding字段 Id = f.Funding.Id, // ...其他Funding字段 Costs = f.Costs.Select(c => new DrawdownFundingCostDto { Cost = c.Cost }), // 使用静态函数处理预计算值,复用逻辑 ExpenditureToDate = CalculateExpenditureToDate(f.PreCalculatedExpenditure) }).ToList() }; return dto; } // 可复用的静态计算函数 public static decimal CalculateExpenditureToDate(decimal? totalCost) { // 统一处理null值、精度调整等逻辑 return totalCost ?? 0; }
2. 控制DbContext生命周期+启用延迟加载
如果坚持要在静态函数中直接访问导航属性,需要:
- 安装
Microsoft.EntityFrameworkCore.Proxies包,在DbContext配置中添加options.UseLazyLoadingProxies()启用代理延迟加载 - 确保DbContext在Mapper处理时仍处于活跃状态(比如Scoped生命周期的DbContext在同一个请求内不提前释放)
但这种方式容易引发N+1查询问题(每个Funding都会触发单独查询获取DrawdownFundingCosts),性能损耗大,不推荐。
三、兼顾复用性与性能的最优方案
优先选择投影查询+静态函数复用的组合:
- 性能优势:投影查询只获取需要的字段,Sum计算在数据库层面完成,避免Include带来的冗余数据和笛卡尔积问题,内存占用更低,查询效率更高。
- 复用性优势:
CalculateExpenditureToDate静态函数可以在多个接口、Mapper中复用,统一处理总和的计算逻辑(比如空值处理、精度调整等)。
如果需要在多个查询中复用投影逻辑,还可以把投影表达式封装成静态方法,进一步提升代码复用性:
public static class DrawdownQueryExpressions { public static Expression<Func<Funding, FundingProjection>> FundingProjection => f => new FundingProjection { Funding = f, Costs = f.DrawdownFundingCosts, PreCalculatedExpenditure = f.DrawdownFundingCosts.Sum(c => c.Cost) }; }
内容的提问来源于stack exchange,提问作者Monset
相关产品推荐
相关产品推荐

