2012-05-25 21 views
5

estoy tratando de volver a la estructura dinámicamente algunos datos que se muestran en una vista de árbol el cual se permite al usuario seleccionar hasta tres de las siguientes dimensiones para agrupar los datos por:Declaraciones Crear GroupBy dinámicamente

Organisation 
Company 
Site 
Division 
Department 

Entonces, por ejemplo, si el usuario seleccionara que desea agrupar por Compañía, Sitio, luego División ... el siguiente código realizaría las agrupaciones requeridas.

var entities = orgEntities 
// Grouping Level 1 
.GroupBy(o => new { o.CompanyID, o.CompanyName }) 
.Select(grp1 => new TreeViewItem 
     { 
     CompanyID = grp1.Key.CompanyID, 
     DisplayName = grp1.Key.CompanyName, 
     ItemTypeEnum = TreeViewItemType.Company, 
     SubItems = grp1 
       // Grouping Level 2 
       .GroupBy(o => new { o.SiteID, o.SiteName }) 
       .Select(grp2 => new TreeViewItem 
       { 
       SiteID = grp2.Key.SiteID, 
       DisplayName = grp2.Key.SiteName, 
       ItemTypeEnum = TreeViewItemType.Site, 
       SubItems = grp2 
        // Grouping Level 3 
        .GroupBy(o => new { o.Division }) 
        .Select(grp3 => new TreeViewItem 
        { 
         DisplayName = grp3.Key.Division, 
         ItemTypeEnum = TreeViewItemType.Division, 
        }).ToList() 
       }).ToList() 
     }) 
.ToList(); 

Esto daría un Structre así:

+ Company A 
    + Site A 
    + Division 1 
    + Division 2 
    + Site B 
    + Division 1 
+ Company B 
    + Site C 
    + Division 2 
+ Company C 
    + Site D 

Sin embargo, esto sólo me proporciona en un gran número de combinaciones.

¿Cómo podría convertir esto en algo que podría crear la expresión equivalente dinámicamente en función de las tres dimensiones que el usuario ha elegido y así no tengo que crear una de cada una de estas expresiones para cada combinación !! ?

Gracias chicos.

Respuesta

4

Un problema intrigante. Elegir un solo tipo para agrupar claves y otro tipo para obtener resultados ... hace que sea muy posible obtener lo que está pidiendo.

public struct EntityGroupKey 
{ 
    public int ID {get;set;} 
    public string Name {get;set;} 
} 

public class EntityGrouper 
{ 
    public Func<Entity, EntityGroupKey> KeySelector {get;set;} 
    public Func<EntityGroupKey, TreeViewItem> ResultSelector {get;set;} 
    public EntityGrouper NextGrouping {get;set;} //null indicates leaf level 

    public List<TreeViewItem> GetItems(IEnumerable<Entity> source) 
    { 
    var query = 
     from x in source 
     group x by KeySelector(x) into g 
     let subItems = NextGrouping == null ? 
     new List<TreeViewItem>() : 
     NextGrouping.GetItems(g) 
     select new { Item = ResultSelector(g.Key), SubItems = subItems }; 

    List<TreeViewItem> result = new List<TreeViewItem>(); 
    foreach(var queryResult in query) 
    { 
      // wire up the subitems 
     queryResult.Item.SubItems = queryResult.SubItems 
     result.Add(queryResult.Item); 
    } 
    return result; 
    } 

} 

utilizado de esta manera:

EntityGrouper companyGrouper = new EntityGrouper() 
{ 
    KeySelector = o => new EntityGroupKey() {ID = o.CompanyID, Name = o.CompanyName}, 
    ResultSelector = key => new TreeViewItem 
    { 
    CompanyID = key.ID, 
    DisplayName = key.Name, 
    ItemTypeEnum = TreeViewItemType.Company 
    } 
} 

EntityGrouper divisionGrouper = new EntityGrouper() 
{ 
    KeySelector = o => new EntityGroupKey() {ID = 0, Name = o.Division}, 
    ResultSelector = key => new TreeViewItem 
    { 
    DisplayName = key.Name, 
    ItemTypeEnum = TreeViewItemType.Division 
    } 
} 

companyGrouper.NextGrouping = divisionGrouper; 

List<TreeViewItem> oneWay = companyGrouper.GetItems(source); 

companyGrouper.NextGrouping = null; 
divisionGrouper.NextGrouping = companyGrouper; 

List<TreeViewItem> otherWay = divisionGrouper.GetItems(source); 
+0

Otro enfoque consiste en crear 5 clases de Grouper (una para cada tipo de agrupación) con una interfaz común IGroupEntities {List GetItems (IEnumerable )} para que puedan llamarse entre sí. –

+0

¡Gracias por esta respuesta! Había intentado algo similar, pero debe haber habido un eslabón perdido ya que nunca llegué a completar esta iteración. He utilizado su muestra como iniciador y he codificado una solución que funciona perfectamente para lo que estaba tratando de hacer. ¡¡¡Muchas gracias!!! – Penfold

+0

+100 si pudiera! ¡Gran respuesta! –

0

Otra opción es utilizar DynamicLinq. Si esto es LINQ recta (no a través de algún contexto DB como LINQ2SQL), entonces esto se puede hacer mediante la composición de sus cadenas de agrupación/selectores:

var entities = orgEntities 
    .GroupBy("new(CompanyID, CompanyName)", "it", null) // DynamicLinq uses 'it' to reference the instance variable in lambdas. 
    .Select(grp1 => new TreeViewItem 
    { 
     ... 
     .GroupBy("new(SiteID, o.SiteName)", "it", null) 
     // And so on... 

es probable que pueda abstracta esto en cada uno de los tipo de criterio. El único problema que veo es que las agrupaciones internas pueden no ser las más fáciles de compilar juntas, pero al menos esto puede ayudarlo a comenzar en alguna dirección. DynamicLinq le permite crear tipos dinámicos, por lo que es posible abstraerlo aún más. En última instancia, el mayor desafío es que, según lo que está agrupando, TreeViewItem generado contiene información diferente. Buen caso de uso para LINQ dinámico, pero el único problema que veo es abstraer aún más abajo en la línea (a las agrupaciones internas).

Háganos saber qué se le ocurre, definitivamente una idea interesante que no había considerado antes.