Linq:GroupBy,求和和计数


132

我有一个产品集合

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。

Sampe数据:

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的计数应为2!

我试图在一个简单的控制台应用程序中对此进行仿真,但是得到了以下结果:

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

产品1:仅应列出一次...以上代码可在pastebin上找到:http : //pastebin.com/cNHTBSie

Answers:


284

我不知道第一个“带有样本数据的结果”是从哪里来的,但是控制台应用程序中的问题是您正在SelectMany查看每个组中的每个项目

我想您只想要:

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()此处获得产品名称的假设是,具有相同产品代码的每个产品都具有相同的产品名称。如注释中所述,您可以按产品名称和产品代码进行分组,如果名称对于任何给定代码始终相同,则将给出相同的结果,但是显然可以在EF中生成更好的SQL。

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


好的,我的控制台应用程序正在运行。感谢您指出我使用First()并省略SelectMany。ResultLine实际上是一个ViewModel。价格将以货币符号格式化。这就是为什么我需要它是一个字符串。但是我可以将数量更改为整数。现在,我看看这是否也对我的网站有所帮助。我会告诉你。
ThdK

6
@ThdK:不,您也应该保留Price小数,然后更改其格式。保持数据表示的整洁,并仅在可能的最后时刻更改为演示视图。
乔恩·斯基特

4
为什么不按ProductCode和名称分组?这样的东西:.GroupBy(l => new {l.ProductCode,l.Name})并使用ProductName = c.Key.Name,
Kirill Bestemyanov

@KirillBestemyanov:是的,当然,这是另一种选择。
乔恩·斯基特

好的,看来我的收藏实际上是真实收藏的包装。.射击我..好是的,我今天练习了一些Linq :)
ThdK 2013年

27

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

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(),
              };

2

有时您需要选择某些字段,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();

1
您能解释一下为什么有时候我需要使用FirstOrDefault() or singleOrDefault()`吗?
Shanteshwar Inde,

@ShanteshwarInde First()和FirstOrDefault()获取序列中的第一个对象,而Single()和SingleOrDefault()期望结果中只有1。如果Single()和SingleOrDefault()发现结果集中或作为提供的参数的结果,存在多个对象,则它将引发异常。在使用时,您仅在需要时使用前者,可能是一系列样品,而其他对象对您而言并不重要,而如果您仅期望一个对象,则使用后者,而如果结果不止一个,则执行某些操作,就像记录错误一样。
Kristianne Nerona
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.