web-dev-qa-db-ja.com

Linq:GroupBy、Sum、Count

私は製品のコレクションを持っています

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はcount = 2でなければなりません。

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

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

Product1:一度だけリストされるべきです...上記のコードはPastebinにあります: http://Pastebin.com/cNHTBSie

104
ThdK

最初の「サンプルデータの結果」がどこから来たのかはわかりませんが、コンソールアプリケーションの問題は、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が生成されるようです。

また、QuantityおよびPriceプロパティをそれぞれintおよびdecimal型に変更することをお勧めします。なぜデータには明らかにテキストではない文字列プロパティを使用するのですか。

231
Jon Skeet

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

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

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();
0
Mahdi Jalali