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.
Данные 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" });
Результат с данными примера:
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)
Product1: следует указывать только один раз...
Код для вышеуказанного можно найти на pastebin: http://pastebin.com/cNHTBSie
Ответы
Ответ 1
Я не понимаю, откуда приходит первый "результат с образцами данных", но проблема в консольном приложении заключается в том, что вы используете 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()
здесь для получения названия продукта предполагает, что каждый продукт с одинаковым кодом продукта имеет одно и то же имя продукта. Как отмечено в комментариях, вы можете группировать имя продукта, а также код продукта, который даст те же результаты, если имя всегда одинаково для любого заданного кода, но, по-видимому, создает лучший SQL в EF.
Я также предлагаю вам изменить свойства Quantity
и Price
соответственно int
и decimal
типов - зачем использовать свойство string для данных, которые явно не являются текстовыми?
Ответ 2
Выполняется следующий запрос. Он использует каждую группу для выбора вместо 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(),
};
Ответ 3
иногда вам нужно выбрать некоторые поля с помощью 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();