I think the secret here is more in the DB query than in C # itself. In SQL SERVER (which seems to be the case) I go with a CTE. Here is an example only by typing the output hierarchically, but I would make a separate component for links, routes, and other specifics that are required.
The Category class
public class Categoria
{
public Categoria()
{
}
public int NumLevel { get; set; }
public int CPCID { get; set; }
public int ParentID { get; set; }
public String Code { get; set; }
public String Nodes { get; set; }
public String EN { get; set; }
public String PT { get; set; }
public String ES { get; set; }
public String IT { get; set; }
public List<Categoria> CategoriasFilho { get; set; }
}
The Console's Main method
static void Main(string[] args)
{
//Executamos a CTE
SqlConnection Conn = DAL.Conexao.AbreConexaoApoio();
StringBuilder cmdText = new StringBuilder();
cmdText.Append(";WITH ArvoreCategorias AS ( ");
cmdText.Append(" SELECT NumLevel, CPCID, ParentID, Code, PT, EN, ES, IT, 1 as Level, CAST(Code as varchar(max)) as Nodes FROM Categorias WHERE ParentID = 0");
cmdText.Append(" UNION ALL");
cmdText.Append(" SELECT c.NumLevel, c.CPCID, c.ParentID, c.Code, c.PT, c.EN, c.ES, c.IT, Level + 1, Cast(ac.Nodes + '->' + c.Code as varchar(max))");
cmdText.Append(" FROM Categorias c ");
cmdText.Append(" INNER JOIN ArvoreCategorias ac ON c.ParentID = ac.CPCID");
cmdText.Append(")");
cmdText.Append("SELECT * FROM ArvoreCategorias ORDER BY Nodes");
SqlCommand cmd = new SqlCommand();
cmd.Connection = Conn;
cmd.CommandText = cmdText.ToString();
SqlDataReader dr = cmd.ExecuteReader();
List<Categoria> ArvoreCategorias = new List<Categoria>();
//Loope perlo retorno da CTE
while (dr.Read())
{
Categoria cat = new Categoria();
cat.NumLevel = Int32.Parse(dr["NumLevel"].ToString());
cat.CPCID = Int32.Parse(dr["CPCID"].ToString());
cat.ParentID = Int32.Parse(dr["ParentID"].ToString());
cat.Code = dr["Code"].ToString();
cat.Nodes = dr["Nodes"].ToString();
cat.EN = dr["EN"].ToString();
cat.ES = dr["ES"].ToString();
cat.PT = dr["PT"].ToString();
cat.IT = dr["IT"].ToString();
ArvoreCategorias.Add(cat);
}
//Separo as Categorias Pai para que sejam preenchidos os filhos
List<Categoria> ArvoreCategoriasPai = ArvoreCategorias.Where(c => c.ParentID == 0).ToList();
//Carregos os Filhos das Categorias Raízes
foreach (Categoria cat in ArvoreCategoriasPai)
{
CarregarCategoriasFilho(cat, ArvoreCategorias);
}
dr.Close();
dr.Dispose();
DAL.Conexao.FechaConexao(Conn);
foreach (Categoria Cat in ArvoreCategoriasPai)
{
Console.WriteLine(Cat.Nodes);
if (Cat.CategoriasFilho.Count > 0)
{
EscreverCategoriasFilho(Cat);
}
}
Console.ReadKey();
}
The Recursive Method for Filling Child Objects
private static void CarregarCategoriasFilho(Categoria CategoriaPai, List<Categoria> ArvoreCategorias)
{
List<Categoria> CategoriasFilho = ArvoreCategorias.Where(cf => cf.ParentID == CategoriaPai.CPCID).ToList();
CategoriaPai.CategoriasFilho = CategoriasFilho;
if (CategoriasFilho.Count > 0)
{
foreach (Categoria cf in CategoriasFilho)
{
CarregarCategoriasFilho(cf, ArvoreCategorias);
}
}
}
The Recursive Method for Writing the Category Node Output
private static void EscreverCategoriasFilho(Categoria Cat)
{
foreach (Categoria cf in Cat.CategoriasFilho)
{
Console.WriteLine(cf.Nodes);
if (cf.CategoriasFilho.Count > 0)
{
EscreverCategoriasFilho(cf);
}
}
}