2015-04-28 7 views
11

Mam klasy jakIEnumerable Group By użytkownik określony dynamiczną listę kluczy

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;} 
} 

i mieć ewidencji wszystkich pracowników w sposób przeliczalna powiedzieć

List<Employee> Employees; 

oraz listę kluczy strunowych jak

var Keys = new List<string>() 
{ 
    "Designation", 
    "Scale", 
    "DOB" 
}; 

zakłada, że ​​elementy listy "Klucze" są określone przez użytkownika i użytkownik może określić brak lub wiele kluczowych elementów.

Teraz chcę zgrupować wszystkich "Pracowników" za pomocą kluczy określonych na liście "Klucze" i wybrać tylko właściwości określone w "Kluczach" plus Sumę sprzedaży dla każdej grupy.

z 3 rozwiązań Próbuję użyć po wyglądał zastosowanie, ale nie mógł go używać, ponieważ nie wiem jak lista „Keys” zostaną zamienione na anonimowego typu

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() 
    }); 
+1

Dlaczego twój ostatni fragment kodu nie jest faktycznym fragmentem kodu, ale zamiast tego jest coś zbliżonego do pseudokodu? Dlaczego chcesz zdefiniować tę listę kluczy osobno? – BCdotWEB

+0

@BCdotWEB mój ostatni fragment kodu nie jest faktyczny, ponieważ aby ten kod działał, muszę utworzyć anonimowy typ w czasie wykonywania z określonych przez użytkownika kluczy. W moim prawdziwym scenariuszu muszę przedstawić moim użytkownikom zestawienie różnych parametrów sprzedaży, aby odpowiedzieć na pytania typu: "Ile sprzedaży wykonali pracownicy o określonym oznaczeniu" i "Ile sprzedaży wykonali pracownicy o konkretnym oznaczeniu z określoną dyscypliną" itp. Tak więc użytkownicy wybierają, według których parametrów chcą otrzymać podsumowanie. – Shahab

Odpowiedz

0

Dla mojego ostatecznego rozwiązania tego problemu, użyłem metody kodowania z odpowiedzią @jamespconnor „s, ale ciąg jako klucz grupowania nie może mi pomóc dużo w moim realnym scenariuszem. Użyłem więc podstawowej koncepcji tablicy @ tim-rogers jako klucza grupującego i porównując tablice za pomocą ArrayEqualityComparer.

aby dostać klucz właściwościach określonych przez kolekcji strun zbudować statyczną klasę jak

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; 
    } 
} 

i zmienił @ jamespconnor za GroupByKeys przedłużenie trochę jak

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

} 

Od Musiałem także wybrać wyników w anonimowym typie z każdym "Kluczem" jako jego właściwością i dodatkową właściwością "Razem", ale nie odnosząc sukcesów, zakończyłem w następujący sposób:

// get properties specified by "Keys" collection 
    var properties = MembersProvider.GetProperties(typeof(Employee), Keys.ToArray()); 

    // Group and Select 
    var SalesSummary = Employees 
     .GroupByKeys(Keys.ToArray()) 
     .Select(g => 
      properties.Aggregate(
       new Dictionary<string, object>() { { "TotalSales", g.Select(employee => employee.Sales).Sum() } }, 
       (dictionary, property) => { 
        dictionary.Add(property.Name, property.GetValue(g.FirstOrDefault(), null)); 
        return dictionary; 
       } 
      ) 
     ); 
1

Jeżeli nie znasz numeru kluczowych właściwości góry, statycznie skompilowane anonimowy typ ISN” będzie cię daleko posuwać. Zamiast tego będziesz potrzebował tablicy dla klucza każdej grupy, ponieważ liczba kluczowych właściwości jest dynamiczna.

Najpierw trzeba będzie mapować ciągi do wartości nieruchomości:

public object[] MapProperty(string key, Employee e) 
{ 
    switch (k) { 
     case "Designation" : return e.Designation; 
     case "DOB" : return e.Dob; 
     // etc 
    } 
} 

Potem trzeba będzie grupą i porównać tablice, upewniając się, aby porównać elementy każdej tablicy przy użyciu niestandardowego IEqualityComparer realizację. Możesz użyć ArrayEqualityComparer<T> z 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() 
    }) 
0

Nie jesteś pewien, czy tego chciałeś, ale możesz wybrać wszystkie dostępne klucze jako nową listę, a następnie dołączyć do nich.

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>(); 
    } 
} 
1

https://dotnetfiddle.net/jAg22Z

To nie jest szczególnie czysty, ale może być uporządkowany - Właśnie używany ciąg jako klucz, ponieważ daje cały hashcode/równości że GroupBy potrzeb, ale można utworzyć klasę zrobić to w sposób bardziej przyjazny dla użytkownika.

Jeśli naprawdę chcesz to zrobić za pomocą ciągów.

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()))); 

    } 

} 

Zachęcam do korzystania z funkcji do nieco silniejszego pisania ...

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()))); 

    } 

}