2015-04-28 7 views
11

Ho una classe comeIEnumerable Raggruppa per utente specificato elenco dinamico di chiavi

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

e hanno i record di tutti i dipendenti in un enumerabile dico

List<Employee> Employees; 

e un elenco di chiavi stringa come

presuppongono che gli elementi della lista "Chiavi" siano specificati dall'utente e l'utente possa specificare no o molti elementi chiave.

ora voglio raggruppare tutti i "Dipendenti" con le chiavi specificate nell'elenco "Chiavi" e selezionare solo le proprietà specificate in "Chiavi" più Somma delle vendite per ciascun gruppo.

su 3 soluzioni ho cercato di utilizzare, a seguito guardato applicabile, ma non poteva usare perché non sanno come lista "chiavi" saranno convertiti tipo anonimo

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

Perché il tuo ultimo pezzo di codice non è un vero pezzo di codice, ma qualcosa che si avvicina allo pseudo-codice? Perché vuoi definire questo elenco di chiavi separatamente? – BCdotWEB

+0

@BCdotWEB il mio ultimo pezzo di codice non è reale perché per far funzionare questo codice ho bisogno di creare un tipo anonimo in fase di runtime dalle chiavi specificate dall'utente. Nel mio scenario attuale ho bisogno di presentare ai miei utenti un riepilogo delle vendite con diversi parametri per rispondere a domande come "Quante vendite hanno fatto i dipendenti di una specifica designazione" e "Quante vendite hanno fatto i dipendenti di specifica designazione con specifica disciplina" ecc. Quindi gli utenti selezionano in base a quali parametri vogliono ottenere il riepilogo. – Shahab

risposta

0

Per la mia soluzione definitiva a questo problema, ho usato l'approccio di codifica dalla risposta 's @jamespconnor ma stringa come chiave di raggruppamento non mi poteva aiutare molto nel mio scenario reale. Quindi ho usato l'idea base di array di @ tim-rogers come chiave di raggruppamento e confrontando gli array usando ArrayEqualityComparer.

Per ottenere le proprietà chiave specificata per collezione stringa costruisco una classe statica come

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

e cambiò @ di jamespconnor GroupByKeys estensione un po 'come

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

} 

Dal momento che ho anche bisogno di selezionare i risultati in un tipo anonimo tra "chiave", come la sua proprietà e un ulteriore proprietà "Total", ma non essendo riuscito a farlo ho finito come

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

Dove non si conosce il numero di proprietà chiave in anticipo, un staticamente compilato anonima tipo isn' Ti porterò molto lontano. Invece sarà necessario un array per la chiave di ciascun gruppo poiché il numero di proprietà chiave è dinamico.

primo luogo è necessario mappare le corde a valori di proprietà:

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

Poi si dovrà gruppo e confrontare le matrici, facendo attenzione a confrontare gli elementi di ogni array usando una IEqualityComparer un'implementazione personalizzata. È possibile utilizzare uno ArrayEqualityComparer<T> da this answer.

0

Non sono sicuro se si desidera, ma è possibile selezionare tutte le chiavi disponibili come nuovo elenco e quindi aggiungerle.

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

E 'non particolarmente pulito, ma potrebbe essere riordinato - ho appena usato una stringa come la chiave dal momento che ti dà tutto il codice hash/uguaglianza che GroupBy ha bisogno, ma si potrebbe creare una classe per farlo in un modo più favorevole agli oggetti.

Se si vuole davvero farlo con le stringhe.

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

    } 

} 

Ti incoraggio a utilizzare le funzioni per digitare un po 'più forte ...

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

    } 

}