【问题标题】:EF Core Linq-to-Sql GroupBy SelectMany not working with SQL ServerEF Core Linq-to-Sql GroupBy SelectMany 不适用于 SQL Server
【发布时间】:2021-11-08 09:35:08
【问题描述】:

我正在尝试使用 LinqPad 使用 EF Core 连接到 SQL Server 的以下 Linq:

MyTable.GroupBy(x => x.SomeField)
       .OrderBy(x => x.Key)
       .Take(5)
       .SelectMany(x => x)




我收到此错误:

无法翻译 LINQ 表达式“x => x”。要么以可翻译的形式重写查询,要么切换到客户端评估显式......

但是,这是可行的:

MyTable.AsEnumerable()
       .GroupBy(x => x.SomeField)
       .OrderBy(x => x.Key)
       .Take(5)
       .SelectMany(x => x)

我的印象是 EF Core 应该能够翻译这样的表达式。

我做错了吗?

【问题讨论】:

    标签: c# sql-server entity-framework-core


    【解决方案1】:

    该异常消息是 EF Core 消息,而不是 EF6。

    在 EF 6 中,您的表达式应该可以工作,尽管末尾带有 ToList() 之类的东西。我怀疑您遇到的错误是您可能在实现集合之前尝试做更多事情,这与SelectMany 评估的组冲突。

    例如,像这样的 EF 可能会例外:

    var results = MyTable
        .GroupBy(x => x.SomeField)
        .OrderBy(x => x.Key)
        .Take(5)
        .SelectMany(x => x)
        .Select(x => new ViewModel { Id = x.Id, Name = x.Name} )
        .ToList();
    

    这样的事情应该在哪里起作用:

    var results = MyTable
        .GroupBy(x => x.SomeField)
        .OrderBy(x => x.Key)
        .Take(5)
        .SelectMany(x => x.Select(y => new ViewModel { Id = y.Id, Name = y.Name} ))
        .ToList();
    

    你不想使用:

    MyTable.AsEnumerable(). ...
    

    由于这是将整个表具体化到内存中,如果保证表保持相对较小,这可能没问题,但如果生产系统显着增长,它会随着时间的推移形成级联性能下降。

    编辑:做了一些挖掘,感谢这篇文章,因为它看起来确实是 EF Core 解析器中的另一个限制。 (不知道如何在 EF6 中工作的东西无法成功集成到 EF Core 中......我猜是在重新发明轮子)

    这应该可行:

    var results = MyTable
        .GroupBy(x => x.SomeField)
        .OrderBy(x => x.Key)
        .Take(5)
        .Select(x => x.Key)
        .SelectMany(x => _context.MyTable.Where(y => y.Key == x))
        .ToList();
    

    例如,我有一个 Parent 和 Child 表,我想按 ParentId 分组,选取前 5 个父母并选择他们所有的孩子:

    var results = context.Children
        .GroupBy(x => x.ParentId)
        .OrderBy(x => x.Key) // ParentId
        .Take(5)
        .Select(x => x.Key) // Select the top 5 parent ID
        .SelectMany(x => context.Children.Where(c => c.ParentId == x)).ToList();
    

    EF 通过在 DbSet 上针对选定的组 ID 执行 SelectMany 将其重新组合在一起。

    感谢此处的讨论:How to select top N rows for each group in a Entity Framework GroupBy with EF 3.1

    编辑 2:我看这个越多,感觉就越 hacky。另一种选择是将其分解为两个更简单的查询:

    var keys = MyTable.OrderBy(x => x.SomeField)
        .Select(x => x.SomeField)
        .Take(5)
        .ToList();
    
    var results = MyTable.Where(x => keys.Contains(x.SomeField))
        .ToList();
    

    我认为这可以翻译您的原始示例,但要点是首先选择适用的 ID/区分键,然后使用这些键查询所需的数据。因此,对于我的前 5 个有孩子的父母的所有孩子:

     var parentIds = context.Children
          .Select(x => x.ParentId)
          .OrderBy(x => x)
          .Take(5)
          .ToList();
     var children = context.Children
        .Where(x => parentIds.Contains(x.ParentId))
        .ToList();
    

    【讨论】:

    • 是的,你是对的 - EF Core .. 这是否意味着这个查询无论如何都不会翻译? (是的,我不想要 .AsEnumerable() 我只是用它来显示我的语法还可以)
    • 你能发布完整的查询吗? (即直到您调用 ToList() 或同等级别。
    • 这是我的完整查询
    • 添加了一个更新,其中包含一些应该可以工作的内容,以及一个指向进一步讨论 EF Core 限制的链接。
    • 嗯,这可能需要在 .Select(x => x.Key) 之后注入 Distinct() 如果查询没有完全尊重 Group By.. 总而言之,这感觉就像一个巨大的黑客工作围绕一些 EF6 能够解决的问题。 :)
    【解决方案2】:

    EF Core 对此类查询有限制,这在 EF Core 6 中已修复。这是 SQL 限制,对于此类 GroupBy 没有直接转换为 SQL。

    翻译此 GroupBy 时,EF Core 6 正在创建以下查询。

    var results = var results = _context.MyTable
        .Select(x => new { x.SomeField })
        .Distinct()
        .OrderBy(x => x.SomeField)
        .Take(5)
        .SelectMany(x => _context.MyTable.Where(y => y.SomeField == x.SomeField))
        .ToList();
    

    这不是此类任务的最佳查询,因为在 SQL 中,它可以通过窗口函数ROW_NUMBER()PARTITION on SomeField 来表示,并且可以省略附加的JOIN

    还要检查this function,它会自动进行此类查询。

    _context.MyTable.TakeDistinct(5, x => x.SomeField);
    

    【讨论】:

      猜你喜欢
      • 2020-06-15
      • 2021-12-28
      • 1970-01-01
      • 2021-01-11
      • 2012-07-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多