LINQ - Revisando conceitos básicos (C#)


Hoje vou revisar os conceitos básicos sobre LINQ - Language Integrated Query - com uma abordagem bem prática. A LINQ é uma linguagem semelhante a SQL que foi projetada para dar ao programador uma sintaxe consistente para consultar qualquer fonte de dados, seja ela banco de dados, XML, arquivos textos, etc.

Não vou entrar em detalhes conceituais da LINQ, para isso veja nas referências os diversos artigos que eu já escrevi sobre o assunto.

Para executar os exemplos a seguir você pode usar o Visual C# 2010 Express Edition ou o SharpDevelop 4.1. Eu vou usar o Visual C#.

Abra o Visual C# 2010 Express Edition (Você pode usar o Visual Studio 2012 Express for desktop)  e crie um novo projeto do tipo Windows Forms Application com o nome LINQ_Basico_Revisao;

A seguir no menu Project clique em Add Class e informe o nome Livro definindo o código abaixo para a classe livro:

namespace LINQ_Basico_Revisao
{
    class Livro
    {
        public string Titulo { get; set; }
        public int AutorId { get; set; }
        public int AnoPublicacao { get; set; }

        public Livro(string titulo, int autorId, int ano)
        {
            this.Titulo = titulo;
            this.AutorId = autorId;
            this.AnoPublicacao = ano;
        }

        public override string ToString()
        {
            return string.Format("{0} - {1}", Titulo, AnoPublicacao);
        }
    }
}

Repita o procedimento anterior e crie uma classe Autor com o seguinte código:

namespace LINQ_Basico_Revisao
{
    class Autor
    {
        public int Id { get; set; }
        public string Nome { get; set; }
        public string Sobrenome { get; set; }

        public Autor (int id, string nome, string sobrenome)
        {
            this.Id = id;
            this.Nome = nome;
            this.Sobrenome = sobrenome;
        }
    }
}

Essas duas classes servirão de base para os nossos exemplos a seguir.

Nota: Eu poderia ter criado as duas classe em um único arquivo.

No formulário form1.cs declare as variáveis objeto livros e autores:

List<Livro> livros = new List<Livro>();
List<Autor> autores = new List<Autor>();

No evento Load do formulário vamos criar as coleções de livros e autores:

 livros = new List<Livro>{
            new Livro("Le Rhin", 1, 1842),
            new Livro("Les Burgraves",1, 1843),
            new Livro("Napoléon le Petit",1, 1852),
            new Livro("Les Châtiments",1, 1853),
            new Livro("Les Contemplations", 1, 1856),
            new Livro("Les Misérables", 1, 1862),
            new Livro("O Alquimista", 2, 1988),
            new Livro("O Processo",3, 1925)
            };

            autores = new List<Autor>
            {
                new Autor(1, "Victor", "Hugo"),
                new Autor(2, "Coelho", "Paulo"),
                new Autor(3, "Kafka", "Franz")
            };

Dessa forma criamos as coleções de objetos que iremos usar nos exemplos.

Inclua no formulário form1.cs um controle ListBox com nome igual a lstDados para exibir os dados.

A tabela abaixo traz um resumo das principais cláusulas usadas nas consultas LINQ:

Cláusula Descrição
from Especifica a fonte de dados e uma variável de série;
where Filtra elementos da fonte de dados baseada em uma ou mais condições definidas
select Faz projeções, permitindo especificar o tipo e o formato dos elementos do resultado da consulta.
join Combina duas fontes de dados baseado em comparações de igualdade entre dois elementos especificados.
in Usada na cláusula join.
on Usada na cláusula join.
equals Palavra-chave contextual, utilizada na cláusula join. Substitui o operador == na comparação.
group Agrupa os resultados de uma consulta de acordo com valores específicos de uma determinada chave.
by Usada na cláusula group.
into Fornece um identificador para servir de referência aos resultados de uma cláusula de junção (join), agrupamento (group) ou projeção (select).
orderby Classifica os resultados em ordem ascendente ou descendente.
ascending Usada na cláusula orderby para determinar a classificação em ordem ascendente, que é a padrão, caso seja omitida.
descending Usada na cláusula orderby para determinar a classificação em ordem descendente.

1- Consultando uma coleção de objetos

Inclua um controle Button no formulário com o nome btnTodosLivros e texto - Retornar Todos os Livros, e no seu evento Click inclua o código abaixo:

  private void btnTodosLivros_Click(object sender, EventArgs e)
   {
            var todosLivros = from livro in livros select livro;
            foreach (Livro livro in todosLivros)
            {
                lstDados.Items.Add(livro.ToString());
            }
   }

A consulta utiliza a palavra-chave var é usada para declarar a todosLivros como uma coleção de objetos Livro.

O laço foreach percorre a coleção exibindo os livros no listbox.

A sintaxe da consulta LINQ inicia com from e termina com select.

Não parece uma declaração da linguagem SQL ??? (Note a cláusula From; Você verá a seguir que as cláusulas Order By, Where , Select , etc também são usadas no LINQ.)

Pois é isso mesmo. A LINQ apresenta uma sintaxe usada nas linguagens funcionais como a SQL de forma tornar mais intuitiva o acesso aos dados.

Perceba também que a lógica da declaração esta invertida em relação a usada na linguagem SQL.

SQL  Select livro From livros
LINQ  From livro in livros Select livro

2- Ordenando o resultado de uma consulta

Para ordenar uma consulta LINQ usamos a instrução orderby.

A sintaxe padrão usada para nosso exemplo seria:

var livrosordenados= from livro in livros orderby livro.Titulo select livro;

Para realizar uma ordenação descendente fazemos assim:

var livrosordenados= from livro in livros
                                orderby livro.Titulo descending
                                select livro;

Para uma ordenação de múltiplas colunas fazemos assim:

var livrosordenados= from livro in livros
                                orderby livro.AnoPublicacao, livro.Titulo descending
                                select livro;

Inclua um Button no formulário com nome btnOrdenarLivros e texto Ordernar Livros e inclua também dois controles checkbox : chkDESC e chkMULT para seleção da ordenação descendente e de múltiplas colunas.

A seguir no evento Click do botão de comando digite o código abaixo:

    private void btnOrdenarLivros_Click(object sender, EventArgs e)
        {
            lstDados.Items.Clear();
           
            if (chkDESC.Checked)
            {
                 var livrosordenados = from livro in livros
                                                 
orderby livro.Titulo descending
                                                  select livro;

                 foreach (Livro livro in livrosordenados)
                 {
                     lstDados.Items.Add(livro.ToString());
                 }
            }
            else if (chkMULT.Checked)
            {
                var livrosordenados = from livro in livros
                                                
orderby livro.AnoPublicacao, livro.Titulo descending
                                                 select livro;

                foreach (Livro livro in livrosordenados)
                {
                    lstDados.Items.Add(livro.ToString());
                }
            }
            else
            {
                var livrosordenados = from livro in livros
orderby livro.Titulo select livro;
                foreach (Livro livro in livrosordenados)
                {
                    lstDados.Items.Add(livro.ToString());
                }
            }
        }

3- Filtrando uma coleção de objetos

Da mesma forma que a SQL a LINQ possui a cláusula Where que pode ser usada para filtrar os resultados de acordo com as condições que você especificar.

Assim para filtrar livros com ano de publicação anterior a 1855 criamos a seguinte consulta:

var antesde1850 = from livro in livros
                             
where livro.AnoPublicacao < 1855
                             select livro;

Se você quiser usar mais de uma condição deve usar os operadores && e || (e/ou). Veja o exemplo a seguir que filtra os livros com ano de publicação entre 1850 e 1860:

var antesde1850 = from livro in livros
                             
where livro.AnoPublicacao > 1850
                            
 && livro.AnoPublicacao <= 1860
                             select livro;

Inclua um novo botão de comando com nome btnFiltrar e texto - Filtrar Livros (<1855) e a seguir defina o código abaixo no evento click do botão:

     private void btnFiltrar_Click(object sender, EventArgs e)
        {

            var antesde1850 = from livro in livros
                             
where livro.AnoPublicacao < 1855
                              select livro;

            var entre1850_1860 = from livro in livros
                                
where livro.AnoPublicacao > 1850
                                
&& livro.AnoPublicacao <= 1860
                                 select livro;

            foreach (Livro livro in antesde1850)
            {
                lstDados.Items.Add(livro.ToString());
            }
        }

4- Obter uma coleção de uma porção de objetos (Projeção)

Vamos supor que desejamos retornar uma coleção de objetos que representam apenas os títulos e o ano de publicação excluindo o autor a partir do nosso exemplo.

Realizar esta tarefa sem usar o LINQ, embora não fosse difícil, envolvia tratar gerar um código volumoso e desajeitado para iterar sobre todos os objetos e inserir as propriedades desejadas para a nova coleção.

Com LINQ podemos fazer isso em apenas uma única linha de código:

var apenasTitulosAntes1855 = from livro in livros
                                              where livro.AnoPublicacao > 1850
                                              select livro.Titulo;

O resultado obtido em apenasTitulosAntes1855 é uma coleção de objetos strings.

Inclua um novo botão de comando com nome btnProjecao e texto - Apenas Titulos (<1855) e a seguir defina o código abaixo no evento click do botão:

 private void btnProjecao_Click(object sender, EventArgs e)
        {
            lstDados.Items.Clear();

            var apenasTitulosAntes1855 = from livro in livros
                                                         where livro.AnoPublicacao > 1850
                                                         select livro.Titulo;

            foreach (
String livro in apenasTitulosAntes1855)
            {
                lstDados.Items.Add(livro.ToString());
            }
        }

5- Realizando um junção (join)

Em muitas ocasiões você vai precisar realizar uma consulta que necessita combinar dados de mais de uma tabela.

Para realizar esta operação podemos usar o operador Join.

No exemplo a seguir vamos combinar a coleção de autores com a coleção de livros, onde desejamos exibir apenas a informação do autor e do título do livro.

A consulta usada neste caso é a seguinte:

var joinLivrosAutores = from livro in livros
                                 
join autor in autores on livro.AutorId equals autor.Id
                                
 select new { Livro = livro.Titulo, Autor = autor.Nome + " " + autor.Sobrenome };

Inclua um novo botão de comando com nome btnJoin e texto - Fazendo Join e a seguir defina o código abaixo no evento click do botão:

      private void btnJoin_Click(object sender, EventArgs e)
        {
            lstDados.Items.Clear();
            var joinLivrosAutores = from livro in livros
                                            
join autor in autores on livro.AutorId equals autor.Id
                                             select new { Livro = livro.Titulo, Autor = autor.Nome + " " + autor.Sobrenome };

            foreach (var tituloAutor in joinLivrosAutores)
            {
                lstDados.Items.Add(tituloAutor.Livro + " - " + tituloAutor.Autor);
            }
        }

Note que joinLivrosAutores é um tipo anônimo (anonymous type) gerada pelo LINQ para esta consulta.

Vimos assim um resumo bem básico sobre consultas LINQ.

Pegue o projeto completo aqui: LINQ_Basico_Revisao.zip

Joã 8:51 Em verdade, em verdade vos digo que, se alguém guardar a minha palavra, nunca verá a morte.

Referências:


José Carlos Macoratti