randoms
randoms

Reputation: 2783

Using a Func expression as a parameter in GroupBy

I am creating some statistics on how many employees from different departments are a member of a specific Active Directory group. To do so, i retrive all the group members from ad and group them by Department / orglevel.

I want the result to look something like this:

Org0 | Org1 | Org2 | Count
CFO  |      |      | 27
CFO  | AA   |      | 11
CFO  | AA   | JJ   | 11
CFO  | BB   |      | 16
CFO  | BB   | CC   |  4
CFO  | BB   | DD   | 12

I have a class OrgUnit that have the following members:

public int Employees { get; set; }
public int Consultants { get; set; }
[NotMapped]
public int Total => Employees + Consultants;
public string Org0 { get; set; }
public string Org1 { get; set; }
public string Org2 { get; set; }

I also have a class Person, which is the derrived AD User object, which contains members Org0, Org1 and Org2.

To get the count of how many employees there are in each org unit / level i use GroupBy, the problem is that i cant figure out how to use a generic group by expression, so i need to have one method for each org level. As shown below:

private static IEnumerable<T> GetLevel0<T>(IEnumerable<Person> persons) where T : OrgUnit, new()
{
    var level0 = persons.Where(x => !string.IsNullOrEmpty(x.Org0))
        .GroupBy(ac => new
        {
            ac.Org0
        })
        .Select(ac =>
        {
            return new T
            {
                Org0 = ac.Key.Org0,
                Consultants = ac.Count(x => x.EmpGroup.Equals("Consultant")),
                Employees = ac.Count(x => x.EmpGroup.Equals("Employee"))
            };
        });
    return level0;
}

private static IEnumerable<T> GetLevel1<T>(IEnumerable<Person> persons) where T : OrgUnit, new()
{
    var level1 = persons.Where(x => !string.IsNullOrEmpty(x.Org1))
        .GroupBy(ac => new
        {
            ac.Org0,
            ac.Org1
        })
        .Select(ac => new T
        {
            Org0 = ac.Key.Org0,
            Org1 = ac.Key.Org1,
            Consultants = ac.Count(x => x.EmpGroup.Equals("Consultant")),
            Employees = ac.Count(x => x.EmpGroup.Equals("Employee"))
        });
    return level1;
}

My question is, how can i use generics to only have one method, instead of one method GetLevel pr org level?

Upvotes: 1

Views: 697

Answers (1)

Reza ArabQaeni
Reza ArabQaeni

Reputation: 4908

You can do this but with scratch and some contract, First contract is you know how many arg column is exist and base on this assumption consider number of Tuple items, In this example number of args is 3:

One of properties of Tuple is equality is determined by the default object equality comparer for each component.[MSDN]

If you want Level1 then fill group with new Tuple.Create(p.Org0, null, null) and if want Level2 then group is Tuple.Create(p.Org0, p.Org1, null) and so on.

public static IEnumerable<T> GetLevel<T>(IEnumerable<Person> persons, 
    Func<Person, Tuple<string, string, string>> group)
    where T : OrgUnit, new()
{
    var level1 = persons.Where(x => 
        !string.IsNullOrEmpty(group(x).Item2 == null ? group(x).Item1 :
        (group(x).Item3 ?? group(x).Item2)))
        .GroupBy(group)
        .Select(ac => new T
        {
            Org0 = ac.Key.Item1,
            Org1 = ac.Key.Item2,
            Org2 = ac.Key.Item3,
            Consultants = ac.Count(x => x.EmpGroup.Equals("Consultant")),
            Employees = ac.Count(x => x.EmpGroup.Equals("Employee"))
        });
    return level1;
}

Upvotes: 2

Related Questions