I have a class like
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;}
}
and have records of all employees in an enumerable say
List<Employee> Employees;
and a list of string keys like
var Keys = new List<string>()
{
"Designation",
"Scale",
"DOB"
};
assume that elements of list "Keys" are user specified and user may specify no or many key elements.
now i want to Group all "Employees" with the keys specified in list "Keys" and select only the properties specified in "Keys" plus Sum of Sales for each group.
out of 3 solutions i tried to use, following looked applicable but could not use it because don't know how list "Keys" will be converted to anonymous type
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()
});
You probably need something like Dynamic LINQ so you can specify your keys and projected values as strings.
See some examples with grouping and projection:
Where you don't know the number of key properties upfront, a statically-compiled anonymous type isn't going to get you very far. Instead you will need an array for each group's key since the number of key properties is dynamic.
First you will need to map your strings to property values:
public object[] MapProperty(string key, Employee e)
{
switch (k) {
case "Designation" : return e.Designation;
case "DOB" : return e.Dob;
// etc
}
}
Then you will have to group and compare the arrays, making sure to compare the elements of each array using a custom IEqualityComparer
implementation. You can use an ArrayEqualityComparer<T>
from this answer.
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()
})
https://dotnetfiddle.net/jAg22Z
It's not particularly clean but could be tidied up - I've just used a string as the key since it gives you all the hashcode/equality that GroupBy needs but you could create a class to do this in a more object-friendly way.
If you really want to do it with strings.
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())));
}
}
I'd encourage you to use functions for a little stronger typing...
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())));
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With