Linq: GroupBy, Sum ve Count


133

Bir ürün koleksiyonum var

public class Product {

   public Product() { }

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

Şimdi koleksiyonu ürün koduna göre gruplamak ve her kod için adı, numarası veya ürünleri ve her ürünün toplam fiyatını içeren bir nesne döndürmek istiyorum.

public class ResultLine{

   public ResultLine() { }

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

Bu nedenle, ProductCode'a göre gruplamak için bir GroupBy kullanıyorum, ardından toplamı hesaplıyorum ve ayrıca her ürün kodu için kayıt sayısını sayıyorum.

Şu ana kadar sahip olduğum şey bu:

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

Bazı nedenlerden dolayı, toplam doğru yapılır ancak sayı her zaman 1'dir.

Örnek verileri:

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

Örnek verilerle sonuç:

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

Ürün 1, sayı = 2 olmalıdır!

Bunu basit bir konsol uygulamasında simüle etmeye çalıştım ancak aşağıdaki sonucu aldım:

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

Ürün1: yalnızca bir kez listelenmelidir ... Yukarıdakinin kodu pastebin'de bulunabilir: http://pastebin.com/cNHTBSie

Yanıtlar:


285

İlk "örnek verilerle sonuç" nereden geliyor anlamıyorum, ancak konsol uygulamasındaki sorun , her bir gruptaki her bir öğeyeSelectMany bakmak için kullanıyor olmanızdır .

Sanırım sadece şunu istiyorsun:

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

Kullanımı First()Ürün adını almak için burayı , aynı ürün koduna sahip her ürünün aynı ürün adına sahip olduğunu varsayar. Yorumlarda belirtildiği gibi, ürün adına ve ürün koduna göre gruplama yapabilirsiniz; bu, adın herhangi bir kod için her zaman aynı olması, ancak görünüşe göre EF'de daha iyi SQL oluşturması durumunda aynı sonuçları verecektir.

Ayrıca, Quantityve Priceözelliklerini sırasıyla intve decimaltürlerini değiştirmenizi öneririm - neden açıkça metinsel olmayan veriler için bir dize özelliği kullanasınız?


Tamam konsol uygulamam çalışıyor. First () 'ü kullanmamı ve SelectMany'yi dışarıda bırakmamı işaret ettiğiniz için teşekkürler. ResultLine aslında bir ViewModel'dir. Fiyat, para birimi işaretiyle biçimlendirilecektir. Bu yüzden onun bir ip olmasına ihtiyacım var. Ama miktarı int olarak değiştirebilirim .. Şimdi bunun web siteme de yardımcı olup olmayacağını göreceğim. Seni bilgilendirecegim.
ThdK

6
@ThdK: Hayır, siz de Priceondalık olarak tutmalı ve sonra onu nasıl biçimlendireceğinizi değiştirmelisiniz. Veri temsilini temiz tutun ve yalnızca mümkün olan en son anda bir sunum görünümüne geçin.
Jon Skeet

4
Neden Ürün Kodu ve İsme Göre Gruplandırılmasın? Bunun gibi bir şey: .GroupBy (l => new {l.ProductCode, l.Name}) ve ProductName = c.Key.Name kullanın,
Kirill Bestemyanov

@KirillBestemyanov: Evet, bu kesinlikle başka bir seçenek.
Jon Skeet

Tamam, görünüşe göre koleksiyonum gerçek koleksiyonun etrafını
saran bir paket gibiydi

27

Aşağıdaki sorgu çalışır. Bunun yerine seçim yapmak için her grubu kullanır SelectMany. SelectManyher koleksiyondaki her öğe üzerinde çalışır. Örneğin, sorgunuzda 2 koleksiyon sonucunuz var. SelectManyher koleksiyon yerine toplam 3 sonuç olmak üzere tüm sonuçları alır. Aşağıdaki kod IGrouping, toplama işlemlerinizin doğru şekilde çalışmasını sağlamak için seçim bölümünün her birinde çalışır .

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

bazen bazı alanları seçmeniz gerekir FirstOrDefault()veya singleOrDefault()aşağıdaki sorguyu kullanabilirsiniz:

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
neden bazen FirstOrDefault() or singleOrDefault () '' kullanmam gerektiğini açıklar mısınız?
Shanteshwar Inde

@ShanteshwarInde First () ve FirstOrDefault () bir serideki ilk nesneyi alırken Single () ve SingleOrDefault () sonuçtan yalnızca 1 tane bekler. Single () ve SingleOrDefault (), bir sonuç kümesinde 1'den fazla nesne olduğunu görürse veya sağlanan bağımsız değişkenin sonucu olarak, bir istisna atar. Kullanımda, ilkini sadece istediğiniz zaman kullanırsınız, belki bir serinin bir örneği ve diğer nesneler sizin için önemli değildir, oysa ikincisini yalnızca bir nesne bekliyorsanız ve birden fazla sonuç varsa bir şeyler yaparsınız. , hatayı günlüğe kaydetme gibi.
Kristianne Nerona
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.