Gravar consultas LINQ em C# para consultar dados

A maioria das consultas na documentação introdutória do LINQ (Consulta Integrada à Linguagem) é escrita com o uso da sintaxe de consulta declarativa do LINQ. No entanto, a sintaxe de consulta deve ser convertida em chamadas de método para o CLR (Common Language Runtime) do .NET quando o código for compilado. Essas chamadas de método invocam os operadores de consulta padrão, que têm nomes como Where, Select, GroupBy, Join, Max e Average. Você pode chamá-los diretamente usando a sintaxe de método em vez da sintaxe de consulta.

A sintaxe de consulta e a sintaxe do método são semanticamente idênticas, mas a sintaxe de consulta geralmente é mais simples e fácil de ler. Algumas consultas devem ser expressadas como chamadas de método. Por exemplo, você deve usar uma chamada de método para expressar uma consulta que recupera o número de elementos que correspondem a uma condição especificada. Você também deve usar uma chamada de método para uma consulta que recupera o elemento que tem o valor máximo em uma sequência de origem. A documentação de referência para os operadores de consulta padrão no namespace System.Linq geralmente usa a sintaxe de método. Você deve se familiarizar com como usar a sintaxe do método em consultas e nas próprias expressões de consulta.

Métodos de extensão do operador de consulta padrão

O exemplo a seguir mostra uma expressão de consulta simples e a consulta semanticamente equivalente escrita como uma consulta baseada em método.

int[] numbers = [ 5, 10, 8, 3, 6, 12 ];

//Query syntax:
IEnumerable<int> numQuery1 =
    from num in numbers
    where num % 2 == 0
    orderby num
    select num;

//Method syntax:
IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n);

foreach (int i in numQuery1)
{
    Console.Write(i + " ");
}
Console.WriteLine(System.Environment.NewLine);
foreach (int i in numQuery2)
{
    Console.Write(i + " ");
}

A saída dos dois exemplos é idêntica. Você pode ver que o tipo da variável de consulta é o mesmo em ambas as formas: IEnumerable<T>.

Para entender a consulta baseada em método, vamos examiná-la melhor. No lado direito da expressão, observe que a cláusula where agora é expressa como um método de instância no objeto numbers, que tem um tipo de IEnumerable<int>. Se você estiver familiarizado com a interface genérica IEnumerable<T>, você saberá que ela não possui um método Where. No entanto, se você invocar a lista de conclusão do IntelliSense no IDE do Visual Studio, verá não apenas um método Where, mas muitos outros métodos, como Select, SelectMany, Join e Orderby. Esses métodos implementam os operadores de consulta padrão.

Screenshot showing all the standard query operators in Intellisense.

Embora pareça que IEnumerable<T> inclui métodos adicionais, ele não inclui. Os operadores de consulta padrão são implementados como métodos de extensão. Métodos de extensão "estendem" um tipo existente, eles podem ser chamados como se fossem métodos de instância no tipo. Os operadores de consulta padrão estendem IEnumerable<T> e que é por esse motivo que você pode escrever numbers.Where(...).

Para usar métodos de extensão, você os coloca no escopo com diretivas using. Do ponto de vista do aplicativo, um método de extensão e um método de instância normal são iguais.

Para obter mais informações sobre os métodos de extensão, consulte Métodos de extensão. Para obter mais informações sobre os operadores de consulta padrão, consulte Visão geral de operadores de consulta padrão (C#). Alguns provedores LINQ, como Entity Framework e LINQ to XML, implementam seus próprios operadores de consulta padrão e métodos de extensão para outros tipos além de IEnumerable<T>.

Expressões lambda

No exemplo anterior, observe que a expressão condicional (num % 2 == 0) é passada como um argumento em linha para o método Enumerable.Where: Where(num => num % 2 == 0). essa expressão embutida é uma expressão lambda. É uma maneira conveniente de escrever código que, de outra forma, teria que ser escrito de forma mais complicada. O num à esquerda do operador é a variável de entrada, que corresponde a num na expressão de consulta. O compilador pode inferir o tipo de num porque ele sabe que numbers é um tipo IEnumerable<T> genérico. O corpo do lambda é igual à expressão na sintaxe da consulta ou em qualquer outra expressão ou instrução C#. Ele pode incluir chamadas de método e outras lógicas complexas. O valor retornado é apenas o resultado da expressão. Determinadas consultas só podem ser expressas na sintaxe do método e algumas delas exigem expressões lambda. As expressões lambda são uma ferramenta poderosa e flexível em sua caixa de ferramentas LINQ.

Possibilidade de composição das consultas

No exemplo de código anterior, o método Enumerable.OrderBy é invocado usando o operador de ponto na chamada para Where. Where produz uma sequência filtrada e, em seguida, Orderby classifica a sequência produzida por Where. Como as consultas retornam uma IEnumerable, você pode escrevê-las na sintaxe de método encadeando as chamadas de método. O compilador faz essa composição quando você escreve consultas usando a sintaxe de consulta. Como uma variável de consulta não armazena os resultados da consulta, você pode modificá-la ou usá-la como base para uma nova consulta a qualquer momento, mesmo depois de executá-la.

Os exemplos a seguir demonstram algumas consultas LINQ simples usando cada abordagem listada anteriormente.

Observação

Essas consultas funcionam em coleções na memória simples, no entanto, a sintaxe básica é idêntica àquela usada no LINQ to Entities e no LINQ to XML.

Exemplo – sintaxe de consulta

Você escreve a maioria das consultas com sintaxe de consulta para criar expressões de consulta. O exemplo a seguir mostra três expressões de consulta. A primeira expressão de consulta demonstra como filtrar ou restringir os resultados aplicando condições com uma cláusula where. Ela retorna todos os elementos na sequência de origem cujos valores são maiores que 7 ou menores que 3. A segunda expressão demonstra como ordenar os resultados retornados. A terceira expressão demonstra como agrupar resultados de acordo com uma chave. Esta consulta retorna dois grupos com base na primeira letra da palavra.

List<int> numbers = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];

