IEnumerable Group按用户指定的动态键列表

时间:2015-04-28 09:11:55

标签: c# .net asp.net-mvc lambda ienumerable

我有一个像

这样的课程
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()
    });

5 个答案:

答案 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);
    }

}