将EF Core查询从2.2转换为3.0-异步等待

时间:2019-11-18 14:14:21

标签: c# entity-framework async-await entity-framework-core ef-core-3.0

在EF Core 2.2中,我有:

      var data = await _ArticleTranslationRepository.DbSet
        .Include(arttrans => arttrans.Article)
        .ThenInclude(art => art.Category)
        .Where(trans => trans.Article != null && trans.Article.Category != null && trans.Article.Category.Id == categoryId.Value)
        .GroupBy(trans => trans.ArticleId)
        .Select(g => new { ArticleId = g.Key, TransInPreferredLang = g.OrderByDescending(trans => trans.LanguageId == lang).ThenByDescending(trans => trans.LanguageId == defaultSiteLanguage).ThenBy(trans => trans.LanguageId).FirstOrDefault() })
        .Select(at => at.TransInPreferredLang)
        .OrderBy(at => at.Article.SortIndex)
        .ToListAsync();

现在有了EF Core 3.0,我不得不写:

      var data = _ArticleTranslationRepository.DbSet
  .Include(arttrans => arttrans.Article)
  .ThenInclude(art => art.Category)
  .Where(trans => trans.Article != null && trans.Article.Category != null && trans.Article.Category.Id == categoryId.Value)
    .AsEnumerable() // client side groupby is not supported (.net core 3.0 (18 nov. 2019)
  .GroupBy(trans => trans.ArticleId)
  .Select(g => new { ArticleId = g.Key, TransInPreferredLang = g.OrderByDescending(trans => trans.LanguageId == lang).ThenByDescending(trans => trans.LanguageId == defaultSiteLanguage).ThenBy(trans => trans.LanguageId).FirstOrDefault() })
  .Select(at => at.TransInPreferredLang)
  .OrderBy(at => at.Article.SortIndex)
  .ToList();

我的asp.net核心mvc操作方法是异步的(public virtual async Task<ICollection<…>>… 因为我使用.AsEnumerable强制进行客户端评估,所以我还必须将.ToListAsync()更改为.ToList()并删除await运算符。

该查询正在运行,但会产生警告: This async method lacs 'await' operators and will run synchronously. Consider using the 'await operator ….

如何重写此EF Core 3.0查询,使其使用异步/等待。我不知道如何在单个查询/ linq表达式中包含AsAsyncEnumerable()

(我知道我可以将其分为“服务器”部分和“客户端”部分,但我想像以前一样在单个 async linq表达式中看到它EF Core 2.2之前的版本。)

1 个答案:

答案 0 :(得分:4)

这个想法似乎是将AsAsyncEnumerable()System.Linq.Async软件包组合在一起,该软件包为IEnumerable<T>提供了等效的LINQ(IAsyncEnumerable<T>)扩展方法。

因此,根据您的想法,如果您安装(或引用了软件包)该软件包,并在.AsAsyncEnumerable()之前插入了.GroupBy,则原始的相关查询应该可以正常工作。

尽管EF Core 3.0 DbSet<T>类存在一个烦人的问题。由于它同时实现了IQueryable<T>IAsyncEnumerable<T>接口,并且它们都不是“更好的”(更紧密的)匹配,因此许多在DbSet<T>上使用标准LINQ运算符的查询都将在编译时使用{ {1}}(歧义通话),并且需要添加CS0121。使用.AsQueryable() / Include之类的EF Core特定扩展的查询将起作用,因为它们已经解析为ThenInclude

正如评论中的某些人所提到的,可以使用IQueryable<T>来消除对(await [serverPart...].ToListAsync())[clientPart...]的需求以及相关的编译时方法的歧义性,但是它与使用System.Linq.Async的缺点相同通过创建不必要的内存中列表,而不是在同步方案中使用ToList()


当然,最好的办法是通过找到等效但完全可翻译的LINQ构造来完全避免客户评估。当前使用AsEnumerable()的代码很难,有时甚至是不可能的。甚至有可能,它需要通过消除GroupBy来重写先前的查询。例如,您可以从GroupBy开始查询,并将ArticleTranslation集合导航属性与{{1}一起使用,而不是从ArticleId开始查询并按Article进行分组。 }。对每个失败的查询重复该过程。这样做的好处是现在您的查询将首先在服务器端执行。