Linq: GroupBy, Sum dan Count


133

Saya punya koleksi produk

public class Product {

   public Product() { }

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

Sekarang saya ingin mengelompokkan koleksi berdasarkan kode produk dan mengembalikan objek yang berisi nama, nomor atau produk untuk setiap kode dan total harga untuk setiap produk.

public class ResultLine{

   public ResultLine() { }

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

Jadi saya menggunakan GroupBy untuk mengelompokkan berdasarkan ProductCode, lalu saya menghitung jumlah dan juga menghitung jumlah catatan untuk setiap kode produk.

Inilah yang saya miliki sejauh ini:

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

Untuk beberapa alasan, jumlah tersebut dilakukan dengan benar tetapi hitungannya selalu 1.

Data sampel:

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

Hasil dengan data sampel:

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

Produk 1 harus dihitung = 2!

Saya mencoba mensimulasikan ini dalam aplikasi konsol sederhana tetapi di sana saya mendapat hasil sebagai berikut:

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

Product1: hanya boleh dicantumkan sekali ... Kode untuk di atas dapat ditemukan di pastebin: http://pastebin.com/cNHTBSie

Jawaban:


285

Saya tidak mengerti dari mana "hasil dengan data sampel" pertama berasal, tetapi masalahnya di aplikasi konsol adalah Anda menggunakannya SelectManyuntuk melihat setiap item dalam setiap grup .

Saya pikir Anda hanya ingin:

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

Penggunaan di First()sini untuk mendapatkan nama produk mengasumsikan bahwa setiap produk dengan kode produk yang sama memiliki nama produk yang sama. Seperti disebutkan dalam komentar, Anda dapat mengelompokkan berdasarkan nama produk dan juga kode produk, yang akan memberikan hasil yang sama jika nama selalu sama untuk setiap kode yang diberikan, tetapi tampaknya menghasilkan SQL yang lebih baik di EF.

Saya juga menyarankan bahwa Anda harus mengubah Quantitydan Priceproperti untuk menjadi intdan decimaljenis masing-masing - mengapa penggunaan properti string untuk data yang jelas tidak tekstual?


Ok aplikasi konsol saya berfungsi. Terima kasih telah menunjukkan saya untuk menggunakan First () dan tinggalkan SelectMany. ResultLine sebenarnya adalah ViewModel. Harga akan diformat dengan tanda mata uang. Itu sebabnya saya membutuhkannya untuk menjadi string. Tetapi saya dapat mengubah kuantitas menjadi int. Saya akan melihat sekarang apakah ini juga dapat membantu situs web saya. Aku akan memberitahu Anda.
ThdK

6
@ThdK: Tidak, Anda harus menyimpan Pricedesimal juga, dan kemudian mengubah cara Anda memformatnya. Jaga representasi data tetap bersih, dan hanya ubah ke tampilan presentasi pada saat terakhir yang memungkinkan.
Jon Skeet

4
Mengapa tidak mengelompokkan berdasarkan ProductCode dan Nama? Sesuatu seperti itu: .GroupBy (l => new {l.ProductCode, l.Name}) dan menggunakan ProductName = c.Key.Name,
Kirill Bestemyanov

@ KirillBestemyanov: Ya, tentu saja itu pilihan lain.
Jon Skeet

Ok, sepertinya koleksi saya sebenarnya adalah pembungkus di sekitar koleksi asli .. Tembak saya .. Untung, saya berlatih beberapa Linq hari ini :)
ThdK

27

Kueri berikut berfungsi. Ini menggunakan setiap grup untuk melakukan pilih, bukan SelectMany. SelectManybekerja pada setiap elemen dari setiap koleksi. Misalnya, dalam kueri Anda, Anda memiliki hasil 2 koleksi. SelectManymendapatkan semua hasil, total 3, bukan setiap koleksi. Kode berikut berfungsi pada masing-masing IGroupingbagian yang dipilih untuk membuat operasi agregat Anda berfungsi dengan benar.

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

terkadang Anda perlu memilih beberapa bidang dengan FirstOrDefault()atau singleOrDefault()Anda dapat menggunakan kueri di bawah ini:

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
bisa tolong jelaskan mengapa kadang-kadang saya perlu menggunakan FirstOrDefault() or singleOrDefault () `?
Shanteshwar Inde

@ShanteshwarInde First () dan FirstOrDefault () mendapatkan objek pertama dalam satu seri, sedangkan Single () dan SingleOrDefault () mengharapkan hanya 1 dari hasilnya. Jika Single () dan SingleOrDefault () melihat bahwa ada lebih dari 1 objek dalam set hasil atau sebagai hasil dari argumen yang diberikan, itu akan melempar pengecualian. Pada penggunaan, Anda menggunakan yang pertama ketika Anda hanya ingin, mungkin sampel dari seri dan objek lain tidak penting bagi Anda, sedangkan Anda menggunakan yang terakhir jika Anda hanya mengharapkan satu objek dan melakukan sesuatu jika ada lebih dari satu hasil , suka mencatat kesalahan.
Kristianne Nerona
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.