.NET - LINQ a seu dispor - Sintaxe e Operadores


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 VB .NET.

Eu estou usando o Visual Basic 2010 Express Edition nos exemplos deste artigo.

Os dados usados nos exemplos deste artigo consistem em um conjunto de clientes, cada um dos quais realizou pedidos de produtos.

Abra o Visual Basic 2010 Express Edition e no menu File selecione New Project e a seguir escolha o template Windows Forms Application e informe o nome LinqToObjectsVB e clique em OK;

A seguir vamos incluir uma classe no projeto escolhendo o menu Project -> Add Class e informando o nome Vendas.vb;

Vamos trabalhar com classes em memórias e para isso vamos definir as seguintes classes no arquivo Vendas.vb;

Obs: Podemos definir mais de uma classe em um único arquivo.

Public Enum Paises
    USA
    Italia
    Brasil
End Enum

Public Class Cliente
    Public Nome As String
    Public Cidade As String
    Public Pais As Paises
    Public Pedidos As Pedido()
    Public Overrides Function ToString() As String
        Return String.Format("Nome: {0} – Cidade: {1} – Pais: {2}", Me.Nome, Me.Cidade, Me.Pais)
    End Function
End Class


Public Class Pedido
    Public PedidoID As Integer
    Public Quantidade As Integer
    Public Despacho As Boolean
    Public Mes As String
    Public ProdutoID As Integer
    Public Overrides Function ToString() As String
        Return String.Format("PedidoID: {0} - ProdutoID: {1} - " & "Quantidade: {2} - Despacho: {3} - " &
        "Mês: {4}", Me.PedidoID, Me.ProdutoID, Me.Quantidade, Me.Despacho, Me.Mes)
    End Function
End Class

Public Class Produto
    Public ProdutoID As Integer
    Public Preco As Decimal
    Public Overrides Function ToString() As String
        Return String.Format("Produto: {0} – Preco: {1}", Me.ProdutoID, Me.Preco)
    End Function
End Class	

Acima temos o código criado no arquivo Vendas.vb contendo as classes usadas no exemplo deste artigo.

No evento Load do formulário form1.vb do projeto vamos declarar duas rotinas para inicializar os clientes e os produtos conforme o código a seguir:

    Dim produtos As Produto()
    Dim clientes As Cliente()

    Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        iniciaClientes()
        iniciaProdutos()
    End Sub
    Private Sub iniciaClientes()
        ' -------------------------------------------------------
        ' Inicializa a coleção de clientes com seus pedidos
        ' -------------------------------------------------------
        clientes = New Cliente() {
        New Cliente With {.Nome = "Macoratti", .Cidade = "Roma", .Pais = Paises.Italia, .Pedidos = New Pedido() {
        New Pedido With {.PedidoID = 1, .Quantidade = 3, .ProdutoID = 1, .Despacho = False, .Mes = "Setembro"},
        New Pedido With {.PedidoID = 2, .Quantidade = 5, .ProdutoID = 2, .Despacho = True, .Mes = "Maio"}}},
        New Cliente With {.Nome = "Janice", .Cidade = "São Paulo", .Pais = Paises.Brasil, .Pedidos = New Pedido() {
        New Pedido With {.PedidoID = 3, .Quantidade = 15, .ProdutoID = 1, .Despacho = False, .Mes = "Julho"},
        New Pedido With {.PedidoID = 4, .Quantidade = 25, .ProdutoID = 3, .Despacho = True, .Mes = "Dezembro"}}},
        New Cliente With {.Nome = "Bianca", .Cidade = "New York", .Pais = Paises.USA, .Pedidos = New Pedido() {
        New Pedido With {.PedidoID = 5, .Quantidade = 10, .ProdutoID = 4, .Despacho = False, .Mes = "Julho"},
        New Pedido With {.PedidoID = 6, .Quantidade = 20, .ProdutoID = 6, .Despacho = True, .Mes = "Outubro"}}},
        New Cliente With {.Nome = "Jefferson", .Cidade = "Rio de Janeiro", .Pais = Paises.Brasil, .Pedidos = New Pedido() {
        New Pedido With {.PedidoID = 7, .Quantidade = 12, .ProdutoID = 3, .Despacho = True, .Mes = "Novembro"}}},
        New Cliente With {.Nome = "Miriam", .Cidade = "Chicago", .Pais = Paises.USA, .Pedidos = New Pedido() {
        New Pedido With {.PedidoID = 8, .Quantidade = 20, .ProdutoID = 5, .Despacho = False, .Mes = "Julho"}}}}
    End Sub

    Private Sub iniciaProdutos()
        produtos = New Produto() {New Produto With {.ProdutoID = 1, .Preco = 10},
                                  New Produto With {.ProdutoID = 2, .Preco = 20},
                                  New Produto With {.ProdutoID = 3, .Preco = 30},
                                  New Produto With {.ProdutoID = 4, .Preco = 40},
                                  New Produto With {.ProdutoID = 5, .Preco = 50},
                                  New Produto With {.ProdutoID = 6, .Preco = 60}}
    End Sub

