C# – Convertendo um DataReader para uma lista genérica e exibindo em um TreeView
O objetivo principal deste artigo é mostrar como podemos converter um DataReader para uma lista genérica de objetos.
O objetivo principal deste artigo é mostrar como podemos converter um DataReader para uma lista genérica de objetos. Eu vou usar um exemplo onde irei acessar uma tabela Alunos de um banco de dados↳Banco de dados134 conteúdosSQL ou NoSQL: eis a questão!!Data · mar 2020Banco de dados: como organizar e dar segurança para milhões de dados de loteriasData · mai 20215 serviços gratuitos na cloud para bancos de dados PostgresData · fev 2025Ver tudo em Data → SQL Server chamado Escola.mdf e exibir os dados em um controle TreeView.
Vou aproveitar e mostrar como preencher o TreeView usando o modo tradicional com ADO .NET↳.NET113 conteúdosNovidades do .NET 9 – o tipo genérico OrderedDictionaryDev (Back & Front) · dez 2024Novidades .NET 10: novas formas de uso do .NET CLIDev (Back & Front) · dez 2025Novidades do .NET 5: implementando um proxy reverso com YARP + ASP.NET 5Dev (Back & Front) · nov 2020Ver tudo em Dev (Back & Front) → e o código embutido no formulário e o modo mais indicado, onde irei criar uma classe que atuará como uma camada de acesso a dados e onde eu irei retornar uma coleção de objetos de forma que, na camada de apresentação – o formulário Windows Forms -, eu não tenha que referenciar nada relacionado a acesso aos dados, como comandos SQL ou objetos ADO .NET para acesso a dados.
Para tornar o exemplo bem simples, eu não vou criar a camada de negócios BLL e dessa forma algumas definições eu vou ter que fazer na camada de acesso a dados como a definição da string de consulta.
Para um solução mais robusta, recomenda-se a complementação do exemplo criando pelo menos a camada de negócios.
Eu vou usar o Visual Studio 2012 Express for Desktop e a linguagem C# e criar uma solução (File->New Project) Windows Forms Application, chamada PreenchendoTreeView.
No formulário form1.cs vamos os seguintes controles:
- GroupBox
- TreeView – treeView1
- Button – btnCarregar – Carregar Dados (SQL)
- Button – btnCarga – Carregar Dados (Objetos)
- LinkLabel – lnkRecolherItens – Recolher Itens
- Label – lblItem ; Autosize = false
O leiaute do formulário form1.cs deverá ter a seguinte aparência:
Abaixo vemos a estrutura da tabela Alunos do banco de dados Escola.mdf e alguns dados incluídos para o teste:
Acessando dados e preenchendo um TreeView – modo tradicional
Vamos começar usando o método tradicional, digo tradicional porque é o modo mais usado, onde todo o código é colocado no próprio formulário.
No formulário form1.cs vamos colocar todo o código necessário para acessar os dados e exibir as informações no TreeView. Para isso teremos que declarar os seguintes namespaces no início do formulário form1.cs:
using System; using System.Drawing; using System.Windows.Forms; using System.Data.SqlClient; using System.Collections.Generic;
A seguir, logo após a declaração da classe form1, devemos definir as variáveis usadas para acessar os dados e a string de conexão.
SqlConnection Conn = new SqlConnection("Data Source=.\\SQLExpress; Initial Catalog=Escola; Integrated Security=True");
SqlDataReader rdr;
SqlCommand cmd;
No evento Click do botão Carregar Dados vamos incluir o código que chama a rotina que acessa os dados e preenche o TreeView:
private void btnCarregar_Click(object sender, EventArgs e)
{
treeView1.Nodes.Clear();
Carregar();
}
A rotina Carregar() faz todo o trabalho: acessa o banco de dados executa a consulta obtendo um DataReader e extrai os dados preenchendo o TreeView:
private void Carregar()
{
lblItem.Text = "";
cmd = new SqlCommand("Select id,nome,email,curso From Alunos Order By id", Conn);
Conn.Open();
rdr = cmd.ExecuteReader();
TreeNode parent = treeView1.Nodes.Add("Alunos");
TreeNode child;
parent.ForeColor = Color.Red;
while (rdr.Read())
{
child = parent.Nodes.Add("Aluno ID: " + rdr.GetValue(0).ToString());
child.ForeColor = Color.Blue;
child.Nodes.Add("Nome: " + rdr.GetValue(1).ToString());
child.Nodes.Add("Email: " + rdr.GetValue(2).ToString());
child.Nodes.Add("Curso: " + rdr.GetValue(3).ToString());
}
parent.ExpandAll();
rdr.Close();
Conn.Close();
}
A execução do código gera o seguinte resultado:
Apenas para conhecimento, a seguir temos o código que obtém um item selecionado do TreeView e o exibe em um controle Label no formulário:
private void treeView1_AfterSelect(object sender, TreeViewEventArgs e)
{
int IDSelecionado = treeView1.SelectedNode.Index;
String NomeSelecinado = treeView1.SelectedNode.Text;
lblItem.Text = NomeSelecinado.ToString();
}
Obs: Estamos obtendo o índice do item selecionado apenas para ilustrar como fazer isso.
O código para recolher os itens do TreeView que colocamos no evento Click do controle LinkLabel esta abaixo:
private void lnkRecolherItens_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
{
lblItem.Text = "";
treeView1.CollapseAll();
}
Para limpar os itens do TreeView usamos o treeView1.Nodes.Clear(). Já para expandir os itens do TreeView, o código é treeView1.ExpandAll().
Tudo muito simples, não é mesmo ?
Acessando dados e preenchendo um TreeView – usando as boas práticas
Você não precisa ser um Phd em padrões de projetos para usar as boas práticas no desenvolvimento de software. Existem ações que podem ser tomadas desde o início do desenvolvimento até mesmo para aplicações muito simples que garantem o mínimo de robustez e tornam o código muito mais legível e fácil de manter.
O modo tradicional usado é muito simples e funciona, mas ele nunca deve ser usado em aplicações de produção pois ele oculta em sua simplicidade grandes problemas que irão aparecer quando você precisar dar manutenção ou estender a aplicação com novas funcionalidades.
A primeira coisa que chama a atenção é que a camada de apresentação tem que conhecer como acessar os dados, e isso não é a sua função. A camada de apresentação deve apenas saber apresentar os dados ao usuário deixando a responsabilidade de saber como acessar e obter os dados para outra camada.
Chamamos isso de separação das responsabilidades em uma aplicação e aplicamos essa separação trabalhando em camadas. Isso não é um capricho ou uma moda, existe uma razão muito forte para se recomendar essa metodologia de trabalho, pois ela facilita a manutenção e permite a reutilização de código.
Vamos então criar uma camada de acesso a dados que no nosso exemplo será representada por uma classe chamada ConexaoDB. O mais correto seria criar um novo projeto do tipo Class Library e nele definir as classes, mas devido a simplicidade do nosso projeto irei criar apenas a classe que atuará como a camada de acesso a dados.
No menu PROJECT clique em Add Class e informe o nome ConexaoDB.cs e a seguir defina o código abaixo nesta classe:
using System;
using System.Data;
using System.Data.SqlClient;
using System.Configuration;
using System.Collections.Generic;
namespace PreenchendoTreeView
{
public class ConexaoDB
{
private SqlConnection _connection;
public ConexaoDB()
{
try
{
string strConnectionString = ConfigurationManager.ConnectionStrings["ConexaoSQL"].ToString();
_connection = new SqlConnection(strConnectionString);
}
catch (Exception ex)
{
throw ex;
}
}
public List<Aluno> GetAlunosOB()
{
string consulta = "Select * from Alunos";
List<Aluno> _alunos = new List<Aluno>();
try
{
_connection.Open();
SqlCommand cmd = new SqlCommand(consulta, _connection);
cmd.CommandType = CommandType.Text;
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
_alunos.Add(new Aluno()
{
Id = Convert.ToInt32(dr["Id"]),
Nome = dr["Nome"].ToString(),
Email = dr["Email"].ToString(),
Curso = dr["Curso"].ToString()
});
}
dr.Close();
return _alunos;
}
catch (Exception ex)
{
throw ex;
}
finally
{
_connection.Close();
}
}
}
}
Esta classe será a responsável pelo acesso ao banco de dados e pela extração das informações da tabela. Todo o conhecimento de como acessar dados é com ela mesmo e ela deve ser uma especialista nisto e deve saber somente isso.
Um dos princípios básicos para desenho de software com o objetivo evitar as más práticas é o principio da responsabilidade única ou SRP – Single Responsability Principle. Este princípio foi introduzido por Tom DeMarco em 1979 no seu livro Structured Analysis and Systems Specification, Yourdon Press Computing Series.
O princípio da responsabilidade única é fundamental no projeto de software que reza o seguinte :
“Deve existir um e somente UM MOTIVO para que uma classe mude”
Portanto, uma classe deve ser implementada tendo apenas um único objetivo.
Quando uma classe possui mais que um motivo para ser alterada é por que provavelmente ela esta fazendo mais coisas do que devia, ou seja, ela esta tendo mais de um objetivo.
Podemos então inferir as seguintes premissas a partir da definição da responsabilidade única:
- Baseado no princípio da coesão funcional, uma classe deve ter uma única responsabilidade;
- Se uma classe possuir mais de uma responsabilidade, deve-se considerar sua decomposição em duas ou mais classes;
- Cada responsabilidade é um “eixo de mudança” e as fontes de mudança devem ser isoladas.
Este conceito é fácil de entender, mas difícil de ser posto em prática.
A nossa classe ConexaoDB deve portanto ter apenas um objetivo: acessar dados. Vamos então cria a classe removendo o código responsável pelo acesso aos dados do formulário.
Nesta nossa classe temos o construtor iniciando a string de conexão que esta sendo obtida do arquivo de configuração, App.Config, usando a classe ConfigurationManager:
public ConexaoDB()
{
try
{
string strConnectionString = ConfigurationManager.ConnectionStrings["ConexaoSQL"].ToString();
_connection = new SqlConnection(strConnectionString);
}
catch (Exception ex)
{
throw ex;
}
}
Assim temos que declarar no arquivo App.Config a string de conexão da seguinte forma:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5" />
</startup>
<connectionStrings>
<add name="ConexaoSQL" connectionString="Data Source=.\SQLExpress; Initial Catalog=Escola; Integrated Security=True;" />
</connectionStrings>
</configuration>
O método GetAlunos() da classe ConexaoDB está definindo uma string de consulta usando o comando SQL – Select * from Alunos -, e, na verdade, isso não deveria estar definido nesse método, mas na camada de negócios. Como eu não criei esta camada, para ficar mais simples, eu embuti a definição da consulta do método, mas NUNCA faça isso em uma aplicação de produção. Primeiro por que nunca devemos usar Select *, visto que isso retorna TODOS os registros da tabela. Imagine se a tabela possuir um bilhão de registros?! Por isso sempre restringimos a quantidade de dados retornados de uma tabela usando a classe Where com um critério de filtro: Ex: Select id, nome, email from Alunos Where id < 10.
public List<Aluno> GetAlunosOB()
{
string consulta = "Select * from Alunos";
List<Aluno> _alunos = new List<Aluno>();
try
{
_connection.Open();
SqlCommand cmd = new SqlCommand(consulta, _connection);
cmd.CommandType = CommandType.Text;
SqlDataReader dr = cmd.ExecuteReader();
while (dr.Read())
{
_alunos.Add(new Aluno()
{
Id = Convert.ToInt32(dr["Id"]),
Nome = dr["Nome"].ToString(),
Email = dr["Email"].ToString(),
Curso = dr["Curso"].ToString()
});
}
dr.Close();
return _alunos;
}
catch (Exception ex)
{
throw ex;
}
finally
{
_connection.Close();
}
}
Outro detalhe importante neste método é que estamos retornando uma coleção de objetos Aluno e não um datareader. Fiz isso nesta classe por que não criei a bendita camada de negócios que seria a responsável por este tratamento. Dessa forma a nossa camada de apresentação vai receber um coleção de objetos e não um datareader o que não seria muito indicado pois ela teria que saber como tratar um DataReader e isso SÓ camada de acesso a dados deve saber.
Precisamos então definir uma classe que irá representar o nosso modelo de dados e faremos isso criando a classe Aluno (PROJECT->Add Class) e definindo o código desta classe conforme abaixo:
namespace PreenchendoTreeView
{
public class Aluno
{
public int Id { get; set; }
public string Nome { get; set; }
public string Email { get; set; }
public string Curso { get; set; }
}
}
Esta é uma classe POCO – Plain Old CLR Object – e ela mapeia os campos definidos na tabela Alunos, onde para cada campo definimos uma propriedade com o mesmo nome.
Essa classe vai agir como um DTO – Data Transfer Object – servindo apenas para que possamos passar os dados entre as camadas. No nosso exemplo entre a classe ConexaoBD e a camada de apresentação.
Finalmente na camada de apresentação no evento Click do botão Carregar Dados temos o código que vai receber os objetos e usando um laço foreach vai percorrer e exibir as informações no TreeView:
private void btnCarga_Click(object sender, EventArgs e)
{
lblItem.Text = "";
treeView1.Nodes.Clear();
try
{
ConexaoDB cdb = new ConexaoDB();
TreeNode parent = treeView1.Nodes.Add("Alunos");
TreeNode child;
parent.ForeColor = Color.Red;
List<Aluno> _alunos = new List<Aluno>();
_alunos = cdb.GetAlunosOB();
foreach (Aluno _aluno in _alunos)
{
child = parent.Nodes.Add("Aluno ID: " + _aluno.Id.ToString());
child.ForeColor = Color.Blue;
child.Nodes.Add("Nome: " + _aluno.Nome.ToString());
child.Nodes.Add("Email: " + _aluno.Email.ToString());
child.Nodes.Add("Curso: " + _aluno.Curso.ToString());
}
treeView1.ExpandAll();
}
catch (Exception ex)
{
MessageBox.Show("Erro : " + ex.Message);
}
}
Note que tivemos que criar uma instância da classe ConexaoDB para poder usar o método GetAlunosOB() pois o método é um método de instância e não é um método estático. Poderíamos ter criado um método estático na classe ConexaoDB de forma a não precisar criar uma instância da classe para acessá-lo.
Ao criar uma instância da classe no formulário acabamos criando uma dependência, ou seja um acoplamento entre ela e o formulário, e isso não é bom mas pode se evitado usando ainjeção de dependência. Assunto que eu não vou tratar neste artigo.
A execução vai produzir o mesmo resultado:
Vimos neste artigos coisas importantes, partindo de uma abordagem tradicional e avançando na direção das boas práticas apenas criando uma classe e separando o código da camada de apresentação; já é um grande avanço mas o caminho é longo e para percorrê-lo precisamos de mais artigos.
Pegue o projeto completo aqui: PreenchendoTreeView.zip (sem o banco de dados)










