【问题标题】:Linq: GroupBy, Sum and CountLinq:GroupBy、Sum 和 Count
【发布时间】:2013-05-07 12:31:37
【问题描述】:

我有一系列产品

public class Product {

   public Product() { }

   public string ProductCode {get; set;}
   public decimal Price {get; set; }
   public string Name {get; set;}
}

现在我想根据产品代码对集合进行分组,并返回一个对象,其中包含每个代码的名称、数量或产品以及每个产品的总价。

public class ResultLine{

   public ResultLine() { }

   public string ProductName {get; set;}
   public string Price {get; set; }
   public string Quantity {get; set;}
}

所以我使用 GroupBy 按 ProductCode 分组,然后计算总和并计算每个产品代码的记录数。

这是我目前所拥有的:

List<Product> Lines = LoadProducts();    
List<ResultLine> result = Lines
                .GroupBy(l => l.ProductCode)
                .SelectMany(cl => cl.Select(
                    csLine => new ResultLine
                    {
                        ProductName =csLine.Name,
                        Quantity = cl.Count().ToString(),
                        Price = cl.Sum(c => c.Price).ToString(),
                    })).ToList<ResultLine>();

由于某种原因,求和正确,但计数始终为 1。

样本数据:

List<CartLine> Lines = new List<CartLine>();
            Lines.Add(new CartLine() { ProductCode = "p1", Price = 6.5M, Name = "Product1" });
            Lines.Add(new CartLine() { ProductCode = "p1", Price = 6.5M, Name = "Product1" });
            Lines.Add(new CartLine() { ProductCode = "p2", Price = 12M, Name = "Product2" });

带有样本数据的结果:

Product1: count 1   - Price:13 (2x6.5)
Product2: count 1   - Price:12 (1x12)

产品 1 的 count = 2!

我尝试在一个简单的控制台应用程序中对此进行模拟,但结果如下:

Product1: count 2   - Price:13 (2x6.5)
Product1: count 2   - Price:13 (2x6.5)
Product2: count 1   - Price:12 (1x12)

Product1:应该只列出一次... 以上代码可以在pastebin上找到:http://pastebin.com/cNHTBSie

【问题讨论】:

    标签: c# .net linq


    【解决方案1】:

    我不明白第一个“带有样本数据的结果”来自哪里,但控制台应用程序中的问题是您使用 SelectMany 来查看每个组中的每个项目 em>。

    我想你只是想要:

    List<ResultLine> result = Lines
        .GroupBy(l => l.ProductCode)
        .Select(cl => new ResultLine
                {
                    ProductName = cl.First().Name,
                    Quantity = cl.Count().ToString(),
                    Price = cl.Sum(c => c.Price).ToString(),
                }).ToList();
    

    这里使用First()获取产品名称是假设每个具有相同产品代码的产品具有相同的产品名称。如 cmets 中所述,您可以按产品名称和产品代码进行分组,如果任何给定代码的名称始终相同,这将给出相同的结果,但显然会在 EF 中生成更好的 SQL。

    我还建议您将 QuantityPrice 属性分别更改为 intdecimal 类型 - 为什么要对显然不是文本的数据使用字符串属性?

    【讨论】:

    • 好的,我的控制台应用程序正在运行。感谢您指出我使用 First() 并忽略 SelectMany。 ResultLine 实际上是一个 ViewModel。价格将使用货币符号格式化。这就是为什么我需要它是一个字符串。但我可以将数量更改为 int。我现在看看这是否也对我的网站有帮助。我会告诉你的。
    • @ThdK:不,您也应该将Price 保留为小数,然后更改其格式。保持数据表示简洁,仅在可能的最后时刻更改为表示视图。
    • 为什么不按产品代码和名称分组?类似的东西: .GroupBy(l => new{l.ProductCode, l.Name}) 并使用 ProductName = c.Key.Name,
    • @KirillBestemyanov:是的,当然,这是另一种选择。
    • 这篇文章在使用 group by 搜索聚合结果的信息时获得了很高的结果,但我想在针对 EntityFramework 使用它时提出警告。 First/FirstOrDefault 将导致 EF 生成嵌套选择,这可能会对性能产生严重影响。 Kirill 关于使用 GroupBy 的建议会生成人们所期望的 SQL。
    【解决方案2】:

    以下查询有效。它使用每个组而不是SelectMany 进行选择。 SelectMany 作用于每个集合中的每个元素。例如,在您的查询中,您有 2 个集合的结果。 SelectMany 获取所有结果,一共3个,而不是每个集合。以下代码适用于选择部分中的每个IGrouping,以使您的聚合操作正常工作。

    var results = from line in Lines
                  group line by line.ProductCode into g
                  select new ResultLine {
                    ProductName = g.First().Name,
                    Price = g.Sum(pc => pc.Price).ToString(),
                    Quantity = g.Count().ToString(),
                  };
    

    【讨论】:

      【解决方案3】:

      有时您需要通过FirstOrDefault()singleOrDefault() 选择某些字段,您可以使用以下查询:

      List<ResultLine> result = Lines
          .GroupBy(l => l.ProductCode)
          .Select(cl => new Models.ResultLine
                  {
                      ProductName = cl.select(x=>x.Name).FirstOrDefault(),
                      Quantity = cl.Count().ToString(),
                      Price = cl.Sum(c => c.Price).ToString(),
                  }).ToList();
      

      【讨论】:

      • 你能解释一下为什么有时我需要使用FirstOrDefault() or singleOrDefault()` 吗?
      • @ShanteshwarInde First() 和 FirstOrDefault() 获取系列中的第一个对象,而 Single() 和 SingleOrDefault() 只期望结果中的 1。如果 Single() 和 SingleOrDefault() 发现结果集中有超过 1 个对象或作为提供的参数的结果,它将抛出异常。在使用时,您只需要使用前者,可能是一系列样本,而其他对象对您来说并不重要,而如果您只期望一个对象并在有多个结果时做某事,则使用后者,比如记录错误。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-02-06
      • 2018-06-05
      • 1970-01-01
      • 2012-11-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多