Dessa forma teremos os objetos preenchidos em memória e estaremos prontos para usar o LINQ to Objects.

Usando o operador Where

Imagine que você precisa listar os nomes das cidades dos clientes dos Estados Unidos (USA).

Para filtrar um conjunto de itens podemos usar o operador Where, o qual é também chamado de operador de restrição, conforme o código abaixo:

Dim consulta = From c In clientes Where c.Pais = Paises.USA Select New With {c.Nome, c.Cidade}

Para exibir os clientes no formulários vamos usar um controle ListBox (lstResultado) e o código completo ficaria assim:

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim consulta = From c In clientes Where c.Pais = Paises.USA Select New With {c.Nome, c.Cidade}

        For Each cliente In consulta
            lstResultado.Items.Add(cliente.Nome & " => " & cliente.Cidade)
        Next
    End Sub
 
 

A assinatura do operador Where é apresentada a seguir para VB .NET e C#:

Public Shared Function Where(Of TSource)(source As IEnumerable(Of TSource), predicate As Func(Of TSource, [Boolean])) As IEnumerable(Of TSource)
End Function

Public Shared Function Where(Of TSource)(source As IEnumerable(Of TSource), predicate As Func(Of TSource, Int32, [Boolean])) As IEnumerable(Of TSource)
End Function

versão C#:

public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, Boolean> predicate);

public static IEnumerable<TSource> Where<TSource>( this IEnumerable<TSource> source, Func<TSource, Int32, Boolean> predicate);

Temos duas assinaturas disponíveis para o operador Where , o código usado utiliza a primeira, a qual enumera itens de fonte e retorna os itens que verificam o critério (c.Pais = Paises.USA);

A segunda assinatura aceita um parâmetro adicional do tipo Int32 o qual pode ser usado como índice de elementos na fonte. Dessa forma podemos usar o índice do parâmetro para realizar o filtro por um índice particular e a consulta ficaria assim expressa:

Dim consulta = clientes.Where(Function(c, index) (c.Pais = Paises.USA AndAlso index >= 1)).Select(Function(c) c.Nome)

A seguir temos o código usando a consulta acima:

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        Dim consulta = clientes.Where(Function(c, index) (c.Pais = Paises.USA AndAlso index >= 1)).Select(Function(c) c.Nome)

        For Each cliente In consulta
            lstResultado.Items.Add(cliente)
        Next
    End Sub

O resultado exibido será o nome dos clientes do pais USA:

Usando o operador de projeção

Os operadores de projeção são usados para selecionar (ou projetar) o conteúdo de uma fonte enumerada em um resultado.

O operador Select é um operador de projeção pois ele projeta um resultado da consulta tornando-a disponível através de um objeto que implementa IEnumerable<T>.

Este objeto irá enumerar itens identificados pelo seletor de critério. Assim para exemplificar temos:

Dim consulta = clientes.Select(Function(c) c.Nome)

Onde o resultado é uma sequência de nomes de clientes (IEnumerable<String>).

Dim consulta = clients.Select(Function(c) New With { c.Nome, c.Cidade })

O resultado da consulta acima projeta uma sequência de instâncias de um tipo anônimo, definido como uma tupla de Nome e Cidade para cada objeto cliente.

- Select x SelectMany

Agora imagine que você deseja obter todos os pedidos dos clientes do Brasil.

Podemos usar as seguintes consultas:

Dim pedidos = clientes.Where(Function(c) c.Pais = Paises.Brasil).Select(Function(c) c.Pedidos)

For Each pedido In pedidos
   
 lstResultado.Items.Add(pedido)
Next

Na consulta acima devido ao comportamento do operador Select o tipo resultante da consulta será um IEnumeralbe<Pedido[])
onde cada item na sequencia resultante representa um array de pedidos de um único cliente. A saida exibida ser: Pedido[]Array
Dim pedidos = clientes.Where(Function(c) c.Pais = Paises.Brasil).SelectMany(Function(c) c.Pedidos)

For Each pedido In pedidos
   lstResultado2.Items.Add(pedido.PedidoID & "-" & pedido.Quantidade & "-" & pedido.Mes)
Next

O operador SelectMany enumera a sequência fonte e mescla os itens resultantes exibindo-os como uma única sequência enumerável. A saída exibida serão os itens enumerados.

A consulta usando SelectMany poderia ser feita também da seguinte forma:

Dim pedidos2 = From c In clientes Where c.Pais = Paises.Brasil From o In c.Pedidos Select o

O resultado final seria o mesmo de onde concluímos que:

A palavra-chave Select em expressões de consultas é transformada para invocação de SelectMany para todas as cláusulas from, exceto a cláusula inicial.

Eu sei é apenas LINQ , mas eu gosto...

Referências:

José Carlos Macoratti