LINQ to Objects - Realizando Consultas em Coleções de objetos

LINQ to Objects permite aos desenvolvedores da plataforma .NET escrever "consultas" sobre coleções de objetos. Para facilitar a Microsoft fornece um grande conjunto de operadores de consulta, e esses operadores oferecem uma profundidade semelhante à funcionalidade que temos com a linguagem SQL trabalhando com banco de dados relacional.

Neste artigo vamos rever alguns conceitos chaves da LINQ to Objects e mostrar na prática a sua utilização criando consultas em coleções de objetos para lembrar a sintaxe usada nessas operações.

Nota: Não Confunda LINQ to Objects com LINQ to Entities, elas são diferentes.

Tradicionalmente, trabalhar com coleções de objetos significava escrever um monte de código usando laços for ou foreach para percorrer a coleção, usar ifs para filtrar durante a execução e  manter uma soma parcial de uma propriedade total.

A LINQ nos libera de ter que escrever o código usando muitos laços; ela permite  escrever consultas que filtram uma lista que façam cálculos usando funções de agregação de elementos em uma coleção em memória.

Podemos escrever consultas contra qualquer tipo de coleção que implementa uma interface IEnumerable<T>, o que a grande parte das classes de coleções da plataforma .NET implementa, inclusive os arrays.

Objetivos

Aprendizado

Recursos usados

Preparando o ambiente - Criando o projeto no Visual Studio 2013

Abra o Visual Studio 2013 Express for Windows Desktop e clique em New Project;

Selecione o template Visual C# -> Windows -> Console Application e informe o nome LINQ_Objects_2 e clique em OK;

Com o projeto criado vamos incluir duas classes na solução :

No menu PROJECT clique em Add Class e informe o nome Contato.vb e a seguir inclua o seguinte código nesta classe:

Public Class Contato
    Public Property Nome() As String
    Public Property Sobrenome() As String
    Public Property Email() As String
    Public Property Telefone() As String
    Public Property Nascimento() As DateTime
    Public Property Estado() As String
    Public Shared Function DadosTeste() As List(Of Contato)
        Return New List(Of Contato)() From { _
            New Contato() With { _
                 .Nome = "José Carlos", .Sobrenome = "Macoratti", .Nascimento = New DateTime(1975, 10, 19), .Telefone = "8983 8858", .Email = "macoratti@yahoo.com", .Estado = "SP" _
            }, _
            New Contato() With { .Nome = "Armando", .Sobrenome = "Soares", .Nascimento = New DateTime(1973, 12, 9), .Telefone = "9553 8487",  .Email = "armando@net.com.br",  .Estado = "RJ" _
            }, _
            New Contato() With {  .Nome = "Natalia",  .Sobrenome = "Bueno",  .Nascimento = New DateTime(1959, 10, 3),  .Telefone = "8999 1154", .Email = "natbueg@net.com.br",   .Estado = "MG" _
            }, _
            New Contato() With { .Nome = "Jefferson", .Sobrenome = "Chaves", .Nascimento = New DateTime(1980, 12, 16),  .Telefone = "9602 6774", .Email = "jeffchav@net.com.br",  .Estado = "SP" _
            }, _
            New Contato() With { .Nome = "Carla",  .Sobrenome = "Andrade", .Nascimento = New DateTime(1985, 2, 10),  .Telefone = "8303 6030",  .Email = "carla@net.com.br", .Estado = "PR" _
            }, _
            New Contato() With { .Nome = "Jonas",  .Sobrenome = "Kagel",  .Nascimento = New DateTime(1960, 2, 20), .Telefone = "9907 5462",  .Email = "jonas@net.com.br",  .Estado = "RJ" _
            }, _
            New Contato() With { .Nome = "Maria",  .Sobrenome = "Siqueira", _
                 .Nascimento = New DateTime(1991, 10, 21), _
                 .Telefone = "9918 2789", _
                 .Email = "mariasiq@net.com.br", _
                 .Estado = "SP" _
            }, _
            New Contato() With { .Nome = "Bruno", .Sobrenome = "Sanches", _
                 .Nascimento = New DateTime(1946, 5, 18), _
                 .Telefone = "8715 9920", _
                 .Email = "bruno@net.com.br", _
                 .Estado = "MG" _
            }, _
            New Contato() With { Nome = "Mario",  .Sobrenome = "Ferreira", .Nascimento = New DateTime(1977, 9, 17), .Telefone = "9902 3644", .Email = "mariofer@net.com.br", .Estado = "RS" _
            }, _
            New Contato() With { .Nome = "Ariel", .Sobrenome = "Santos", .Nascimento = New DateTime(1962, 5, 23), .Telefone = "9837 1656", .Email = "ariel@net.com.br", .Estado = "RJ" _
            } _
        }
    End Function
