LINQ - Usando Linq para consultas e cálculos com coleções


O modelo de programação LINQ ou Language Integrated Query ou linguagem de consulta integrada é um recurso disponível integralmente a partir da versão 3.5 da plataforma .NET que permite realizar consultas de dados em coleções de objetos, banco de dados, arquivos XML, textos, etc.

Dessa forma a LINQ fornece uma metodologia que a princípio simplifica e unifica a implementação de qualquer tipo de acesso a dados.

As três principais divisões da linguagem LINQ são:

A linguagem LINQ esta integrada ao Framework da plataforma .NET e esta totalmente disponível para que você a utilize de acordo com o cenário com o qual esta lidando, e você pode usá-la diretamente em seu código quer seja na linguagem C# , VB .NET, J#, F#, etc.

O movimento em direção ao LINQ teve início na versão 3.0 da plataforma .NET quando foram introduzidas as coleções genéricas , os tipos anônimos, as variáveis tipadas por expressão, a inicialização direta de objetos e coleções e as expressões lambdas e árvores de expressões; esse arsenal de recursos preparou o terreno para que a linguagem LINQ fosse construída com o objetivo de realizar consultas de forma rápida simples e intuitiva em informações que estão em coleções.

OBS: A LINQ teve sua primeira aparição em setembro de 2005 como um tecnical preview. Atualmente na versão 4.0 da plataforma .NET a LINQ também inclui LINQ to Entities, a qual é parte da ADO .NET Entity Framework e Parallel LINQ (PLINQ)

Assim o grande tchan do LINQ é que ela foi criada com o objetivo de simplificar consultas a informações na memória em coleções como listas e arrays, bem como informações armazenadas em base de dados , documentos XML , arquivos e outras fontes de dados. Ela realiza uma mapeamento objeto Relacional de forma que  o acesso a dados é feito através do framework LINQ e as instruções SQL são geradas implicitamente.

Usando o LINQ você não vai precisar conhecer SQL , XML , XPath, ADO .NET para acessar/atualizar dados pois após referenciar as classes LINQ e efetuar o mapeamento basta usar os recursos do framework LINQ para realizar as tarefas comuns de manutenção conhecidas como CRUD (Create, Update, Delete).

A figura abaixo representa a arquitetura da linguagem LINQ:

Compare abaixo dois trechos de código com acesso a dados, o primeiro usando ADO .NET e o segundo usando LINQ (LINQ to SQL)

1- ADO .NET

Dim c As SqlConnection = New SqlConnection(stringConexao) 

c.Open()

