我有一个包含两个元素的列表
元素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
尝试过以下方法,但不确定如何对价格求和并返回整个字段,而无需使用新的
list.GroupBy(y => new { y.Description,y.vendor, y.no})
.Select(x => x.ToList().OrderBy(t => t.Price)).FirstOrDefault()
答案 0 :(得分:0)
尝试以下操作:
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;
}
}
}
答案 1 :(得分:0)
您需要创建一个自定义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
可以比较Property
类中存在的每个Element
,但Linq Where
子句中定义的除外,在这种情况下为"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;
});
答案 2 :(得分:0)
非常讨厌,您必须创建具有Key
个属性的3
;
如果您不喜欢使用匿名类
的当前解决方案list
.GroupBy(y => new {
y.Description,
y.vendor,
y.no}
)
...
您可以通过其他方式进行操作,例如借助未命名元组:
list
.GroupBy(y => Tuple.Create(
y.Description,
y.vendor,
y.no)
)
...
或命名为元组(有关详细信息,请参见https://docs.microsoft.com/en-us/dotnet/csharp/tuples)
list
.GroupBy(y => (
Description : y.Description,
vendor : y.vendor,
no : y.no)
)
...
甚至是定制类。但是,最重要的是,您不能只是从First
中获取 group
项目
但应创建一个新实例。另一个问题是过早实现:.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?
});
答案 3 :(得分:0)
如果您喜欢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
方法调用来计算的。
答案 4 :(得分:0)
我认为您要尝试编写的动态代码将要汇总的属性按所有属性(除 以外)分组。尽管我不愿意使用反射,但是该解决方案应该可以工作。一种性能更高的方法是使用表达式树来生成您重复使用的聚合委托,但这非常涉及。这应该可以解决问题:
编辑:还有另一个答案似乎也可行。我的假设您将要对任何集合执行此操作,而无论其类型如何。不需要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));