web-dev-qa-db-fra.com

Linq: GroupBy, Sum et Count

J'ai une collection de produits

public class Product {

   public Product() { }

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

Maintenant, je souhaite regrouper la collection en fonction du code produit et renvoyer un objet contenant le nom, le nombre ou les produits de chaque code et le prix total de chaque produit.

public class ResultLine{

   public ResultLine() { }

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

J'utilise donc GroupBy pour grouper par code de produit, puis je calcule la somme et compte également le nombre d'enregistrements pour chaque code de produit.

C'est ce que j'ai jusqu'ici:

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

Pour une raison quelconque, la somme est effectuée correctement mais le nombre est toujours égal à 1.

Données 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" });

Résultat avec exemple de données:

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

Le produit 1 devrait compter = 2!

J'ai essayé de simuler cela dans une application console simple, mais j'ai obtenu le résultat suivant:

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

Product1: ne devrait figurer qu'une seule fois ... Le code de ce qui précède se trouve sur Pastebin: http://Pastebin.com/cNHTBSie

111
ThdK

Je ne comprends pas d'où vient le premier "résultat avec des exemples de données", mais le problème dans l'application console est que vous utilisez SelectMany pour regarder chaque élément de chaque groupe =.

Je pense que tu veux juste:

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

L'utilisation de First() ici pour obtenir le nom du produit suppose que tous les produits portant le même code de produit portent le même nom. Comme indiqué dans les commentaires, vous pouvez grouper par nom de produit ainsi que par code de produit, ce qui donnera les mêmes résultats si le nom est toujours le même pour un code donné, mais génère apparemment un meilleur code SQL dans EF.

Je vous suggérerais également de modifier les propriétés Quantity et Price en int et decimal, respectivement. Pourquoi utiliser une propriété string pour des données qui ne sont clairement pas textuelles ?

242
Jon Skeet

La requête suivante fonctionne. Il utilise chaque groupe pour faire la sélection au lieu de SelectMany. SelectMany fonctionne sur chaque élément de chaque collection. Par exemple, dans votre requête, vous obtenez un résultat de 2 collections. SelectMany obtient tous les résultats, un total de 3, au lieu de chaque collection. Le code suivant fonctionne sur chaque IGrouping dans la partie sélectionnée pour que vos opérations d'agrégat fonctionnent correctement.

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

parfois, vous devez sélectionner certains champs avec FirstOrDefault() ou singleOrDefault(), vous pouvez utiliser la requête ci-dessous:

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
Mahdi Jalali