Linq: GroupBy, Sum và Count


133

Tôi có một bộ sưu tập các sản phẩm

public class Product {

   public Product() { }

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

Bây giờ tôi muốn nhóm bộ sưu tập dựa trên mã sản phẩm và trả về một đối tượng có chứa tên, số hoặc sản phẩm cho mỗi mã và tổng giá cho mỗi sản phẩm.

public class ResultLine{

   public ResultLine() { }

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

Vì vậy, tôi sử dụng GroupBy để nhóm theo ProductCode, sau đó tôi tính tổng và cũng đếm số lượng bản ghi cho mỗi mã sản phẩm.

Đây là những gì tôi có cho đến nay:

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

Vì một số lý do, tổng được thực hiện chính xác nhưng số luôn luôn là 1.

Dữ liệu 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" });

Kết quả với dữ liệu mẫu:

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

Sản phẩm 1 nên có số lượng = 2!

Tôi đã cố gắng mô phỏng điều này trong một ứng dụng giao diện điều khiển đơn giản nhưng ở đó tôi nhận được kết quả như sau:

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

Sản phẩm1: chỉ nên được liệt kê một lần ... Mã cho những điều trên có thể được tìm thấy trên pastebin: http://pastebin.com/cNHTBSie

Câu trả lời:


285

Tôi không hiểu "kết quả với dữ liệu mẫu" đầu tiên đến từ đâu, nhưng vấn đề trong ứng dụng bảng điều khiển là bạn đang sử dụng SelectManyđể xem xét từng mục trong mỗi nhóm .

Tôi nghĩ bạn chỉ muốn:

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

Việc sử dụng First()ở đây để có được tên sản phẩm giả định rằng mọi sản phẩm có cùng mã sản phẩm đều có cùng tên sản phẩm. Như đã lưu ý trong các nhận xét, bạn có thể nhóm theo tên sản phẩm cũng như mã sản phẩm, sẽ cho kết quả tương tự nếu tên luôn giống nhau cho bất kỳ mã đã cho nào, nhưng rõ ràng tạo ra SQL tốt hơn trong EF.

Tôi cũng đề nghị rằng bạn nên thay đổi QuantityPricetính được intdecimalcác loại tương ứng - tại sao sử dụng một tài sản chuỗi dữ liệu mà rõ ràng là không văn bản?


Ok ứng dụng giao diện điều khiển của tôi đang hoạt động. Cảm ơn đã chỉ cho tôi sử dụng First () và bỏ chọn SelectMany. ResultLine thực sự là một ViewModel. Giá sẽ được định dạng với ký hiệu tiền tệ. Đó là lý do tại sao tôi cần nó là một chuỗi. Nhưng tôi có thể thay đổi số lượng thành int .. Tôi sẽ thấy ngay bây giờ nếu điều này cũng có thể giúp cho trang web của tôi. Tôi sẽ cho bạn biết.
ThdK

6
@ThdK: Không, bạn cũng nên giữ Pricedưới dạng thập phân, và sau đó thay đổi cách bạn định dạng nó. Giữ biểu diễn dữ liệu sạch sẽ và chỉ thay đổi thành chế độ xem bản trình bày vào thời điểm cuối cùng có thể.
Jon Skeet

4
Tại sao không nhóm theo ProductCode và Name? Một cái gì đó tương tự: .groupBy (l => new {l. ProducttCode, l.Name}) và sử dụng ProductName = c.Key.Name,
Kirill Bestemyanov

@KirillBestemyanov: Vâng, đó là một lựa chọn khác, chắc chắn.
Jon Skeet

Ok, có vẻ như bộ sưu tập của tôi thực sự là một gói bao quanh bộ sưu tập thực sự .. Hãy bắn tôi .. Điều tốt là tôi đã thực hành một số Linq ngày hôm nay :)
ThdK

27

Các truy vấn sau đây hoạt động. Nó sử dụng mỗi nhóm để chọn thay vì SelectMany. SelectManyhoạt động trên từng yếu tố từ mỗi bộ sưu tập. Ví dụ: trong truy vấn của bạn, bạn có kết quả của 2 bộ sưu tập. SelectManynhận được tất cả các kết quả, tổng cộng 3, thay vì mỗi bộ sưu tập. Đoạn mã sau hoạt động trên từng IGroupingphần trong phần chọn để hoạt động tổng hợp của bạn hoạt động chính xác.

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

đôi khi bạn cần chọn một số trường theo FirstOrDefault()hoặc singleOrDefault()bạn có thể sử dụng truy vấn bên dưới:

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
bạn có thể giải thích lý do tại sao đôi khi tôi cần sử dụng FirstOrDefault() or singleOrDefault () `không?
Chaieshwar Inde

@ShanteshwarInde First () và FirstOrDefault () nhận được đối tượng đầu tiên trong chuỗi, trong khi Single () và SingleOrDefault () chỉ mong đợi 1 từ kết quả. Nếu Single () và SingleOrDefault () thấy rằng có nhiều hơn 1 đối tượng trong tập kết quả hoặc là kết quả của đối số được cung cấp, nó sẽ đưa ra một ngoại lệ. Về cách sử dụng, bạn sử dụng cái trước khi bạn chỉ muốn, có thể là một mẫu của một chuỗi và các đối tượng khác không quan trọng đối với bạn, trong khi bạn sử dụng cái sau nếu bạn chỉ mong đợi một đối tượng và làm gì đó nếu có nhiều hơn một kết quả , như đăng nhập lỗi.
Kristianne Nerona
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.