将EF Core查询从2.2转换为3.0-异步等待 [英] Converting EF Core queries from 2.2 to 3.0 - async await

查看:240
本文介绍了将EF Core查询从2.2转换为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之前的版本.)

解决方案

这个想法似乎是将AsAsyncEnumerable()DbSet<T>上使用标准LINQ运算符的查询都将在编译时通过CS0121中断(歧义调用)并需要添加.AsQueryable().使用诸如Include/ThenInclude之类的EF Core特定扩展的查询将起作用,因为它们已经解析为IQueryable<T>.

正如评论中的某些人所提到的,可以使用(await [serverPart...].ToListAsync())[clientPart...]消除System.Linq.Async的需要以及相关的编译时方法的歧义,但是与使用ToList()代替AsEnumerable()具有相同的缺点.通过创建不必要的内存列表来实现同步方案.


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

In EF Core 2.2 I had:

      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();

Now with EF Core 3.0 I had to write:

      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();

My asp.net core mvc actionmethod is async (public virtual async Task<ICollection<…>>…) Because I used .AsEnumerable to force client side evaluation I also had to change .ToListAsync() to .ToList() and remove the await operator.

The query is working but produces a warning: This async method lacs 'await' operators and will run synchronously. Consider using the 'await operator ….

How can this EF Core 3.0 query be rewritten so that it uses async / await. I can't figure out how to include the AsAsyncEnumerable() in a single query/linq expression.

(I know that I can split it up in a 'server' part and a 'client-side' part, but I would like to see it in a single async linq expression as I had before in EF Core 2.2.)

解决方案

The idea seems to be combining the AsAsyncEnumerable() with System.Linq.Async package which provides equivalent LINQ (IEnumerable<T>) extension methods for IAsyncEnumerable<T>.

So by idea if you install (or package reference) that package, inserting .AsAsyncEnumerable() before .GroupBy, the original query in question should work.

There is an annoying issue though with EF Core 3.0 DbSet<T> class. Since it implements both IQueryable<T> and IAsyncEnumerable<T> interfaces, and none of them is "better" (closer) match, many queries using standard LINQ operators on DbSet<T> will simply break at compile time with CS0121 (ambiguous call) and will require adding .AsQueryable(). Queries which use EF Core specific extensions like Include / ThenInclude will work because they already resolve to IQueryable<T>.

As some people mentioned in the comments, it's possible to use (await [serverPart...].ToListAsync())[clientPart...] which eliminates the need of System.Linq.Async and associated compile time method ambiguity, but it has the same drawback as using ToList() instead of AsEnumerable() in the synchronous scenario by creating an unnecessary in-memory list.


Of course the best would be to avoid client evaluation at all by finding equivalent, but fully translatable LINQ construct. Which currently with GroupBy is hard, and sometimes even impossible. And even it is possible, it requires rewriting the previous query by eliminating the GroupBy. For instance, instead of starting the query from ArticleTranslation and grouping by ArticleId, you might start the query from Article and use the Translations collection navigation property with OrderByDescending()...FirstOrDefault() which is supported. Repeat the procedure for each failing query. The benefit will be that now your queries will execute server side as they should in the first place.

这篇关于将EF Core查询从2.2转换为3.0-异步等待的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