domingo, 27 de noviembre de 2011

[Linq] Categoría Jerárquica múltiple niveles

 

Introducción

El objetivo del artículo es demostrar como linq puede ser verdaderamente potente a la hora de convertir estructuras simple y planas como ser entidades (o tablas) en objetos jerárquicos que faciliten la asignación de datos a controles, en este caso un treeview.

El resultado del artículo cargara un árbol como se muestra en la imagen, el cual podrá tener tantos niveles como uno necesite.

 imagen1

Recuperar los registro de forma básica

El primer paso para armar la estructura será contar con información plana y sin procesar de la base de datos.

public static List<CategoriaEntity> ObtenerCategorias()
{
    string sql = @"SELECT C.IdCategoria,
                            C.IdCategoriaPadre,
                            C.Descripcion,
                            C.Posicion
                    FROM Categorias C
                    ORDER BY C.IdCategoria, C.Posicion";

    List<CategoriaEntity> categorias = new List<CategoriaEntity>();

    using (SqlCeConnection conn = new SqlCeConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        conn.Open();

        SqlCeCommand cmd = new SqlCeCommand(sql, conn);
        IDataReader reader = cmd.ExecuteReader();

        while (reader.Read())
        {
            categorias.Add(ConvertirCategoria(reader));
        }
        
    }

    return categorias;
}

private static CategoriaEntity ConvertirCategoria(IDataReader reder)
{
    return new CategoriaEntity()
    {
        IdCategoria = Convert.ToInt32(reder["IdCategoria"]),
        IdCategoriaPadre = reder["IdCategoriaPadre"] == DBNull.Value ? null : (int?)Convert.ToInt32(reder["IdCategoriaPadre"]),
        Descripcion = Convert.ToString(reder["Descripcion"]),
        Posicion = Convert.ToInt16(reder["Posicion"])
    };
}
Aplicar jerarquía a los datos

La información plana hay que darle jerarquía, determinando que ítem son hijos de que otros.

public static List<CategoriaJerarquica> ObtenerCategoriarJerarquia()
{
    List<CategoriaEntity> categoriasList = ObtenerCategorias();

    List<CategoriaJerarquica> query = (from item in categoriasList
                                        where item.IdCategoriaPadre == null
                                        select new CategoriaJerarquica
                                        {
                                            IdCategoria = item.IdCategoria,
                                            Descripcion = item.Descripcion,
                                            CategoriaHija = ObtenerHijos(item.IdCategoria, categoriasList)
                                        }).ToList();

    return query;
}

private static List<CategoriaJerarquica> ObtenerHijos(int idCategoria, List<CategoriaEntity> categoriasList)
{

    List<CategoriaJerarquica> query = (from item in categoriasList
                                       let tieneHijos = categoriasList.Where(o => o.IdCategoriaPadre == item.IdCategoria).Any()
                                       where item.IdCategoriaPadre == idCategoria
                                       select new CategoriaJerarquica
                                        {
                                            IdCategoria = item.IdCategoria,
                                            Descripcion = item.Descripcion,
                                            CategoriaHija = tieneHijos ? ObtenerHijos(item.IdCategoria, categoriasList) : null
                                        }).ToList();

    return query;

}

 

Con el primer linq al usar

where item.IdCategoriaPadre == null

se obtienen los nodos padres, o sea aquellos que no dependen de ningún otro nodo

 

Asignación de la jerarquía al control TreeView

Como ultimo paso y no menos importante será crear los TreeNode que requiere el control TreeView

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        List<CategoriaJerarquica> categoriaList = CategoriasDAL.ObtenerCategoriarJerarquia();

        CrearNodoHijo(categoriaList, null);
                 
    }
}


private void CrearNodoHijo(List<CategoriaJerarquica> categorias, TreeNode parentNode)
{
    categorias.ForEach(x =>
    {
        TreeNode node = new TreeNode(x.Descripcion, Convert.ToString(x.IdCategoria));

        if (x.CategoriaHija != null)
        {
            CrearNodoHijo(x.CategoriaHija, node);
        }

        if (parentNode == null)
            TreeView1.Nodes.Add(node);
        else
            parentNode.ChildNodes.Add(node);
    });   

}

Como se observa hay una invocación recursiva para armar la estructura del árbol.

 

Código de Ejemplo

El articulo fue creado con visual Studio 2008

La db es Sql Compact Edition (.sdf), por lo tanto esta se encuentra en la carpeta App_Data

 

  [C# SkyDrive]

6 comentarios:

  1. Hola Leandro retomando este tema, cree un nuevo campo llamado men_path que guarda las url de las paginas del menu por ej Inicio.aspx ,quisiera hacerte una pregunta ¿como puedo redireccionar a una pagina cuando presiono una opción del menu? ,lo he intentado pero no me resulta mucho, agradeceré tu respuesta ...gracias

    ResponderEliminar
  2. hola leandro, este tipo de ejemplo se puede usar para desplegar un select box en vez de un arbol?. vi que cuando tenes mucha profundidad de categorias el arbol resulta confuso. mas que agradecido, slds.

    ResponderEliminar
  3. hola Sebastian

    que seria un select box ?

    ese codigo sirve para armar estructurar jerarquizas con estructura de arbol, si el control que quieres suar define este tipo de estructura puedes hacerlo sin problemas

    saludos

    ResponderEliminar
  4. hola leandro gracias por la respuesta. por selectbox me refiero a algo como esto: http://remysharp.com/wp-content/uploads/2007/01/ebay_categories.gif

    me esta costando encontrar en internet como mostrar estructuras jerarquizadas sin usar estructura de arbol. de hecho tengo una pregunta sobre ese tema en SO hace varios dias con un premio de 100 puntos y tampoco nada aparece. http://stackoverflow.com/questions/15717573/auto-populating-select-boxes-using-jquery-ajax-in-asp-net-mvc mas que agradecido por cualquier orientacion. saludos!.

    ResponderEliminar
  5. hola Sebastian

    pero es o es como armar controles anidados, en donde la seleccion de uno se sua como filtro del siguiente
    hacerlo con ese control o con combos seria exactamente igual

    en al seleccion del primer combo deberias invocar a un action del controller que devuelva el modelo con los items del segunda lista

    es mas hasta podrias usar partical view con al funcionalidad ajax de asp.net mvc

    Part 3 – Cascading using Microsoft AJAX (Ajax.BeginForm helper)

    hay varias formas de hacerlo pero creo que usar Ajax.BeginForm() y partial view es la mejor

    saludos

    ResponderEliminar
  6. Leandro me sirvió muchisimo esta función!! Te agradezco.

    ResponderEliminar