【问题标题】:Linq Group by Complex Type of ListLinq 按复杂类型的列表分组
【发布时间】:2020-03-30 13:06:18
【问题描述】:

我正在尝试对列表的某些列表对象执行 Linq GroupBy。

public class ItemDto
{

    public int ItemId { get; set; }
    public string ProductName { get; set; }
    public int Count { get; set; }
    public List<CondimentDto> Condiments { get; set; }
}

public class CondimentDto
{
    public CondimentDto(int ItemId, string productName, decimal originalPrice)
    {
        ItemId = ItemId;
        ProductName = productName;
        OriginalPrice = originalPrice;

    }
    public int PosItemId { get; set; }
    public string ProductName { get; set; }
    public decimal OriginalPrice { get; set; }

}

这里是列表示例。

var items = new List<ItemDto>();

items.Add(new ItemDto(){ItemId = 1, ProductName = "Steak", Condiments = new List<CondimentDto>{new CondimentDto(11, "Mayonez", 1)}});
items.Add(new ItemDto() { ItemId = 1, ProductName = "Steak", Condiments = new List<CondimentDto> { new CondimentDto(11, "Mayonez", 1) } });
items.Add(new ItemDto() { ItemId = 1, ProductName = "Steak", Condiments = new List<CondimentDto> { new CondimentDto(11, "Hardal", 1) } });

这是我的 LinQ 查询;

var result = items.GroupBy(item => new {item.ItemId, item.Condiments})
                  .Select(x=>new ItemDto
                  {
                     ItemId = x.Key,
                     ProductName = x.First().ProductName,
                     Condiments = x.First().Condiments,
                     Count = x.Count()

                  })
                  .ToList();

我想要做的是我想按 ItemId 和 List of Condiments 对我的列表进行分组。我期望的结果是

{
  items[
    {
      "ItemId": 1,
      "ProductName": "Steak",
      "Condiments": [
        {
          "ItemId": 11,
          "Name": "Mayonez",
          "Price": 1
        }
      ],
      "Count": 2
    },
    {
      "ItemId": 1,
      "ProductName": "Steak",
      "Condiments": [
        {
          "ItemId": 11,
          "Name": "Hardal",
          "Price": 1
        }
      ],
      "Count": 1
    }
  ]
}

因此,有两个相同的列表项,这就是为什么它必须被分组并且计数为 2。而另一个不同的项目是调味品是 Hardal。它是独立的,计数为 1。

一个列表对象可以分成两组吗?顺便说一句,调味品可以是空的。然后它应该只按 ID 分组。

谢谢

【问题讨论】:

  • 如果一件商品有不止一种调味品会怎样?它是否应该只与具有相同调味品的物品组合在一起?如果只有一个,您可以使用item.Condiments?.FirstOrDefault() 进行分组(注意将空列表和空列表组合在一起)。
  • 是的,调味品不止一种。在这种情况下,其他项目调味品也必须不止一种调味品,否则它们将彼此不同。它应该包括相同的调味品。它应该和我数不完的一样。
  • 我想先按 ItemId 分组,然后比较 Condimets List 但我做不到

标签: c# linq


【解决方案1】:

您可以在这里创建两个IEqualityComparer&lt;T&gt; 类。一个用于比较两个CondimentDto 对象是否相等,一个用于比较两个List&lt;CondimentDto&gt; 是否相等。我们将CondimentComparer 传递给SequenceEqual() 以确定两个列表是否相等,我们将ItemDtoComparer 传递给GroupBy 以将ItemDto 对象从主items 列表中分组。我们还必须创建自己的Equals()GetHashCode() 来确定相等性。

public class CondimentComparer : IEqualityComparer<CondimentDto>
{
    public bool Equals(CondimentDto x, CondimentDto y)
    {
        // If all the properties match, then its equal
        return x.PosItemId == y.PosItemId && 
               x.ProductName == y.ProductName && 
               x.OriginalPrice == y.OriginalPrice;
    }

    // Get all hashcodes from object properties using XOR
    // Might be better ways to do this
    public int GetHashCode(CondimentDto obj)
    {
        return obj.PosItemId.GetHashCode() ^ 
               obj.ProductName.GetHashCode() ^
               obj.OriginalPrice.GetHashCode();
    }
}

public class ItemDtoComparer : IEqualityComparer<ItemDto>
{
    public bool Equals(ItemDto x, ItemDto y)
    {

        // If two condiments are null, then just compare ItemId
        if (x.Condiments == null && y.Condiments == null)
        {
            return x.ItemId == y.ItemId;
        }

        // If one is null and the other is not, can't possiblly be equal
        else if (x.Condiments == null && y.Condiments != null ||
                 x.Condiments != null && y.Condiments == null)
        {
            return false;
        }

        // If we get here we have two normal objects
        // Compare two objects ItemId and lists of condiments
        return x.ItemId == y.ItemId &&
               x.Condiments.SequenceEqual(y.Condiments, new CondimentComparer());
    }

    public int GetHashCode(ItemDto obj)
    {

        // If condiments list is null, just get the ItemId hashcode
        if (obj.Condiments == null)
        {
            return obj.ItemId.GetHashCode();
        }

        // Otherwise do the XOR of ItemId and the aggregated XOR of the condiment list properties
        return obj.ItemId.GetHashCode() ^ obj.Condiments.Aggregate(0, (a, c) => a ^ c.PosItemId.GetHashCode() ^ c.ProductName.GetHashCode() ^ c.OriginalPrice.GetHashCode());
    }
}

那么你可以将ItemDtoComparer传递给Groupby

var result = items
    .GroupBy(item => item, new ItemDtoComparer()) // Pass ItemDtoComparer here
    .Select(grp => new ItemDto
    {
        ItemId = grp.Key.ItemId,
        ProductName = grp.Key.ProductName,
        Condiments = grp.Key.Condiments,
        Count = grp.Count()
    })
    .ToList();

dotnetfiddle.net上的演示

注意:上述GetHashCode() 实现是异或哈希码。这被认为不是最佳实践,遵循answer 中的建议可以提供更好的替代方案来避免哈希冲突。我只是选择了这种方法,因为它易于记忆和演示。

【讨论】:

  • 如果调味品不止一种怎么办?这只是集体冷杉调味品。
  • 不,即使可以有空的调味品清单,也可能有所不同。 Itemdto 可以包含或不包含调味品。但它应该由 itemdto 的相同元素分组
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多