【问题标题】:Converting EF Core queries from 2.2 to 3.0 - async await将 EF Core 查询从 2.2 转换为 3.0 - 异步等待
【发布时间】:2020-03-13 22:32:52
【问题描述】:

在 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 actionmethod 是异步的 (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 查询以使其使用异步/等待。我不知道如何在 single 查询/linq 表达式中包含 AsAsyncEnumerable()

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

【问题讨论】:

  • 为什么需要强制客户端评估?而不是AsEnumerable 使用ToListAsync 并将其结果存储在一个变量中,然后对其进行其余的查询。
  • 因为使用 .GroupBy 时“不支持客户端 groupby”
  • @Fildor:我已经重写了它,它工作我正在使用 Asenumerable()。但它现在不再使用异步等待了。我确实可以将它拆分为服务器端部分和客户端部分,但我想知道是否可以使用例如 AsAsyncEnumerable 在单个 linq“表达式”中异步编写它。
  • 啊,我没抓住重点。是的,我同意,如果不将其撕成两半,我不知道怎么做。
  • 你可以做(await /* ... */.ToListAsync()).GroupBy( /* ... */ - 即将数据库执行的一半包裹在括号中,等待它,然后继续等待结果的链 - 在单个语句中完成。

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


【解决方案1】:

我在 dotnet core 5.0 和 6.0 上遇到了同样的问题,我这样解决了:

安装 nuget 包 System.Linq.Async 取决于你的 dotnet 版本https://www.nuget.org/packages/System.Linq.Async

然后在您的查询中,您将能够添加 .ToAsyncEnumerable()

这是一个例子:

        public async Task<IEnumerable<ProductTableView>> GetProduct(Pagination pagination, string identifier)
    {

        var ListProductsTable = await _context.ModelView
                                       .FromSqlRaw("GetProductByIdentifier {0}", identifier)
                                       .ToAsyncEnumerable()
                                       .Select(a => new ProductTableView
                                       {
                                           ID = a.ID,
                                           Guid = a.Guid,
                                           Guid_Product_Category = a.Guid_Product_Category,
                                           Guid_Currency = a.Guid_Currency,
                                           NameProduct = a.NameProduct,
                                       }).ToListAsync();

        return ListProductsTable;
    }

【讨论】:

    【解决方案2】:

    这个想法似乎是将AsAsyncEnumerable()System.Linq.Async 包相结合,后者为IAsyncEnumerable&lt;T&gt; 提供等效的LINQ (IEnumerable&lt;T&gt;) 扩展方法。

    因此,如果您安装(或包引用)该包,在.GroupBy 之前插入.AsAsyncEnumerable(),那么有问题的原始查询应该可以工作。

    虽然 EF Core 3.0 DbSet&lt;T&gt; 类存在一个烦人的问题。由于它同时实现了IQueryable&lt;T&gt;IAsyncEnumerable&lt;T&gt; 接口,并且它们都不是“更好”(更接近)的匹配,因此在DbSet&lt;T&gt; 上使用标准LINQ 运算符的许多查询将在编译时与CS0121 一起中断(模糊调用)并且需要添加.AsQueryable()。使用 EF Core 特定扩展(如 Include / ThenInclude)的查询将起作用,因为它们已经解析为 IQueryable&lt;T&gt;

    正如某些人在 cmets 中提到的,可以使用 (await [serverPart...].ToListAsync())[clientPart...],它消除了 System.Linq.Async 和相关编译时方法歧义的需要,但它与使用 ToList() 而不是 AsEnumerable() 具有相同的缺点通过创建一个不必要的内存列表来实现同步场景。


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

    【讨论】:

    • 为什么不使用正确的工具来完成这项工作,EF Core LINQ 被转换为用于简单查询的 sql 和用于GroupBy 查询的原始 sql ;)
    猜你喜欢
    • 2023-03-19
    • 2020-01-25
    • 1970-01-01
    • 2020-04-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-22
    • 1970-01-01
    相关资源
    最近更新 更多