End Class

Na classe Contato declaramos 6 propriedades e definimos os dados para uma coleção de objetos do tipo Contato a ser criada em memória.

A seguir, menu PROJECT clique em Add Class e informe o nome LogChamadas.vb e a seguir inclua o seguinte código nesta classe:

Public Class LogChamadas
    Public Property Numero() As String
    Public Property Duracao() As Integer
    Public Property Recebida() As Boolean
    Public Property Data() As DateTime
    Public Shared Function DadosTeste() As List(Of LogChamadas)
        Return New List(Of LogChamadas)() From { _
            New LogChamadas() With { _
                 .Numero = "9553 8487", _
                 .Duracao = 2, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 8, 7, 8, 12, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "8999 1154", _
                 .Duracao = 15, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 8, 7, 9, 23, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9602 6774", _
                 .Duracao = 1, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 8, 7, 10, 5, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "8303 6030", _
                 .Duracao = 2, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 8, 7, 10, 35, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9907 5462", _
                 .Duracao = 4, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 8, 7, 11, 15, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9553 8487", _
                 .Duracao = 15, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 8, 7, 13, 12, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9553 8487", _
                 .Duracao = 3, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 8, 7, 13, 47, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9907 5462", _
                 .Duracao = 1, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 8, 7, 20, 34, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9907 5462", _
                 .Duracao = 3, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 8, 8, 10, 10, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "8303 6030", _
                 .Duracao = 23, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 8, 8, 10, 40, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9918 2789", _
                 .Duracao = 3, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 8, 8, 14, 0, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9918 2789", _
                 .Duracao = 7, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 8, 8, 14, 37, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9902 3644", _
                 .Duracao = 6, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 8, 8, 15, 23, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9602 6774", _
                 .Duracao = 20, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 8, 8, 17, 12, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9553 8487", _
                 .Duracao = 5, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 7, 12, 8, 12, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "8999 1154", _
                 .Duracao = 12, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 6, 14, 9, 23, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9602 6774", _
                 .Duracao = 10, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 7, 9, 10, 5, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "8303 6030", _
                 .Duracao = 22, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 7, 5, 10, 35, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9907 5462", _
                 .Duracao = 9, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 6, 7, 11, 15, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9553 8487", _
                 .Duracao = 10, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 6, 7, 13, 12, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9553 8487", _
                 .Duracao = 21, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 7, 7, 13, 47, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9907 5462", _
                 .Duracao = 7, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 7, 7, 20, 34, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9907 5462", _
                 .Duracao = 2, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 6, 8, 10, 10, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "8303 6030", _
                 .Duracao = 3, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 6, 8, 10, 40, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9918 2789", _
                 .Duracao = 32, _
                 .Recebida = False, _
                 .Data = New DateTime(2014, 7, 8, 14, 0, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9918 2789", _
                 .Duracao = 13, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 7, 8, 14, 37, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9902 3644", _
                 .Duracao = 16, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 5, 8, 15, 23, 0) _
            }, _
            New LogChamadas() With { _
                 .Numero = "9602 6774", _
                 .Duracao = 24, _
                 .Recebida = True, _
                 .Data = New DateTime(2014, 6, 8, 17, 12, 0) _
            } _
        }
    End Function
End Class

Na classe LogChamadas declaramos 4 propriedades e definimos os dados para uma coleção de objetos do tipo LogChamadas a ser criada em memória que representa as ligações feitas pelos contatos. Existem portanto uma relação entre as duas classes que iremos usar para realizar a junção entre as coleções.

Consultando coleções de objetos

Vamos agora definir 4 consultas LINQ to Objects para mostrar como podemos obter informações de coleções.

No arquivo Module1.vb  da nossa solução vamos declarar a chamada para 4 métodos que serão criados posteriormente conforme o código a seguir:

 Sub Main()
        Exemplo1()
        'Exemplo2()
        'Exemplo3()
        'Exemplo4()
    End Sub

A seguir vamos criar um método que irá ser executado em todas as consultas e que irá criar um pequeno cabeçalho para identificar a consulta:

Public Sub cabecalho(texto As String)
        Console.WriteLine("Macoratti .net")
        Console.WriteLine("--------------------------------------------------")
        Console.WriteLine(texto)
        Console.WriteLine("--------------------------------------------------")
        Console.WriteLine("")
    End Sub

Vamos agora criar cada uma das consultas:

