2015-04-28 7 views
11

Ich habe eine Klasse wieIEnumerable Gruppe von Benutzer angegebene dynamische Liste der Schlüssel

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

und haben Aufzeichnungen aller Mitarbeiter in einem enumerable sagen

List<Employee> Employees; 

und eine Liste von String-Schlüssel wie

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

angenommen, dass Elemente der Liste "Keys" vom Benutzer angegeben sind und der Benutzer keine oder viele Schlüsselelemente angeben kann.

Jetzt möchte ich alle "Mitarbeiter" mit den Schlüsseln gruppieren, die in der Liste "Keys" angegeben sind, und nur die unter "Schlüssel" angegebenen Eigenschaften plus Summe der Verkäufe für jede Gruppe auswählen.

von 3 Lösungen habe ich versucht, zu verwenden, um nach anwendbar sah, konnte es aber nicht benutzen, weil nicht wissen, wie die Liste „Keys“ wird anonymen Typ umgewandelt werden

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

Warum ist Ihr letztes Stück Code kein echtes Stück Code, sondern etwas, das dem Pseudocode nahe kommt? Warum möchten Sie diese Liste der Schlüssel separat definieren? – BCdotWEB

+0

@BCdotWEB mein letztes Stück Code ist kein tatsächliches, weil für diesen Code ich anonymen Typ zur Laufzeit von Benutzer angegebenen Schlüsseln erstellen muss. In meinem tatsächlichen Szenario muss ich meinen Benutzern eine Zusammenfassung der Verkäufe bezüglich der verschiedenen Parameter präsentieren, um Fragen wie "Wie viel Verkäufe die Angestellten der spezifischen Bezeichnung getan haben" und "Wie viel Verkäufe die Angestellten der spezifischen Bezeichnung mit spezifischer Disziplin getan haben" usw So wählen die Benutzer aus, über welche Parameter sie eine Zusammenfassung erhalten möchten. – Shahab

Antwort

0

Für meine endgültige Lösung für dieses Problem, benutzen ich die Codierung Ansatz von @jamespconnor ‚s Antwort, aber String als Gruppierungsschlüssel mir nicht viel in meinem realen Szenario helfen könnte. Also habe ich @ Tim-Rogers Grundidee von Array als Gruppierungsschlüssel verwendet und die Arrays mit ArrayEqualityComparer verglichen.

die wichtigsten Eigenschaften von String-Sammlung angegeben Um baue ich eine statische Klasse wie

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

Und geändert @ GroupByKeys Erweiterung des jamespconnor ein wenig wie

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

} 

Da ich auch die Ergebnisse benötigt auszuwählen in einem anonymen Typ mit jedem "Key" als Eigenschaft und einer zusätzlichen "Total" -Eigenschaft, die aber nicht erfolgreich war, endete ich wie

// 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

Wo Sie die Anzahl der Schlüsseleigenschaften im Voraus nicht wissen, ein statisch kompilierten anonymer Typ isn‘ Ich werde dich sehr weit bringen. Stattdessen benötigen Sie ein Array für jeden Schlüssel der Gruppe, da die Anzahl der Schlüsseleigenschaften dynamisch ist.

Zuerst müssen Sie Ihre Strings Eigenschaftswerte zuordnen:

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

Dann sind Sie in der Gruppe haben und die Arrays zu vergleichen, um sicherzustellen, die Elemente jedes Feldes zu vergleichen, um eine benutzerdefinierte IEqualityComparer Implementierung verwenden. Sie können eine ArrayEqualityComparer<T> von this answer verwenden.

0

Nicht sicher, ob dies gewünscht wurde, aber Sie könnten alle verfügbaren Schlüssel als neue Liste auswählen und dann beitreten.

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

Es ist nicht besonders sauber, aber bis in Ordnung gebracht werden kann - ich habe nur einen String als Schlüssel verwendet, da es Ihnen alle hashcode/Gleichheit gibt, dass GroupBy braucht, aber man könnte eine Klasse erstellen dies auf eine objektfreundlichere Weise zu tun.

Wenn Sie wirklich mit Strings tun wollen.

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

    } 

} 

Ich würde Sie ermutigen, Funktionen für ein wenig stärkeres Tippen zu verwenden ...

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

    } 

} 
Verwandte Themen