Certamente, uma das features mais interessantes e legais que o .NET e seu ecossistema oferece é o LINQ. LINQ é um acrônimo para Language Integrated Query, ou Consulta Integrada à Linguagem . Trata-se de um “framework” dentro do .NET destinado a auxiliar os desenvolvedores a escrever expressões de consulta diretamente em C# de maneira agnóstica.
Curso C# (C Sharp) Básico
Conhecer o cursoMas o que exatamente é o LINQ?
Para entender melhor: pense nas seguintes hipóteses dentro de um software:
- Se o software em questão precisar realizar consultas em um banco de dados relacional, provavelmente será necessário escrever consultas SQL;
- Se o software em questão precisar realizar consultas em um arquivo XML, provavelmente será necessário utilizar expressões XPath e/ou XQuery;
- Se o software em questão precisar realizar consultas em coleções de objetos, provavelmente será necessário utilizar blocos de código com a linguagem na qual o software está sendo desenvolvido.
Essa complexidade é adicionada pelo fato de estarmos tratando de fontes de dados heterogêneas de dados, cada qual com suas linguagens de manipulação específicas. Ou seja: se seu software se conecta a uma base de dados relacional e manipula objetos, você provavelmente precisará desenvolver e dar manutenção em pelo menos três linguagens diferentes: SQL, XPath (ou XQuery) e a linguagem utilizada para desenvolver a aplicação em si. O LINQ tem como objetivo justamente remover essa complexidade, pois ele abstrai a complexidade envolvida na utilização de diferentes linguagens de consulta, como SQL, xPath e xQuery. Essa abstração é feita em cima de uma API de alto nível compatível com as linguagens integrantes do .NET Framework. Ou seja: você consegue consultar uma base de dados relacional, um arquivo XML uma coleção de objetos através de uma API unificada, invocada através de uma linguagem integrante do .NET Framework. Trazendo para um exemplo mais palpável: você consegue unicamente com código C# fazer consultas a conjuntos de objetos, bases de dados relacionais e arquivos XML, sendo o LINQ o encarregado de fazer a devida “tradução” para cada uma das fontes a serem consultadas.
Providers LINQ e árvores de expressão (expression trees)
O LINQ consegue trabalhar de maneira agnóstica (ou seja, a mesma API do LINQ consegue trabalhar em cima de várias fontes de dados diferentes) principalmente por causa de dois pilares: os providers e as árvores de expressão - ou expression trees. Para entendermos cada um deles, vamos a partir deste ponto adotar o C# como a linguagem de referência.
Curso C# (C Sharp) Intermediário
Conhecer o cursoDe maneira geral e simplificada, uma expression tree é um trecho de código organizado como uma árvore onde cada nó pode corresponder a um valor, uma chamada de método ou até mesmo um operador aritmético ou lógico. No .NET, as árvores de expressão são definidas pela classe Expression
, advinda do namespace System.Linq.Expressions
.
Vamos montar uma árvore de expressão simples: uma árvore que retorna true
caso receba um número menor que 5 ou retorna false
no caso contrário. Para isso, precisamos criar um delegate que seja capaz de realizar essa análise… No .NET, existem dois tipos de delegate especializados e já disponibilizados pela API do LINQ:
-
Func
: é um delegate que indica que um valor vai ser retornado. Delegates do tipoFunc
podem não receber parâmetros, mas sempre tem que prover algum retorno; -
Action
: é um delegate que indica que nenhum valor será retornado. Delegates do tipoAction
podem não receber parâmetros e nunca irão retornar um valor.
Se verificarmos a situação acima, veremos que um delegate do tipo Func
com um parâmetro de entrada do tipo int
e uma saída do tipo boolean
é o mais adequado. Sendo assim, utilizando expressões-lambda, podemos escrever esta definição:
Func<int, bool> expr = num => num < 5;
Para que este delegate se torne uma árvore de expressão de verdade, é necessário que ele seja envolvido pela classe Expression
:
Expression<Func<int, bool>> expr = num => num < 5;
Agora, temos uma árvore de expressão completa com três ramificações:
- O argumento do tipo
int
de entrada; - O operador de comparação
>
, revelando que se trata de uma árvore de expressão binária; - O
5
, um valor constante que deve ser utilizado para comparação.
Qualquer expressão LINQ é decomposta nestas árvores de expressão. Neste momento, entra em cena os providers LINQ. Os providers LINQ são utilizados basicamente para fazer a tradução das árvores de expressão para cada uma das fontes de dados na qual a expressão LINQ está conectada. Os principais providers LINQ são:
- LINQ to objects: utilizado quando a fonte de consulta é um conjunto de objetos;
- LINQ to XML: utilizado quando a fonte de consulta é um XML;
- LINQ to SQL: utilizado quando a fonte de consulta é um banco de dados relacional.
As principais estruturas dos providers LINQ são as interfaces IQueryProvider
e IQueryable
. Todos os providers LINQ e suas variações são obrigados a implementar estas interfaces.
Através das implementações dos providers LINQ, as expressões de consulta podem ser traduzidas para as suas respectivas fontes de dados… Por exemplo: vamos considerar a árvore de expressão anterior (Expression expr = num => num < 5
) com o número 4
como entrada.
Se estivéssemos falando de LINQ to Objects, cada nó que faz parte da expressão seria traduzido para o código C# correspondente. Teríamos o resultado abaixo:
Se estivéssemos falando de LINQ to SQL, cada nó que faz parte da mesma expressão seria traduzido para o código SQL correspondente. Teríamos o resultado abaixo:
Já se estivéssemos falando de LINQ to XML, cada nó que faz parte da mesma expressão seria traduzido para o código xPath/xQuery correspondente. Teríamos o resultado abaixo:
Veja que o LINQ, de acordo com a fonte de dados que está sendo analisada, utiliza o provider para realizar uma “tradução” da API de alto nível em C# para o código correspondente ao tipo da fonte de dados que está sendo utilizada. E esse processo torna a API completamente independente da fonte de dados a qual esta se conecta: quem fica responsável por dizer ao LINQ como cada nó da árvore de expressão tem que ser traduzido é o provider.
Curso C# (C Sharp) Avançado
Conhecer o cursoExistem muito mais coisas do LINQ a serem exploradas!
O LINQ é de fato uma ferramenta muito poderosa quando utilizada corretamente (especialmente quando estamos falando do LINQ to SQL). Este primeiro post na verdade tem a intenção de mostrar os princípios básicos e o funcionamento interno do LINQ. Nos próximos posts, iremos começar a analisar alguns pontos interessantes para quem utiliza o LINQ no dia-a-dia.