Dim cmd As SqlCommand = New SqlCommand("SELECT p.ProductID, p.ProductName FROM Products p WHERE p.ProductName = @p0") 
cmd.Parameters.AddWithValue("@p0", "Teste“)

Dim dr As DataReader = c.Execute(cmd) 

While dr.Read()
   Dim ID As String = dr.GetString(0) 
   Dim ProductName As String = dr.GetString 
End While
 

2- LINQ To SQL

Dim db as new DataContext1()

Dim consulta = from p in db.Products where (p.ProductName=="Teste")
                     select new {p.ProductID,p.ProductName};

                     Me.GridView1.DataSource = consulta
                     Me.GridView1.DataBind()

O código ficou mais enxuto mais elegante, e , além da economia de código o LINQ continua fortemente tipado e usa os recursos do IntelliSense da plataforma .NET sendo que após efetuar o mapeamento usando as consultas LINQ você terá acesso aos campos da tabela de dados que foram mapeados sem precisar conhecer a sua estrutura de dados, poderá usar o Intellisense e outros recursos da plataforma .NET.

A sintaxe LINQ

A LINQ é baseado em um conjunto de operadores de consulta, definidos como métodos de extensão, que trabalham com qualquer objeto que implemente a interface IEnumerable<T>.

A sintaxe básica da linguagem é:

var <variável> = from <elemento da lista> in <lista de dados>
                         where <clausula>
                         select <elemento>
Dim <variável> = from <elemento da lista> in <lista de dados>
                          where <clausula>
                          select <elemento>
C# VB .NET

Exemplo usando a sintaxe VB .NET :

Dim dados = From produtos In db.Products 
                  Where
 produtos.UnitPrice > 50 
                
  Order By produtos.ProductName 
                  
Select
 produtos.ProductName, produtos.UnitPrice

A consulta LINQ To SQL inicia com a cláusula From e em seguida o operador de condição Where depois ordenação com Order By, e, no final o operador de seleção Select.

Um dos motivos desta inversão de ordens é  o uso recurso IntelliSense, pois quando você indica primeiro a origem dos dados ele pode mostrar as listas de membros de tipos nos objetos em sua coleção.

Outro motivo , segundo Anders Hejlsberg , seria que a ordem esta mais próxima da nossa lógica de pensamento.

Quando você digita uma instrução SQL iniciando com Select na verdade você já esta pensando na origem dos dados , condições , agrupamentos. etc. (você concorda?)

A cláusula From é a mais importante do LINQ pois é usada em todas as consultas.

Uma consulta deve sempre começar com From. (O Select pode estar implícito o From não.)

LINQ to Objects

Neste artigo eu vou tratar do LINQ to Objects na plataforma .NET versão 4.0 cujo objetivo é realizar consultas sobre coleções que implementam as interfaces IEnumerable ou IEnumerable<T>, ou seja, Arrays, Listas genéricas do tipo List<T>, dicionários de dados, textos, etc.

Para poder usar LINQ to Objects basta definir o namespace System.Linq no seu projeto conforme a linguagem que estiver usando.

Nos exemplos usados neste artigo vamos usar LINQ to Objects focando nas consultas e nos operadores usando a linguagem C#.

1- Usando LINQ to Objects com DataGridView

A LINQ to Objects nos dá a capacidade de escrever código declarativo para retornar dados de coleções que implementam as interfaces IEnumerable ou IEnumerable<T>.

Neste primeiro exemplo vou criar um projeto Windows Forms usando o Visual C# 2010 Express Edition para exibir dados em um controle DataGridView.

Vou criar uma classe Funcionarios definindo algumas propriedades que iremos retornar. A seguir vamos declarar um objeto BindingList<> do tipo Funcionarios de forma que quando o usuário preencher cada caixa de texto os valores  serão atribuídos às propriedades da classe e os objetos serão incluídos na coleção BindingList<>.

Para concluir vamos usar uma consulta LINQ para obter o resultado exibindo no controle DataGridView.

Abra o Visual C# 2010 Express Edition e crie um novo projeto do tipo Windows Forms no menu FIle-> New Project e informe o nome LinqToObjects_DataGridView;

A seguir no formulário padrão form1.cs inclua os controles GroupBox,  TextBox , Button e DataGridView conforme o leiaute da figura abaixo:

No início do formulário Form1.cs temos a declaração do objeto BindingList<> do tipo Funcionarios:

// declara um objeto BindingList<> do tipo Funcionarios
BindingList<Funcionarios> funcionarios = new BindingList<Funcionarios
>();

A classe BindingList<T> Fornece uma coleção genérica que suporte vinculação de dados.

A classe BindingList<T> pode ser usada como uma classe base para criar um mecanismo de ligação de dados bidirecional. A classe BindingList<T> fornece uma implementação concreta e genérica da interface IBindingList interface.

Essa é uma alternativa para implementar a interface IBindingList, que pode ser difícil devido a interação sutil entre IBindingList, IEditableObject e o associado CurrencyManager.

No menu Project clique em Add Class para incluir uma nova classe ao projeto com o nome Funcionarios.cs.

Em seguida defina o seguinte código para a classe Funcionarios:

namespace LinqToObjects_DataGridView
{
    class Funcionarios
    {
        //Propriedades Automatica
        public string Nome { get; set; }
        public string Email { get; set; }
    }
}

Os namespaces usados no formulário Form1.cs são:

using System;

using System.ComponentModel;

using System.Linq;

using System.Windows.Forms;

No evento Click do botão Inclui na Lista temos o código abaixo que atribui os valores informados na caixas de texto as propriedades da classe e inclui os objetos na lista:

private void btnIncluiNaLista_Click(object sender, EventArgs e)
 {
            // Inclui objetos funcionarios à lista 
            funcionarios.Add(new Funcionarios
            {
                Nome = txtNome.Text,
                Email = txtEmail.Text
            });
 }

No evento Click do botão Exibe Dados temos o código a seguir que realiza um consulta LINQ na lista de funcionarios e os exibe em um controle DataGridView:

   private void btnExibeDados_Click(object sender, EventArgs e)
   {
            //consulta com linq para obter os funcionários da lista
            var consulta = from funci in funcionarios select funci;
            //exibição do resultado no datagridview
            gdvFuncionarios.DataSource = consulta.ToList();
  }

Executando o projeto e teremos o seguinte resultado:

2- Usando LINQ to Objects com uma Cesta de Compras

Neste exemplo vou mostrar como podemos realizar consultas e efetuar cálculos usando LINQ.

Vamos simular uma cesta de comparas onde o usuário irá selecionar um produto, preço e quantidade exibidos em controles ComboBox e poderá incluir a seleção em um controle ListBox montando uma cesta de compras onde teremos o cálculo do valor total da cesta.

No menu Project clique em Add Windows Form e aceite o nome padrão Form2.cs.

A seguir inclua os seguintes controles : GroupBox, Combobox, Button, ListBox e TextBox conforme o leiaute abaixo:

Agora vamos criar duas classes: uma para a cesta de compras chamada CestaCompras e outra para os produtos chamada Produto.

No menu Project clique em Add Class para incluir uma nova classe ao projeto com o nome CestaCompras.cs e defina o seguinte código na classe:

namespace LinqToObjects_DataGridView

{

  class CestaCompra

  {

     public string Produto { get; set; }

     public double Preco { get; set; }

    public int Quantidade { get; set; }

  }

}

No menu Project clique em Add Class para incluir uma nova classe ao projeto com o nome CestaCompras.cs e defina o seguinte código na classe:

namespace LinqToObjects_DataGridView

{

  class Produto

  {

    public string ProdutoNome { get; set; }

    public double ProdutoPreco { get; set; }

  }

}

 

Os namespaces usados no formulário são:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Windows.Forms;

No evento Load do formulário Form2.cs temos o código que irá popular os controles Combobox do formulário com base na classe Produto:

      private void Form2_Load(object sender, EventArgs e)
        {
            //Cria uma lista de produtos
            var dataSource = new List<Produto>();
            dataSource.Add(new Produto() { ProdutoNome = "Mouse", ProdutoPreco = 12.65 });
            dataSource.Add(new Produto() { ProdutoNome = "PadMouse", ProdutoPreco = 11.25 });
            dataSource.Add(new Produto() { ProdutoNome = "Teclado", ProdutoPreco = 10.56 });
            dataSource.Add(new Produto() { ProdutoNome = "Monitor", ProdutoPreco = 356.41 });
            dataSource.Add(new Produto() { ProdutoNome = "HD Samsumg 320 GB", ProdutoPreco = 156.18 });
            dataSource.Add(new Produto() { ProdutoNome = "Memoria 2 GB", ProdutoPreco = 89.15 });
            //define o databinding o produto
            this.cboProduto.DisplayMember = "ProdutoNome";
            this.cboProduto.ValueMember = "ProdutoNome";
            this.cboProduto.DataSource = dataSource;
            // torna somente leitura
            this.cboProduto.DropDownStyle = ComboBoxStyle.DropDownList;
            //define o databinding para o preco
            this.cboPreco.DisplayMember = "ProdutoPreco";
            this.cboPreco.ValueMember = "ProdutoPreco";
            this.cboPreco.DataSource = dataSource;

            // torna somente leitura
            this.cboPreco.DropDownStyle = ComboBoxStyle.DropDownList;
            //seleciona o primeiro elemento da combo cboquantidade
            cboQuantidade.SelectedIndex = 0;
       }

Para atribuir valores na ComboBox cboQuantidade  usamos a propriedade Items e atribuímos valores de 1 a 10;

No início do formulário Form2.cs temos a declaração que irá definir uma lista de objetos do tipo CestaCompras :

// declara um objeto List<> do tipo CestaCompras
List<CestaCompra> cesta = new List<CestaCompra>();

No evento Click do botão - Incluir na Cesta de Compras - temos o código que irá atribuir os valores selecionados ao objeto incluindo-os na lista e a seguir calculando o preço e o valor total para exibição no formulário:

    private void btnIncluiNaCesta_Click(object sender, EventArgs e)
        {
            // Inclui objetos CestaCompra a lista 
            string nomeProduto = cboProduto.SelectedValue.ToString();
            double precoProduto = Convert.ToDouble(cboPreco.SelectedValue);
            int qtde = Convert.ToInt32(cboQuantidade.SelectedItem);
            cesta.Add(new CestaCompra { Produto = nomeProduto, 
                                                        Preco =precoProduto, 
                                                        Quantidade = qtde });
            // usando linq para consultar as propriedades da classe CestaCompras
            var q = from item in cesta
                       select string.Format("Produto: {0}    Preco: {1}   Qtde: {2}", item.Produto, item.Preco, item.Quantidade);
            
            //Populando o ListBox com todas as propriedades de CestaCompras
            lstbCesta.Items.Clear();
            lstbCesta.Items.AddRange(q.ToArray());
            lstbCesta.Items.Add("");

            // usando linq para calcular o preco de cada produto
            var preco = from item in cesta select (item.Preco * item.Quantidade);         
            // usando linq para calcular o preco total usando os métodos de extensão agregados
            double[] Precos = preco.ToArray();
            double total = Precos.Aggregate((a, b) => a + b);
            txtTotal.Text = total.ToString();
        }

Destaques do código acima:

Estamos realizando um cálculo para cada item da cesta multiplicando o preço pela quantidade:

 var preco = from item in cesta select (item.Preco * item.Quantidade);    

A seguir usamos o método Aggregate para somar os valores:

double[] Precos = preco.ToArray();
double total = Precos.Aggregate((a, b) => a + b);

A sintaxe deste método é a seguinte:   Aggregate<(Of TSource>)(IEnumerable<(Of TSource>), Func<(Of TSource, TSource, TSource>))

O método Aggregate torna mais simples realizar um cálculo sobre uma seqüência de valores. Este método funciona chamando a função func uma vez para cada elemento na fonte. Cada vez que a função é chamada, Aggregate passa o elemento da seqüência e um valor agregado (como o primeiro argumento para func).

O primeiro elemento da fonte é usado como valor inicial agregado. O resultado da func substitui o valor agregado anterior.

O método Aggregate retorna o resultado final da func.

Abaixo vemos o resultado obtido:

E assim mostramos como usar a LINQ para consultas e cálculos em coleções.

Pegue projeto completo aqui: LinqToObjects_DataGridView.zip

"Porque tudo o que há no mundo, a concupiscência da carne, a concupiscência dos olhos e a soberba da vida, não vem do Pai, mas sim do mundo..." 1 João 2:16

Referências:


José Carlos Macoratti