// The query variables can also be implicitly typed by using var

// Query #1.
IEnumerable<int> filteringQuery =
    from num in numbers
    where num is < 3 or > 7
    select num;

// Query #2.
IEnumerable<int> orderingQuery =
    from num in numbers
    where num is < 3 or > 7
    orderby num ascending
    select num;

// Query #3.
string[] groupingQuery = ["carrots", "cabbage", "broccoli", "beans", "barley"];
IEnumerable<IGrouping<char, string>> queryFoodGroups =
    from item in groupingQuery
    group item by item[0];

O tipo das consultas é IEnumerable<T>. Todas essas consultas poderiam ser escritas usando var conforme mostrado no exemplo a seguir:

var query = from num in numbers...

Em cada exemplo anterior, as consultas não são realmente executadas até que você itere sobre a variável de consulta em uma instrução foreach ou outra instrução.

Exemplo – sintaxe de método

Algumas operações de consulta devem ser expressas como uma chamada de método. Os métodos mais comuns são aqueles que retornam valores numéricos singleton, como Sum, Max, Min, Average, e assim por diante. Esses métodos sempre devem ser chamados por último em qualquer consulta porque retornam um único valor e não podem servir como a origem de uma operação de consulta extra. O exemplo a seguir mostra uma chamada de método em uma expressão de consulta:

List<int> numbers1 = [5, 4, 1, 3, 9, 8, 6, 7, 2, 0];
List<int> numbers2 = [15, 14, 11, 13, 19, 18, 16, 17, 12, 10];

// Query #4.
double average = numbers1.Average();

// Query #5.
IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);

Se o método tiver parâmetros System.Action ou System.Func<TResult>, esses argumentos serão fornecidos na forma de uma expressão lambda, conforme mostrado no exemplo a seguir:

// Query #6.
IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);

Nas consultas anteriores, somente a Consulta nº 4 é executada imediatamente, pois retorna um único valor e não uma coleção genérica IEnumerable<T>. O método em si usa foreach ou código semelhante para calcular seu valor.

Cada uma das consultas anteriores pode ser escrita usando digitação implícita com `var``, conforme mostrado no exemplo a seguir:

// var is used for convenience in these queries
double average = numbers1.Average();
var concatenationQuery = numbers1.Concat(numbers2);
var largeNumbersQuery = numbers2.Where(c => c > 15);

Exemplo – sintaxe mista de consulta e do método

Este exemplo mostra como usar a sintaxe do método nos resultados de uma cláusula de consulta. Simplesmente coloque a expressão de consulta entre parênteses e, em seguida, aplique o operador de ponto e chame o método. No exemplo a seguir, a Query #7 retorna uma contagem dos números cujo valor está entre 3 e 7. Em geral, no entanto, é melhor usar uma segunda variável para armazenar o resultado da chamada de método. Dessa forma, é menos provável que a consulta seja confundida com os resultados da consulta.

// Query #7.

// Using a query expression with method syntax
var numCount1 = (
    from num in numbers1
    where num is > 3 and < 7
    select num
).Count();

// Better: Create a new variable to store
// the method call result
IEnumerable<int> numbersQuery =
    from num in numbers1
    where num is > 3 and < 7
    select num;

var numCount2 = numbersQuery.Count();

Como a Query #7 retorna um único valor e não uma coleção, a consulta é executada imediatamente.

A consulta anterior pode ser escrita usando a tipagem implícita com var, da seguinte maneira:

var numCount = (from num in numbers...

Ela pode ser escrita na sintaxe de método da seguinte maneira:

var numCount = numbers.Count(n => n is > 3 and < 7);

Ela pode ser escrita usando a tipagem explícita da seguinte maneira:

int numCount = numbers.Count(n => n is > 3 and < 7);

Confira também