我有一个包含两个元素的列表
要素1:
no:1,
vendor: a,
Description: Nice,
price :10
要素2:
no:1
vendor:a,
Description: Nice,
price:20
我在列表元素中有更多的字段,所以我不能使用 new 来求和价格
如果除价格外一切都相同,我需要通过对价格求和将两个元素组合成一个元素。
O/P 元素 1:
no:1,
vendor:a,
Description:Nice,
price:30
尝试过下面的一个,但不确定如何对价格求和并返回整个字段,而无需使用 new
list.GroupBy(y => new { y.Description,y.vendor, y.no})
.Select(x => x.ToList().OrderBy(t => t.Price)).FirstOrDefault()
如果您更喜欢 LINQ 查询表达式:
var groupedElements = from element in elements
group element by new
{
element.no,
element.Description,
element.vendor
}
into grouped
select new {grouped, TotalPrice = grouped.Sum(x => x.price)};
总价是通过对分组元素的最终.Sum
方法调用来计算的。
尝试以下操作:
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
List<Element> totals = elements.GroupBy(x => x.no).Select(x => new Element()
{
no = x.Key,
vendor = x.FirstOrDefault().vendor,
Description = x.FirstOrDefault().Description,
price = x.Sum(y => y.price)
}).ToList();
}
}
public class Element
{
public int no { get;set; }
public string vendor { get;set; }
public string Description { get;set; }
public decimal price { get;set; }
}
尝试使用克隆关注
using System;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
var groups = elements.GroupBy(x => x.no).ToList();
List<Element> totals = new List<Element>();
foreach (var group in groups)
{
Element newElement = (Element)group.FirstOrDefault().Clone();
newElement.price = group.Sum(x => x.price);
totals.Add(newElement);
}
}
}
public class Element : ICloneable
{
public int no { get;set; }
public string vendor { get;set; }
public string Description { get;set; }
public decimal price { get;set; }
public object Clone()
{
return this;
}
}
}
无论如何,您必须创建具有3
属性的Key
;
如果您不喜欢当前带有匿名类的解决方案
list
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
...
你可以用不同的方式做到这一点,例如,在未命名元组的帮助下:
list
.GroupBy(y => Tuple.Create(
y.Description,
y.vendor,
y.no)
)
...
或命名元组(有关详细信息,请参阅 https://learn.microsoft.com/en-us/dotnet/csharp/tuples(:
list
.GroupBy(y => (
Description : y.Description,
vendor : y.vendor,
no : y.no)
)
...
甚至是量身定制的课程。然而,最重要的是,你不能只从group
First
物品 但应该创建一个新实例。另一个问题是过早实现:.ToList()
当你摆脱这个新生列表并继续查询.OrderBy(...)
var result = result
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
.Select(group => MyObject() { //TODO: put the right syntax here
Description = group.Key.Description,
vendor = group.Key.vendor,
no = group.Key.no,
price = group.Sum(item => item.price) // you want to sum prices, right?
});
您需要创建一个自定义IEqualityComparer
,当传递到GroupBy
子句中时,它将根据您的需要对项目进行分组。
假设以下示例类:
public class Element
{
public int no { get; set; }
public string vendor { get; set; }
public string Description { get; set; }
public decimal price { get; set; }
}
您可以实现以下IEqualityComparer
,使用Reflection
将比较Element
类中存在的每个Property
,但 LinqWhere
子句中定义的除外,在本例中为"price"
。请记住,可能需要进一步的自定义。
public class ElementComparer : IEqualityComparer<Element>
{
public bool Equals(Element a, Element b) => typeof(Element).GetProperties()
.Where(p => p.Name != "price")
.All(p => p.GetValue(a).Equals(p.GetValue(b)));
public int GetHashCode(Element obj) => obj.no.GetHashCode();
}
然后简单地以这种方式对它们进行分组
list.GroupBy(x => x, new ElementComparer()).Select(g =>
{
// Here you need to either clone the first element of the group like
// @jdweng did, or create a new instance of Element like I'm doing below
Element element = new Element();
foreach (var prop in element.GetType().GetProperties())
{
if (prop.Name == "price")
{
prop.SetValue(element, g.Sum(y => y.price));
}
else
{
prop.SetValue(element, prop.GetValue(g.First()));
}
}
return element;
});
我认为您要做的是编写动态代码,按除要求和的属性之外的所有属性进行分组。这个解决方案应该有效,尽管我不愿意使用反射。性能更高的方法是使用表达式树生成可重用的聚合委托,但这非常复杂。这应该可以解决问题:
编辑:还有另一个答案似乎也有效。我的假设您希望对任何集合执行此操作,无论类型如何。不需要ICloneable
或特定于类型的IEqualityComparer<T>
,但是,作为轻微的权衡,另一个可能会在非常大的数据集中表现得更好。
static T[] GetGroupSums<T>(IEnumerable<T> collection, string sumPropertyName) where T : new()
{
//get the PropertyInfo you want to sum
//var sumProp = (PropertyInfo)((MemberExpression)((UnaryExpression)memberExpression.Body).Operand).Member;
var sumProp = typeof(T).GetProperty(sumPropertyName);
//get all PropertyInfos that are not the property to sum
var groupProps = typeof(T).GetProperties().Where(x => x != sumProp).ToArray();
//group them by a hash of non-summed properties (I got this hash method off StackExchange many years back)
var groups = collection
.GroupBy(x => GetHash(groupProps.Select(pi => pi.GetValue(x)).ToArray()))
.Select(items =>
{
var item = new T();
var firstItem = items.First();
//clone the first item
foreach (var gp in groupProps)
{
gp.SetValue(item, gp.GetValue(firstItem));
}
//Get a decimal sum and then convert back to the sum property type
var sum = items.Sum(_item => (decimal)Convert.ChangeType(sumProp.GetValue(_item), typeof(decimal)));
sumProp.SetValue(item, Convert.ChangeType(sum, sumProp.PropertyType));
//If it will always be int, just do this
//var sum = items.Sum(_item => (int)sumProp.GetValue(_item));
//sumProp.SetValue(item, sum);
return item;
});
return groups.ToArray();
}
//I got this hash method off StackExchange many years back
public static int GetHash(params object[] args)
{
unchecked
{
int hash = 17;
foreach (object arg in args)
{
hash = hash * 23 + arg.GetHashCode();
}
return hash;
}
}
像这样使用它:
List<Element> elements = new List<Element>() {
new Element() { no = 1, vendor = "a", Description = "Nice", price = 10},
new Element() { no = 2, vendor = "a", Description = "Nice", price = 15},
new Element() { no = 2, vendor = "b", Description = "Nice", price = 10},
new Element() { no = 1, vendor = "a", Description = "Nice", price = 20}
};
var groups = GetGroupSums(elements, nameof(Element.price));