几年前有这么多帖子,在具体化查询之前,你需要指定一次.AsNoTracking;然后有人说它是错误的,你必须在顶部做一次,这将使它对整个查询有效.然而,这些帖子真的很旧,可能与EF6及以上版本无关.

考虑以下复杂的查询场景,涉及多个实体和includes(我删除了部分WHERE子句和投影,只是为了最小化查询的大小):

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()应该在查询开始时全局应用一次(_dbContext.MarketTransactions.AsNoTracking()_dbContext.Set<OfferMonitoring>().AsNoTracking()),还是应该在每个include或join子句之前应用?
  2. 每种方法的性能影响是什么,特别是考虑到结果集的潜在规模?
  3. Entity Framework Core版本之间在行为或性能上是否存在任何需要考虑的差异?

推荐答案

是否应该在查询开始时全局应用一次.AsNoTracking(),还是应该在每个include或join子句之前应用它?

AsNoTracking设置了整个查询的跟踪行为,后续调用将不会有任何影响,所以使用单个调用(我个人更喜欢在查询开始时添加它,因为更好的可读性)

每种方法的性能影响是什么,特别是考虑到结果集的潜在规模?

根据经验,AsNoTracking应该在只读场景中产生更好的性能,但如果由于某种原因EF Core映射/查询处理成为瓶颈(这很少,通常性能问题在于查询转换和/或数据库),您应该始终自己对其进行基准测试.

我强烈建议阅读文档的Tracking vs. No-Tracking Queries部分和Efficient Querying部分,其中给出了跟踪与不跟踪查询的以下比较:

下面是一个基准测试的结果,比较了一个查询加载10个博客,每个博客加载20个帖子的跟踪和不跟踪行为.The source code is available here,请随时使用它作为您自己测量的基础.

Method NumBlogs NumPostsPerBlog Mean Error StdDev Median Ratio RatioSD Gen 0 Gen 1 Gen 2 Allocated
AsTracking 10 20 1,414.7 us 27.20 us 45.44 us 1,405.5 us 1.00 0.00 60.5469 13.6719 - 380.11 KB
AsNoTracking 10 20 993.3 us 24.04 us 65.40 us 966.2 us 0.71 0.05 37.1094 6.8359 - 232.89 KB

同样—如果这部分性能是一个问题—基准测试您的实际场景(包括实际的数据分布),还有一个值得考虑的选项—AsNoTrackingWithIdentityResolution理论上可以提供一些性能好处,但我无法找到这样的场景在我的情况下.

Csharp相关问答推荐

自定义JsonEditor,用于将SON序列化为抽象类

CsWin32如何创建PWSTR的实例,例如GetWindowText

MAUI查询参数单一字符串项将不起作用

从.Net 6 DLL注册和检索COM对象(Typelib导出:类型库未注册.(异常来自HRESULT:0x80131165))

如何使用NumberFormatInfo

TCPClient阅读流

有没有类似于扩展元素的合并元组的语法?

该函数不能检测两条曲线的交点

EF核心区分大小写的主键

当试图限制EF Select 的列时,如何避免重复代码?

.NET 6:如何防止系统生成的日志(log)?

链接到字典字符串.拆分为(.Key,.Value)

当空判断结果赋给变量时,为什么会出现可能空异常警告的解引用?

我的命名管道在第一次连接后工作正常,但后来我得到了System.ObjectDisposedException:无法访问关闭的管道

我可以强制System.Text.Json.JsonSerializer以非递归方式工作吗?

如何使用ODP.NET C#设置Oracle会话时间长度限制

为什么我可以在注册表编辑器中更改值,但不能在以管理员身份运行的C#表单应用程序中更改?

无法向Unity注册Microsoft Logger

获取应用程序版本信息时出现奇怪信息

在使用.NET EF Core DbContext属性之前,是否应使用null判断