1- Retornando uma lista de contatos com idade inferior a 45 anos:

 Public Sub Exemplo1()
        cabecalho("Lista Contatos com idade inferior a 45 anos")
        Dim contatos As List(Of Contato) = Contato.DadosTeste()
        Dim q = From c In contatos Where c.Nascimento.AddYears(45) > DateTime.Now Order By c.Nascimento Descending
                    Select String.Format("{0} {1} , nascimento : {2}", c.Nome, c.Sobrenome, c.Nascimento.ToString("dd-MMM-yyyy"))
        For Each s As String In q
            Console.WriteLine(s)
        Next
        Console.ReadKey()
    End Sub

Observe que na cláusula Select usamos o método String.Format() para formatar a saída a consulta.

2- Além de filtrar itens em uma coleção e projetar os resultados, a LINQ oferece a capacidade de agrupar os itens de coleta em qualquer forma que você precisar. Nesta consulta agrupamos os contatos por Estado de origem:

 Public Sub Exemplo2()
        cabecalho("Lista Contatos por Estado")
        Dim contatos As List(Of Contato) = Contato.DadosTeste()
        Dim consulta = From c In contatos Group c By c.Estado Into ContatosGrupo = Group
        For Each grupo In consulta
            Console.WriteLine("Estado : " + grupo.Estado)
            For Each c In grupo.ContatosGrupo
                Console.WriteLine(" {0} {1}", c.Nome, c.Sobrenome)
            Next
        Next
        Console.ReadKey()
    End Sub

3-  Um aspecto fundamental no acesso aos dados relacionados é o conceito de junção dos dados relacionados.

Os Sistemas de banco de dados relacional (como o Microsoft SQL Server) têm um poderoso recursos para realizar junções que permitem escrever consultas contra dados normalizados, o que é um termo chique para a "não repetir os dados" por separar os dados entre várias tabelas vinculando-os com um valor comum.

A LINQ permite fazer a junção(join) de várias coleções de objetos em conjunto, utilizando a sintaxe similar à SQL, como vemos na consulta a seguir:

 Public Sub Exemplo3()
        cabecalho("Consulta com junção de duas Coleções com base no numero do telefone")
        Dim contatos As List(Of Contato) = Contato.DadosTeste()
        Dim chamadas As List(Of LogChamadas) = LogChamadas.DadosTeste()
        Dim consulta = From ligacao In chamadas
                             Join contato In contatos On _
                             ligacao.Numero Equals contato.Telefone _
                             Select New With { _
                                contato.Nome, _
                                contato.Sobrenome, _
                                ligacao.Data, _
                                ligacao.Duracao _
                           }
        For Each c In consulta
            Console.WriteLine(" {0} – {1} {2} ({3} min)", c.Data.ToString("dd MMM HH:m"), c.Nome, c.Sobrenome, c.Duracao)
        Next
        Console.ReadKey()
    End Sub

4-  Para demonstrar o poder da LINQ to Objects a consulta a seguir totaliza os dados a partir de duas coleções em memória usando junções, agrupamentos e ainda os operadores agregados (SUM, COUNT, AVG, etc.) na cláusula Select :

 Public Sub Exemplo4()
        cabecalho("Resumo do log de ligações, filtrando, ordenando, agrupando, " & vbLf & _ 
"juntando e selecionando usando valores agregados")
        Dim contatos As List(Of Contato) = Contato.DadosTeste()
        Dim chamadas As List(Of LogChamadas) = LogChamadas.DadosTeste()
        Dim consulta = From ligacao In chamadas
                             Where ligacao.Recebida = True
                             Group ligacao By ligacao.Numero Into lig = Group
                             Join contato In contatos On
                                Numero Equals contato.Telefone
                             Order By contato.Nome, contato.Sobrenome
                             Select New With { _
                                 contato.Nome, _
                                 contato.Sobrenome, _
                                 .Conta = lig.Count(), _
                                 .Media = lig.Average(Function(c) c.Duracao), _
                                .Total = lig.Sum(Function(c) c.Duracao) _
                            }
        For Each ligacao In consulta
            Console.WriteLine("{0} {1} - ligações: {2}, Tempo: {3} mins, Média: {4} mins ", _ 
ligacao.Nome, ligacao.Sobrenome, ligacao.Conta, ligacao.Total, Math.Round(ligacao.Media, 2))
        Next
        Console.ReadKey()
    End Sub

Percebeu que de forma quase intuitiva criamos as consultas usando a sintaxe do LINQ to Objects em coleções de memórias.

Pegue o projeto completo aqui: LINQ_Objects_2.zip

João 3:21 Mas quem pratica a verdade vem para a luz, a fim de que seja manifesto que as suas obras são feitas em Deus. (disse Jesus)

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

 

             Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter
 

Referências:


José Carlos Macoratti