我有一个像
这样的课程public class Empolyee
{
public string Designation {get ;set;}
public string Discipline {get ;set;}
public int Scale {get ;set;}
public DateTime DOB {get ;set;}
public int Sales {get ;set;}
}
并以可数的说法记录所有员工
List<Employee> Employees;
和
之类的字符串键列表var Keys = new List<string>()
{
"Designation",
"Scale",
"DOB"
};
假设列表的元素&#34;键&#34;是用户指定的,用户可以指定no或许多关键元素。
现在我想将所有&#34;员工&#34;使用列表中指定的键&#34;键&#34;并仅选择&#34; Keys&#34;中指定的属性。加上每组的销售总额。
我尝试使用的3个解决方案中,以下看似适用但无法使用它,因为我们不知道如何列出&#34; Keys&#34;将被转换为匿名类型
Employees.GroupBy(e => new { e.Key1, e.Key2, ... })
.Select(group => new {
Key1 = group.Key.Key1,
Key2 = group.Key.Key2,
...
TotalSales = group.Select(employee => employee.Sales).Sum()
});
答案 0 :(得分:1)
您可能需要Dynamic LINQ之类的内容,以便将键和投影值指定为字符串。
查看分组和投影的一些示例:
答案 1 :(得分:1)
如果您事先不知道关键属性的数量,那么静态编译的匿名类型不会让您走得太远。相反,由于关键属性的数量是动态的,因此每个组的密钥都需要一个数组。
首先,您需要将字符串映射到属性值:
public object[] MapProperty(string key, Employee e)
{
switch (k) {
case "Designation" : return e.Designation;
case "DOB" : return e.Dob;
// etc
}
}
然后,您必须对数组进行分组和比较,确保使用自定义IEqualityComparer
实现来比较每个数组的元素。您可以使用this answer中的ArrayEqualityComparer<T>
。
var comparer = new ArrayEqualityComparer<object>();
Employees.GroupBy(e => Keys.Select(k => MapProperty(k, e)).ToArray(), e => e, comparer)
.Select(group => new {
Keys = group.Key,
TotalSales = group.Select(employee => employee.Sales).Sum()
})
答案 2 :(得分:1)
https://dotnetfiddle.net/jAg22Z
它不是特别干净但可以整理 - 我刚刚使用了一个字符串作为键,因为它为您提供了GroupBy所需的所有哈希码/相等性,但您可以创建一个类以更友好的方式执行此操作方式。
如果你真的想用字符串来做。
void Main()
{
var vs = Enumerable.Range(0, 50).Select(i => Create(i));
var groups = vs.GroupByKeys(new [] { "Scale" });
Console.WriteLine("{0} groups", groups.Count());
Console.WriteLine(string.Join(", ", groups.Select(g => g.Key)));
}
Employee Create(int i) {
return new Employee { Scale = (((int)(i / 10)) * 10), DOB = new DateTime(2011, 11, 11), Sales = 50000 };
}
public class Employee
{
public string Designation {get ;set;}
public string Discipline {get ;set;}
public int Scale {get ;set;}
public DateTime DOB {get ;set;}
public int Sales {get ;set;}
}
public static class GroupByExtensions
{
public static IEnumerable<IGrouping<string, TValue>> GroupByKeys<TValue>(this IEnumerable<TValue> values, IEnumerable<string> keys)
{
var getters = typeof(TValue).GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty)
.Where(pi => keys.Contains(pi.Name))
.Select(pi => pi.GetMethod)
.Where(mi => mi != null)
.ToArray();
if (keys.Count() != getters.Length)
{
throw new InvalidOperationException("Couldn't find all keys for grouping");
}
return values.GroupBy(v => getters.Aggregate("", (acc, getter) => string.Format("{0}¬{1}", acc, getter.Invoke(v, null).ToString())));
}
}
我鼓励你使用函数进行更强大的打字......
void Main()
{
var vs = Enumerable.Range(0, 50).Select(i => Create(i));
var groups = vs.GroupByKeys(new Func<Employee, object>[] { x=> x.Scale });
Console.WriteLine("{0} groups", groups.Count());
Console.WriteLine(string.Join(", ", groups.Select(g => g.Key)));
}
Employee Create(int i) {
return new Employee { Scale = (((int)(i / 10)) * 10), DOB = new DateTime(2011, 11, 11), Sales = 50000 };
}
public class Employee
{
public string Designation {get ;set;}
public string Discipline {get ;set;}
public int Scale {get ;set;}
public DateTime DOB {get ;set;}
public int Sales {get ;set;}
}
public static class GroupByExtensions
{
public static IEnumerable<IGrouping<string, TValue>> GroupByKeys<TValue>(this IEnumerable<TValue> values, IEnumerable<Func<TValue, object>> getters)
{
return values.GroupBy(v => getters.Aggregate("", (acc, getter) => string.Format("{0}¬{1}", acc, getter(v).ToString())));
}
}
答案 3 :(得分:0)
不确定这是否是您想要的,但您可以选择所有可用的密钥作为新列表,然后加入它们。
void Main()
{
var employees = new List<Employee>()
{
new Employee{
Name = "Bob",
Sales = 1,
Keys = { "A", "B" }
},
new Employee{
Name = "Jane",
Sales = 2,
Keys = { "A", "C" }
}
};
var grouping = (from e in employees
from k in employees.SelectMany(s => s.Keys).Distinct()
where e.Keys.Contains(k)
select new
{
e.Name,
e.Sales,
Key = k
})
.GroupBy(a => a.Key)
.Select(g => new { Key = g.Key, TotalSales = g.Select(a => a.Sales).Sum() });
}
public class Employee
{
public int Sales { get; set; }
public string Name { get; set; }
public List<string> Keys { get; set;}
public Employee()
{
Keys = new List<string>();
}
}
答案 4 :(得分:0)
对于这个问题的最终解决方案,我使用了@jamespconnor的答案中的编码方法,但字符串作为分组键在我的实际场景中对我没什么帮助。所以我使用@ tim-rogers的数组基本思想作为分组键,并使用ArrayEqualityComparer比较数组。
要获取字符串集合指定的键属性,我构建一个静态类,如
yum update
将@ jamespconnor的GroupByKeys扩展名改为
public static class MembersProvider
{
public static IEnumerable<PropertyInfo> GetProperties(Type type, params string[] names)
{
var properties = type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.GetProperty)
.Where(pi => names.Contains(pi.Name))
.Where(pi => pi != null)
.AsEnumerable();
if (names.Count() != properties.Count())
{
throw new InvalidOperationException("Couldn't find all properties on type " + type.Name);
}
return properties;
}
}
由于我还需要以匿名类型选择结果,每个“Key”作为其属性和一个额外的“Total”属性,但是没有成功,我最终会像
public static class GroupByExtensions
{
public static IEnumerable<IGrouping<object[], TValue>> GroupByKeys<TValue>(this IEnumerable<TValue> values, IEnumerable<string> keys)
{
var properties = MembersProvider.GetProperties(typeof(TValue), keys.ToArray());
var comparer = new ArrayEqualityComparer<object>();
// jamespconnor's string as key approch - off course it will need to return IEnumerable<IGrouping<string, TValue>>
/*return values.GroupBy(v => getters.Aggregate(
"",
(acc, getter) => string.Format(
"{0}-{1}",
acc,
getter.Invoke(v, null).ToString()
)
)
);*/
//objects array as key approch
return values.GroupBy(v => properties.Select(property => property.GetValue(v, null)).ToArray(), comparer);
}
}