【问题标题】:Group by multiple columns as if it was one column按多列分组,就好像它是一列
【发布时间】:2019-12-01 17:55:04
【问题描述】:

有没有办法按多个列进行分组,就好像这些列在 C# 中使用 Linq 合并一样?

假设我有一个对象表示从一个用户发送给另一个用户的消息:

public class Message
{
    public Int32 SenderId { get; set; }
    public Int32 RecipientId { get; set; }
    public String Text { get; set; }
    public DateTime SentAt { get; set; }
}

我从存储库中获取了一组这些,然后我必须按发送日期对它们进行排序,并按配对 SenderId, RecipientId 过滤掉它们。但我只想从两个用户之间的给定对话中取回最后一条消息。方向无所谓,所以

(SenderId == 1 && RecipientId == 2) 

应该被视为

(SenderId == 2 && RecipientId == 1)

到目前为止,我得到了这个:

Messages
    .GroupBy(m => new { m.SenderId, m.RecipientId })
    .Select(gm => gm.OrderByDescending(m => m.SentAt).FirstOrDefault());

但是这种方法的问题是当我有一条消息从用户 1 发送到用户 2 时,反之亦然 2 => 1,两者都会被返回。因为键 (1,2) 与 (2,1) 不同。

任何建议都会很棒。谢谢!

UPD:我应该补充说我正在使用带有 IQueryable 表达式的表达式。所以它是 LINQ to Entity。

【问题讨论】:

  • 您使用的是哪个 .NET 和 C# 版本?您可以将键转换为元组并为此使用自定义比较器
  • 我使用 .net core 2.2 和任何版本的 C#。但它不支持元组。但如果我找不到解决方案,我会升级。可以举个例子吗?
  • 其实它支持元组,你可以尝试将(m => new { m.SenderId, m.RecipientId } 更改为m => (m.SenderId, m.RecipientId) 并检查它是如何工作的
  • 它在我的代码下划线并说“表达式树可能不包含元组文字。”
  • 它是 VS Code 和 netstandard2.0 库,根据此表 docs.microsoft.com/en-us/dotnet/csharp/language-reference/… 默认使用 C# 7.3。我真的不明白为什么我不能使用命名元组。

标签: c# .net entity-framework linq


【解决方案1】:

即使您将问题标记为,但似乎并不是每个人都明白许多内存解决方案会失败,因为它们没有等效的 SQL。这是一个查询,它将为许多不同的IQueryable 实现和查询提供程序转换为 SQL:

Messages
    .GroupBy(m => new
    {
        MinId = m.SenderId <= m.RecipientId ? m.SenderId : m.RecipientId,
        MaxId = m.SenderId > m.RecipientId ? m.SenderId : m.RecipientId
    })
    .Select(gm => gm.OrderByDescending(m => m.SentAt).FirstOrDefault());

使用 Math.Min / Math.Max 只能在 Entity Framework 核心 2 中使用,因为这是唯一一个自动切换到客户端评估的 EF 版本,用于对不转换为 SQL 的查询的任何部分进行评估。这在 EF 核心 3 中被放弃了。如果需要,EF 核心 3 仅在最终投影 (Select) 中应用客户端评估。所以GroupBy 将在 EF 核心 3 中失败,您迟早会使用它。

但是,我必须警告您,目前,这在 EF Core 3 中不起作用,因为它们还不完全支持 GroupBy。但我相信这只是时间问题。

【讨论】:

    【解决方案2】:

    GroupBy 接受custom equality comparer。你可以像这样实现你的特定匹配逻辑:

    class MessageComparer : IEqualityComparer<Message>
    {
        public bool Equals(Message x, Message y)
        {
            return (x.SenderId == y.SenderId || x.SenderId == y.RecipientId)
            && (x.RecipientId == y.RecipientId || x.RecipientId == y.SenderId); // i guess you can go as specific as you like here, depending on your requirements
        }
    
        public int GetHashCode(Message obj)
        {
            return (obj.SenderId ^ obj.RecipientId).GetHashCode();
        }
    }
    

    在您的调用方法中,使用自定义比较器执行类似的操作: Messages.GroupBy(m => m, new MessageComparer()) .Select(gm => gm.OrderByDescending(m => m.SentAt).FirstOrDefault());

    UPD:使用 LINQ to Entities 将大大改变解决方案。没错,EF 将不支持自定义函数和相等比较器,因为它需要以某种方式将其转换为 SQL(即使您设法做到了,由于缺少功能索引,您的查询性能可能会急剧下降)。我看到了几个探索途径:

    1. 为没有分组的两个用户选择所有消息并在 C# 中应用分组(您有自定义比较器并且可以执行几乎任何类似的操作:Messages.Select(gm =&gt; gm.OrderByDescending(m =&gt; m.SentAt).ToList(/*DB call happens here */).GroupBy(m =&gt; m, new MessageComparer()).FirstOrDefault() - 但是这可能会从数据库中获取大量数据所以你需要考虑过滤
    2. 更改您的数据库架构以引入 SequenceNumber 列,该列将在发件人和收件人之间共享,您可以在 SQL 中使用该列将所有您喜欢的索引
    3. 编写一个存储过程,对您的两列进行某种诡计并为您提供所需的输出(例如,您可以对两个整数进行排序并将它们连接起来以产生一个您可以依赖的值)。

    【讨论】:

    • 感谢您的建议。它编译但我得到 NotSupportedException 说:“无法解析表达式'值(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1 [Message])。[Includes].GroupBy(m => m,__p_0)':这个重载目前不支持“System.Linq.Queryable.GroupBy”方法。”
    • 对,我用一些想法更新了答案。看看其中任何一个会吸引你吗?
    • 我同意所有这些,除了前者。由于可能有很多消息,我不想在完成过滤后进行分页时将它们全部记入内存。我几乎开始写 sp 但其中一个答案做到了。无论如何,谢谢您的建议。
    【解决方案3】:

    由于两个 Id 都是整数,因此您可以通过以下方式在组中对它们进行排序:

    Messages
        .GroupBy(m => new { Math.Min(m.SenderId, m.RecipientId), Math.Max(m.SenderId, m.RecipientId)})
        .Select(gm => gm.OrderByDescending(m => m.SentAt).FirstOrDefault());
    

    【讨论】:

    • 你好。非常感谢您的建议。我稍微更改了表达式,因为它抱怨属性名称: .GroupBy(m => new { X = Math.Min(m.SenderId, m.RecipientId), Y = Math.Max(m.SenderId, m. RecipientId)}) 但它现在给了我异常消息“值不能为空。参数名称:源”。我现在正在努力解决。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-26
    • 1970-01-01
    • 2017-06-21
    • 1970-01-01
    相关资源
    最近更新 更多