一、基本含义
AsNoTracking() 是 Entity Framework (EF) 和 Entity Framework Core (EF Core) 中的一个 LINQ 查询扩展方法,用于指示 EF 不跟踪查询返回的实体对象。
二、主要作用
1. 性能优化
cs
// 使用跟踪(默认)
var trackedUsers = context.Users.Where(u => u.IsActive).ToList();
// EF 会创建这些对象的快照并跟踪变化
// 使用非跟踪
var noTrackedUsers = context.Users.AsNoTracking()
.Where(u => u.IsActive)
.ToList();
// EF 不跟踪这些对象,减少内存占用和性能开销
2. 避免状态管理
-
跟踪实体:EF 会监视实体属性的变化
-
非跟踪实体:EF 将实体视为"只读",不监视变化
三、工作原理对比
EF、EFCore中的查询默认是跟踪的,使用AsNoTracking()禁用跟踪查询时,对实体类的更新不会起作用,但是对于大数据量的查询会显著提升查询效率。
| 特性 | 跟踪查询 (默认) | 非跟踪查询 (AsNoTracking) |
|---|---|---|
| 变更跟踪 | ✅ 启用 | ❌ 禁用 |
| 内存使用 | 较高(维护状态) | 较低 |
| 查询速度 | 稍慢 | 更快 |
| 标识解析 | ✅ 有 | ❌ 无 |
| 自动保存 | ✅ 支持 | ❌ 不支持 |
四、主要使用场景
1. 只读数据展示
cs
// 报表、统计、数据展示等只读场景
public List<OrderDto> GetRecentOrders(int days)
{
return context.Orders
.AsNoTracking()
.Where(o => o.OrderDate >= DateTime.Now.AddDays(-days))
.Select(o => new OrderDto
{
Id = o.Id,
CustomerName = o.Customer.Name,
TotalAmount = o.TotalAmount
})
.ToList();
}
2. 大数据量查询
cs
// 导出大量数据时使用
public async Task ExportUsersToCsv(string filePath)
{
var users = await context.Users
.AsNoTracking()
.Where(u => u.IsActive)
.ToListAsync();
// 处理大量数据,避免内存压力
await WriteToCsv(users, filePath);
}
3. DTO/ViewModel 映射
cs
public UserProfileDto GetUserProfile(int userId)
{
var user = context.Users
.AsNoTracking()
.Include(u => u.Addresses)
.Include(u => u.Orders)
.FirstOrDefault(u => u.Id == userId);
// 转换为 DTO,不需要跟踪
return _mapper.Map<UserProfileDto>(user);
}
五、注意事项
1. 更新非跟踪实体
cs
// 错误:直接修改不会保存
var user = context.Users.AsNoTracking().First(u => u.Id == 1);
user.Name = "New Name";
context.SaveChanges(); // 不会更新!
// 正确:需要先附加到上下文
var user = context.Users.AsNoTracking().First(u => u.Id == 1);
user.Name = "New Name";
context.Users.Update(user); // 或者 Attach + 设置状态
context.SaveChanges();
2. 导航属性延迟加载
cs
// 非跟踪实体不支持延迟加载
var order = context.Orders.AsNoTracking().First();
// var items = order.OrderItems; // 这会抛出异常!
// 解决方案:使用 Include 或显式加载
var order = context.Orders
.AsNoTracking()
.Include(o => o.OrderItems)
.First();
3. 性能权衡
**// 场景决定是否使用
- 需要多次更新同一实体 ❌ 不要使用 AsNoTracking
- 只读操作、数据展示 ✅ 推荐使用 AsNoTracking
- 复杂查询链 ✅ 考虑使用 AsNoTracking**
六、最佳实践建议
-
默认使用非跟踪 :除非明确需要跟踪,否则优先使用
AsNoTracking() -
明确查询意图:让代码清晰表达这是只读操作
-
结合性能监控:在大数据量场景下测试性能差异
-
注意生命周期:避免将非跟踪实体传递到需要跟踪的代码中
-
文档说明:在团队项目中建立统一的使用规范
核心原则 :如果知道实体需要被修改,就不要使用 AsNoTracking()。如果因为性能原因使用了非跟踪查询,更新时需要显式地将实体附加到上下文并标记为已修改状态。
七、使用
1、当然 如果代码中对查询和更新使用了两个独立的ORM框架 那么更新就不会受不跟踪的影响,
如下:
cs
// EF Core 查询(使用 AsNoTracking)
var stkTrayDtlLists = await (await _stkTrayDtlRepository.GetQueryableAsync())
.AsNoTracking() // ← EF Core 的变更跟踪设置
.Where(...)
.ToListAsync();
// SqlSugar 批量更新
await sqlSugarClient.Fastest<WmsStkTrayDtl>()
.BulkUpdateAsync(prepareUpdateStkTrayDtl); // ← SqlSugar 的批量更新
-
EF Core 的变更跟踪:内置的上下文跟踪机制
-
SqlSugar 的 BulkUpdate :直接根据实体属性值生成 SQL 语句并执行、SqlSugar 不依赖变更跟踪
2、显式更新(更新成功):
UpdateManyAsync会将它实体类附加到DbContext并标记为已修改。
cs
// EF Core 查询(使用 AsNoTracking)
var stkTrayDtlLists = await (await _stkTrayDtlRepository.GetQueryableAsync())
.AsNoTracking() // ← EF Core 的变更跟踪设置
.Where(...)
.ToListAsync();
await _stkTrayDtlRepository.UpdateManyAsync(prepareUpdateStkTrayDtl);