【问题标题】:.net core 6 - Entity Framework - when to use AsEnumerable AsQueryable.net core 6 - 实体框架 - 何时使用 AsEnumerable AsQueryable
【发布时间】:2022-01-03 15:41:12
【问题描述】:

背景

几周前,我已将我的项目从 .net core 2.2 迁移到 .net core 6。在过去的几周里,我发现 Entity Framework 呼叫在过去运作良好。

问题

以下代码曾经在 .net core 2.2 上运行良好:

using (var context = new MyContext())
{
        return await context.Accounts
                    .AsNoTracking()
                    .Where(account => SOME_CONDITIONS)
                    .ToArrayAsync()
                    .ConfigureAwait(false);
}

我们发现现在这段代码陷入了某种死锁,函数没有返回。

建议的解决方案

添加AsAsyncEnumerable 可以解决问题,并且函数执行得非常快:

using (var context = new MyContext())
{
        return await context.Accounts
                    .AsNoTracking()
                    .AsAsyncEnumerable()
                    .Where(account => SOME_CONDITIONS)
                    .ToArrayAsync()
                    .ConfigureAwait(false);
}

问题

我看到建议使用 AsEnumerable/AsAsyncEnumerable/AsQueryable 在 .net core 6 上使用 EF 进行调用。

什么时候应该使用AsEnumerable/AsAsyncEnumerable/AsQueryable

【问题讨论】:

  • 能否将帖子缩减为一个问题?我很难一次写出一个答案来解决所有问题
  • @TheTanic 我会试试的

标签: c# entity-framework .net-core


【解决方案1】:

从 EF Core 2.x 迁移到 3.x+ 时,您将看到的典型错误与 EF Core 中称为客户端评估的功能有关。这实际上所做的是设置它,以便当 EF 遇到无法组合到 SQL 的内容时(通常在 Where 子句中,但也可能在 Select 中),它将使用它可以组合的内容执行查询到那个时候到 SQL,然后针对内存中的实体重新运行其余部分。

在 EF Core 2.x 中,此功能默认启用。在 EF Core 3+ 中,它默认被禁用。客户端评估的危险类似于延迟加载,它可以帮助确保您的代码最终执行,但它可以隐藏潜在的破坏性性能问题,特别是在没有真正生产意识的情况下开发的新系统的情况下数据加载。当您只有几百条测试记录时,既好又快,但是当生产集跨越数百万行服务器试图加载到内存中时,它就会崩溃。

因此,在您的情况下,典型的罪魁祸首将是您的 Where 子句条件中的某些内容。这通常类似于在实体上调用方法或使用 EF 最终无法转换为 SQL 的 C# 函数。

Where 子句之前使用AsEnumerableToList 实质上将通过在尝试Where 子句之前将查询加载到内存中的实体来执行客户端评估。显然,这里的代价是将 所有 实体加载到内存中。或者,您可以选择启用客户端评估并获得大致相同的行为。

最好的解决方案是始终设法避免任何无法评估为 SQL 的事情。

需要考虑的其他解决方案:

将所有可以转换为 SQL 的 Where 条件移动到 Where 之前的 AsEnumerable。这将有助于确保为客户端评估加载最少的数据量。

例如,如果我有类似的东西:

return await context.Accounts
    .AsNoTracking()
    .Where(a => a.CreatedAt >= startDate && a.CreatedAt < endDate && someEvaluation(a))
    .ToArrayAsync()
    .ConfigureAwait(false);

someEvaluation 方法无法组合成 SQL,因此需要客户端评估。在 Where 子句之前使用 AsEnumerable 方法将涉及将 all 帐户加载到内存中。相反,我可以这样做:

return await context.Accounts
    .AsNoTracking()
    .Where(a => a.CreatedAt >= startDate && a.CreatedAt < endDate)
    .AsEnumerableAsync()
    .Where(a => someEvaluation(a))
    .ToArrayAsync()
    .ConfigureAwait(false);

至少在这种情况下,将被加载到内存中的帐户将被限制在日期范围内。

对于复杂的条件表达式,它可能没有那么简单。只要有可能,您应该努力使表达式保持纯粹的实体属性和最终可以归结为 SQL 的计算。

【讨论】:

  • 感谢您的详细回答。关于AsQueryable,是效果一样还是作用不一样?
  • 使用 EF Linq 查询时,该对象将已作为 IQueryableIOrderedQueryable 返回。 AsQueryable 通常仅在您想有条件地编写查询时才需要,例如仅在变量不为空时才添加 Where 子句,因此 C# 很高兴它将 DbSet 作为 IQueryable 使用。 var query = context.Accounts.AsQueryable(); if (!string.IsNullOrEmpty(nameFilter)) query = query.Where(x =&gt; x.AccountName.StartsWith(nameFilter)); 那种事。 AsQueryable 不会执行对内存的查询,而AsEnumerable / ToList 将执行此操作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-26
  • 1970-01-01
  • 2017-01-14
  • 2017-12-15
  • 2015-07-21
  • 2015-02-28
  • 1970-01-01
相关资源
最近更新 更多