【问题标题】:Can I put a list of a reference object in the KeySelector in a GroupBy?我可以在 GroupBy 的 KeySelector 中放置一个引用对象的列表吗?
【发布时间】:2019-12-27 22:18:28
【问题描述】:

假设我有这样的课程:

public class Transaction
{
    public string PointOfSale { get; set; }

    public List<PurchaseItem> PurchaseItems { get; set; }

    public decimal Cost { get; set; }
}

public class PurchaseItem
{
    public ItemType Type { get; set; } //ItemType is an enum
    public string Subtype { get; set; }
}

我想做的是根据交易的地点和购买的物品对交易进行分组,并总结成本。

我觉得GroupBy() 将是这里的解决方案,但我不知道如何让它比较 PurchaseItems 作为键的一部分,因为 PurchaseItem 是一个引用类型,并指定一个 IEqualityComparerGroupBy() 必须能够比较整个密钥。

例如:

Transaction[] transactions =
{
    new Transaction
    {
        PointOfSale = "Bytes-R-Us",
        PurchaseItems = new List<PurchaseItem>
        {
            new PurchaseItem { ItemType = ItemType.Electronics, Subtype = "peripherals" },
            new PurchaseItem { ItemType = ItemType.Food, Subtype = "candy" }
        },
        Cost = 50.00
    },
    new Transaction
    {
        PointOfSale = "Bytes-R-Us",
        PurchaseItems = new List<PurchaseItem>
        {
            new PurchaseItem { ItemType = ItemType.Electronics, Subtype = "peripherals" },
            new PurchaseItem { ItemType = ItemType.Food, Subtype = "candy" }
        },
        Cost = 25.00
    },
    new Transaction
    {
        PointOfSale = "Bytes-R-Us",
        PurchaseItems = new List<PurchaseItem>
        {
            new PurchaseItem { ItemType = ItemType.Software, Subtype = "games" }
        },
        Cost = 100.00
    },
    new Transaction
    {
        PointOfSale = "The Foo Bar & Grill",
        PurchaseItems = new List<PurchaseItem>
        {
            new PurchaseItem { ItemType = ItemType.Food, Subtype = "fine dining" },
            new PurchaseItem { ItemType = ItemType.Food, Subtype = "liquor" }
        },
        Cost = 75.49
    }
}

在这种情况下,我希望看到结果表明我在 Bytes-R-Us 的外围设备和糖果上花费了 75 美元,在 Bytes-R-Us 的游戏上花费了 100 美元,在 Foo Bar & 的食物和饮料上花费了 75.49 美元炙烤。像这样的:

var groupedTransactions = transactions.GroupBy(
    x => new {PointOfSale = x.PointOfSale, Items = x.PurchaseItems},
    y => y.Cost,
    (x, y) => new Transaction
    {
        PointOfSale = x.PointOfSale,
        PurchaseItems = x.Items,
        Cost = y.Sum()
    });

但是我怎样才能让x =&gt; new {PointOfSale = x.PointOfSale, Items = x.PurchaseItems} 像我描述的那样工作呢?

【问题讨论】:

  • 使用 SelectMany。我不知道如何获得成本。尝试这样的事情: var groupedTransactions = transactions .SelectMany(x => x.PurchaseItems.Select(y => new { PointOfSale = x.PointOfSale, Type = y.Type, Subtype = y.Subtype })) .GroupBy(x => new { PointOfSale = x.PointOfSale, Items = x.Type, Subtype = x.Subtype }) .ToList();

标签: c# linq group-by equality


【解决方案1】:

您基本上可以分 3 步完成此操作。首先用SelectMany 扁平化你的内部PurchaseItem 集合:

var result1 = transactions.SelectMany(
        x => x.PurchaseItems,
        (y, z) => new
                      {
                          y.PointOfSale,
                          y.Cost,
                          z.ItemType,
                          z.Subtype
                      })
    .ToList();

然后使用该结果和您需要的GroupBy 复合键:

var result2 = result1.GroupBy(x => new { x.PointOfSale, x.ItemType, x.Subtype}).ToList();

最后,使用Select 合并您的结果集:

var result3 = result2.Select(
        x => new
                 {
                     PointOfSale = x.Key.PointOfSale,
                     PurchaseItemSubtype = x.FirstOrDefault()?.Subtype,
                     TotalCost = x.Sum(y => y.Cost)
                 })
    .ToList();

最终结果的序列化版本如下所示:

[  
   {  
      "PointOfSale":"Bytes-R-Us",
      "PurchaseItemSubtype":"peripherals",
      "TotalCost":75.0
   },
   {  
      "PointOfSale":"Bytes-R-Us",
      "PurchaseItemSubtype":"candy",
      "TotalCost":75.0
   },
   {  
      "PointOfSale":"Bytes-R-Us",
      "PurchaseItemSubtype":"games",
      "TotalCost":100.0
   },
   {  
      "PointOfSale":"The Foo Bar & Grill",
      "PurchaseItemSubtype":"fine dining",
      "TotalCost":75.49
   },
   {  
      "PointOfSale":"The Foo Bar & Grill",
      "PurchaseItemSubtype":"liquor",
      "TotalCost":75.49
   }
]

我在每个步骤的末尾使用了ToList(),以便您可以在调试器中逐步执行并可视化中间步骤结果,但这当然不是必需的。单个 linq 链将是:

var result = transactions.SelectMany(
        x => x.PurchaseItems,
        (y, z) => new
                      {
                          y.PointOfSale,
                          y.Cost,
                          z.ItemType,
                          z.Subtype
                      })
    .GroupBy(
        x => new
                 {
                     x.PointOfSale,
                     x.ItemType,
                     x.Subtype
                 })
    .Select(
        x => new
                 {
                     PointOfSale = x.Key.PointOfSale,
                     PurchaseItemSubtype = x.FirstOrDefault()?.Subtype,
                     TotalCost = x.Sum(y => y.Cost)
                 });

【讨论】:

    【解决方案2】:

    您可以从 PurchaseItems 构建一个字符串并将其用作键的一部分。 例如,如果您只关心不同的子类型组合:

    x => new
    {
       PointOfSale = x.PointOfSale,
       Items = string.Join(", ", x.PurchaseItems.OrderBy(p => p.Subtype).Select(p => p.Subtype))
    }
    

    【讨论】:

      猜你喜欢
      • 2013-03-12
      • 2022-01-15
      • 2012-11-02
      • 2015-12-28
      • 2020-10-20
      • 2021-03-15
      • 1970-01-01
      • 2019-03-07
      • 1970-01-01
      相关资源
      最近更新 更多