Linq: GroupBy, Sum and Count

Tengo una colección de productos

public class Product { public Product() { } public string ProductCode {get; set;} public decimal Price {get; set; } public string Name {get; set;} } 

Ahora quiero agrupar la colección según el código del producto y devolver un objeto que contenga el nombre, el número o los productos para cada código y el precio total de cada producto.

 public class ResultLine{ public ResultLine() { } public string ProductName {get; set;} public string Price {get; set; } public string Quantity {get; set;} } 

Entonces utilizo GroupBy para agrupar por ProductCode, luego calculo la sum y también cuento el número de registros para cada código de producto.

Esto es lo que tengo hasta ahora:

 List Lines = LoadProducts(); List 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(); 

Por alguna razón, la sum se realiza correctamente, pero el conteo siempre es 1.

Datos de Sampe:

 List Lines = new List(); 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" }); 

Resultado con datos de muestra:

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

¡El producto 1 debería tener una cuenta = 2!

Intenté simular esto en una aplicación de consola simple pero obtuve el siguiente resultado:

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

Producto1: solo debe aparecer una vez … El código para lo anterior se puede encontrar en pastebin: http://pastebin.com/cNHTBSie

No entiendo de dónde viene el primer “resultado con datos de muestra”, pero el problema en la aplicación de la consola es que está utilizando SelectMany para ver cada elemento en cada grupo .

Creo que solo quieres:

 List 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(); 

El uso de First() aquí para obtener el nombre del producto supone que cada producto con el mismo código de producto tiene el mismo nombre de producto. Como se señala en los comentarios, puede agrupar por nombre de producto así como por código de producto, lo que dará los mismos resultados si el nombre es siempre el mismo para cualquier código dado, pero aparentemente genera mejor SQL en EF.

También sugiero que cambie las propiedades de Quantity y Price para que sean tipos int y decimal respectivamente. ¿Por qué usar una propiedad de cadena para datos que claramente no son textuales?

La siguiente consulta funciona. Utiliza cada grupo para hacer la selección en lugar de SelectMany . SelectMany funciona en cada elemento de cada colección. Por ejemplo, en su consulta tiene un resultado de 2 colecciones. SelectMany obtiene todos los resultados, un total de 3, en lugar de cada colección. El siguiente código funciona en cada IGrouping en la porción de selección para que las operaciones agregadas funcionen correctamente.

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