Linq:GroupBy、Sum、Count


133

製品のコレクションがあります

public class Product {

   public Product() { }

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

次に、製品コードに基づいてコレクションをグループ化し、名前、各コードの製品数、および各製品の合計価格を含むオブジェクトを返します。

public class ResultLine{

   public ResultLine() { }

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

そこで、GroupByを使用してProductCodeでグループ化し、合計を計算して、各製品コードのレコード数もカウントします。

これは私がこれまでに持っているものです:

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

何らかの理由で、合計は正しく行われますが、カウントは常に1です。

サンペデータ:

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

サンプルデータの結果:

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

製品1にはカウント= 2が必要です!

簡単なコンソールアプリケーションでこれをシミュレートしようとしましたが、次の結果が得られました。

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

:製品1:上記のコードはペーストビン上で見つけることができたら...のみ表示される必要がありhttp://pastebin.com/cNHTBSie

回答:


285

最初の「サンプルデータの結果」がどこから来ているのかわかりませんが、コンソールアプリの問題は、各グループの各項目SelectManyを確認するために使用していることです。

私はあなたが望むだけだと思います:

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

使用First()製品名を取得するには、ここでは同じ製品コードを持つすべての製品が同じ製品名を持っていることを前提としています。コメントで述べたように、製品名と製品コードでグループ化できます。これにより、特定のコードで名前が常に同じであれば同じ結果が得られますが、EFでより優れたSQLが生成されるようです。

プロパティQuantityPriceプロパティをそれぞれintdecimalタイプに変更することをお勧めします-明らかにテキストではないデータに文字列プロパティを使用するのはなぜですか?


コンソールアプリは動作しています。First()を使用してSelectManyを除外するように指摘してくれてありがとう。ResultLineは実際にはViewModelです。価格は通貨記号でフォーマットされます。それが文字列である必要がある理由です。しかし、私は数量をintに変更することができます。これが私のWebサイトにも役立つかどうかを確認します。知らせます。
ThdK 2013年

6
@ThdK:いいえ、Price小数として保持し、フォーマット方法を変更する必要があります。データ表現をきれいに保ち、最後の可能な瞬間にのみプレゼンテーションビューに変更します。
ジョンスキート2013年

4
ProductCodeと名前でグループ化しないのはなぜですか?そのような何か:.GroupBy(L =>新しい{l.ProductCode、l.Name})と使用商品名= c.Key.Name、
キリルBestemyanov

@KirillBestemyanov:はい、それは確かに別のオプションです。
ジョンスキート2013年

わかりました、私のコレクションは実際には実際のコレクションのラッパーだったようです。私を撃ってください。良いことは、今日Linqを練習したことです:)
ThdK

27

次のクエリは機能します。代わりに、各グループを使用して選択を行いますSelectManySelectMany各コレクションの各要素で機能します。たとえば、クエリに2つのコレクションの結果があるとします。SelectMany各コレクションではなく、すべての結果、合計3を取得します。次のコードIGroupingは、select部分のそれぞれに対して機能し、集計操作を正しく機能させます。

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

いくつかのフィールドを選択する必要がある場合FirstOrDefault()singleOrDefault()、以下のクエリを使用できる場合があります。

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
時々私はFirstOrDefault() or singleOrDefault() ` を使用する必要がある理由を説明していただけますか?
Shanteshwar Inde、

@ShanteshwarInde First()とFirstOrDefault()はシリーズの最初のオブジェクトを取得しますが、Single()とSingleOrDefault()は結果から1つだけを期待します。Single()およびSingleOrDefault()は、結果セットに、または指定された引数の結果として複数のオブジェクトがあることを確認すると、例外をスローします。使用時、前者は必要なときにのみ使用します。シリーズのサンプルや他のオブジェクトは重要ではありませんが、後者を使用するのは、1つのオブジェクトしか期待しておらず、複数の結果がある場合は何かを行う場合です。 、エラーを記録するように。
Kristianne Nerona
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.