EF Core中.Include与.AsNoTracking的正确用法及相关疑问
AsNoTracking() 使用位置与性能问题解答
背景
早年不少技术帖子对AsNoTracking()的使用位置存在争议:部分观点认为需在查询物化前多次指定,部分则认为仅在查询顶部设置即可作用于全局,但这类旧帖大多不适用于EF6及以上版本,尤其是EF Core场景。以下是包含多实体关联、Include与Union的复杂查询示例:
var dataSet1 = _dbContext.MarketTransactions.AsNoTracking() .AsSplitQuery() .Include(a => a.Commodity).ThenInclude(a => a.Product).AsNoTracking() .Include(a => a.Contract).ThenInclude(a => a.Customer).AsNoTracking() .Include(a => a.Offer).ThenInclude(a => a.Customer).AsNoTracking() .Where(f => f.IsActive && (f.Source == EMarketTransactionSource...)) .GroupJoin( _dbContext.HedgeAccounts.AsNoTracking(), transaction => transaction.MarketAccount, hedgeAcct => hedgeAcct.Account, (transaction, hedgeAcct) => new { Transaction = transaction, HedgeAccounts = hedgeAcct }) .SelectMany( x => x.HedgeAccounts.DefaultIfEmpty(), (x, hedgeAcct) => new { x.Transaction, HedgeAccount = hedgeAcct }) .Select(f => new ...); var dataSet2 = _dbContext.Set<OfferMonitoring>().AsNoTracking() .AsSplitQuery() .Include(a => a.Offer).ThenInclude(a => a.Commodity).ThenInclude(a => a.Product).AsNoTracking() .Include(a => a.Offer).ThenInclude(a => a.Customer).AsNoTracking() .Where(f => f.Action.Value == EOfferMonitorAction....) .Select(f => new ...); var query = dataSet1.Union(dataSet2); var total = await query.CountAsync(cancellationToken); query = query.OrderByDescending(x => x.UpdatedOn ?? x.CreationDate); query = query.GetPagedQuery(request.Filter.Start, request.Filter.Limit); var items = await query.ToListAsync(cancellationToken);
问题解答
1. AsNoTracking() 应在查询开头全局设置一次,还是在每个Include或Join子句前设置?
在EF Core 3.x及以上版本中:
- 根查询起始位置调用一次
AsNoTracking(),会自动作用于所有后续Include/ThenInclude加载的关联实体,无需在每个Include分支重复调用。 - 对于
GroupJoin/Join中单独从DbContext获取的独立查询源(比如示例中的_dbContext.HedgeAccounts),需要单独添加AsNoTracking(),因为这类子查询是独立的查询根,不会继承主查询的跟踪设置。 - 对于
Union这类组合查询,每个分支查询(dataSet1、dataSet2)需要各自设置AsNoTracking(),因为它们是独立构建的查询链。
简言之:根查询+独立子查询源各设置一次即可,无需在Include后重复调用。
2. 两种用法的性能影响如何,尤其是结果集较大的情况下?
- 重复调用
AsNoTracking()不会带来性能提升:EF Core内部会忽略重复的无跟踪设置(只要已启用无跟踪,后续调用不会改变状态),只会增加代码冗余。 - 漏加独立查询源的
AsNoTracking()会显著增加开销:如果关联的子查询实体被跟踪,EF需要维护每个实体的状态快照,结果集越大,内存占用越高,后续GC压力也会越大,查询整体性能会下降。 - 正确的用法(根查询+独立子查询源各一次)是性能最优的:既避免了状态跟踪的额外内存与CPU开销,又保持了代码简洁。
3. Entity Framework Core不同版本间在行为或性能上是否存在差异?
- EF Core 1.x-2.x:早期版本中
AsNoTracking()的作用范围有限,Include加载的实体可能需要单独设置,子查询也不会继承主查询的跟踪设置,这也是早年帖子争议的核心原因。 - EF Core 3.x及以上:官方明确了
AsNoTracking()的作用规则,根查询的设置会覆盖所有Include/ThenInclude的关联实体;同时对无跟踪查询做了底层优化,减少了不必要的状态跟踪逻辑,性能比早期版本提升明显,尤其是大结果集场景下内存占用差异更显著。 - 注意:即使在EF Core 3.x+中,独立子查询源仍需单独设置
AsNoTracking(),不会自动继承主查询的设置。
内容的提问来源于stack exchange,提问作者nop
相关产品推荐
相关产品推荐

