Introdução às consultas LINQ no C#

Uma consulta é uma expressão que recupera dados de uma fonte de dados. Diferentes fontes de dados têm diferentes linguagens de consulta nativas, por exemplo, SQL para bancos de dados relacionais e XQuery para XML. Os desenvolvedores devem aprender uma nova linguagem de consulta para cada tipo de fonte de dados ou formato de dados para o qual eles devem dar suporte. O LINQ simplifica essa situação, oferecendo um modelo de linguagem C# consistente para tipos de fontes de dados e formatos. Em uma consulta LINQ, você sempre trabalha com objetos C#. Use os mesmos padrões básicos de codificação para consultar e transformar dados em documentos XML, bancos de dados SQL, coleções .NET e qualquer outro formato quando um provedor LINQ estiver disponível.

Três Partes de uma Operação de Consulta

Todos as operações de consulta LINQ consistem em três ações distintas:

  1. Obter a fonte de dados.
  2. Criar a consulta.
  3. Executar a consulta.

O exemplo a seguir mostra como as três partes de uma operação de consulta são expressas em código-fonte. O exemplo usa uma matriz de inteiros como uma fonte de dados para sua conveniência. No entanto, os mesmos conceitos também se aplicam a outras fontes de dados. Este exemplo é referenciado em todo o restante deste artigo.

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

// 3. Query execution.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

A ilustração a seguir mostra a operação de consulta completa. No LINQ, a execução da consulta é distinta da própria consulta. Em outras palavras, você não recupera dados criando uma variável de consulta.

Diagrama da operação completa de consulta LINQ.

A Fonte de Dados

A fonte de dados no exemplo anterior é uma matriz, que dá suporte à interface genérica IEnumerable<T>. Isso significa que ela pode ser consultada com o LINQ. Uma consulta é executada em uma instrução foreach, e foreach requer IEnumerable ou IEnumerable<T>. Tipos que dão suporte a IEnumerable<T> ou uma interface derivada, como a genérica IQueryable<T>, são chamados tipos passíveis de consulta.

Um tipo passível de consulta não exige modificação ou tratamento especial para servir como uma fonte de dados do LINQ. Se os dados de origem ainda não estiverem na memória como um tipo queryable, o provedor LINQ deverá representá-los como tal. Por exemplo, LINQ to XML carrega um documento XML em um tipo XElement passível de consulta:

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

Com o EntityFramework, você cria um mapeamento relacional de objeto entre classes C# e seu esquema de banco de dados. Você grava suas consultas nos objetos e, em tempo de execução, o EntityFramework manipula a comunicação com o banco de dados. No exemplo a seguir, Customers representa uma tabela específica no banco de dados, e o tipo do resultado da consulta, IQueryable<T>, deriva de IEnumerable<T>.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

Para obter mais informações sobre como criar tipos específicos de fontes de dados, consulte a documentação para os diversos provedores LINQ. No entanto, a regra básica é simples: uma fonte de dados LINQ é qualquer objeto que dê suporte à interface genérica IEnumerable<T> ou uma interface que herde dela, normalmente IQueryable<T>.

Observação

Tipos como ArrayList, que dão suporte à interface IEnumerable não genérica, também podem ser usados como uma fonte de dados LINQ. Para obter mais informações, consulte Como consultar um ArrayList com LINQ (C#).

A consulta

A consulta especifica quais informações devem ser recuperadas da fonte (ou fontes) de dados. Opcionalmente, uma consulta também especifica como essas informações devem ser classificadas, agrupadas e moldadas antes de serem retornadas. Uma consulta é armazenada em uma variável de consulta e é inicializada com uma expressão de consulta. Use a sintaxe de consulta em C# para gravar consultas.

A consulta no exemplo anterior retorna todos os números pares da matriz de inteiros. A expressão de consulta contém três cláusulas: from, where e select. (Se você está familiarizado com o SQL, deve ter notado que a ordenação das cláusulas é ao contrário da ordenação no SQL.) A cláusula from especifica a fonte de dados, a cláusula where aplica o filtro e a cláusula select especifica o tipo dos elementos retornados. Todas as cláusulas de consulta são discutidas em detalhes nesta seção. Por enquanto, o ponto importante é que no LINQ a variável de consulta não faz nada e não retorna nenhum dado. Ele apenas armazena as informações necessárias para produzir os resultados quando a consulta for executada em um momento posterior. Para obter mais informações sobre como as consultas são construídas, confira Visão geral dos operadores de consulta padrão (C#).

Observação

As consultas também podem ser expressas usando a sintaxe de método. Para obter mais informações, consulte Sintaxe de consulta e sintaxe de método em LINQ.

Classificação de operadores de consulta padrão por maneira de execução

As implementações dos métodos de operador de consulta padrão no LINQ to Objects são executadas de duas maneiras principais: imediata ou adiada. Os operadores de consulta que usam a execução adiada podem, adicionalmente, ser divididos em duas categorias: com fluxo de dados e sem fluxo de dados.

Imediata

A execução imediata significa que a fonte de dados é lida e a operação é executada uma vez. Todos os operadores de consulta padrão que retornam um resultado escalar são executados imediatamente. Exemplos dessas consultas são Count, Max, Average e First. Esses métodos são executados sem uma instrução explícita foreach porque a consulta em si deve usar foreach para retornar um resultado. Essas consultas retornam um único valor, não uma coleção IEnumerable. Você pode forçar a execução imediata de qualquer consulta usando os métodos Enumerable.ToList ou Enumerable.ToArray. A execução imediata fornece reutilização dos resultados da consulta, não uma declaração de consulta. Os resultados são recuperados uma vez e armazenados para uso futuro. A consulta a seguir retorna uma contagem de números pares na matriz de origem:

var evenNumQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

Para forçar a execução imediata de qualquer consulta e armazenar seus resultados em cache, você pode chamar os métodos ToList ou ToArray.

List<int> numQuery2 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToArray();

Você também pode forçar a execução colocando o loop foreach imediatamente após a expressão de consulta. No entanto, ao chamar ToList ou ToArray, você também armazena em cache todos os dados em um único objeto de coleção.

Adiado

Uma execução adiada significa que a operação não é realizada no ponto do código em que a consulta está declarada. A operação será realizada somente quando a variável de consulta for enumerada, por exemplo, usando uma instrução foreach. Os resultados da execução da consulta dependem do conteúdo da fonte de dados no momento em que a consulta for executada, não no momento em que a consulta for definida. Se a variável de consulta for enumerada várias vezes, os resultados poderão ser diferentes a cada vez. Quase todos os operadores de consulta padrão cujo tipo de retorno é IEnumerable<T> ou IOrderedEnumerable<TElement> executam de maneira adiada. A execução adiada oferece a facilidade da reutilização da consulta, uma vez que ela busca os dados atualizados da fonte de dados sempre que os seus resultados são iterados. O código a seguir mostra um exemplo de execução adiada:

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

A instrução foreach também é o local em que os resultados da consulta são recuperados. Por exemplo, na consulta anterior, a variável de iteração num armazena cada valor (um de cada vez) na sequência retornada.

Como a variável de consulta em si nunca contém os resultados da consulta, você pode executá-la repetidamente para recuperar dados atualizados. Por exemplo, um aplicativo separado talvez atualize um banco de dados continuamente. No seu aplicativo, você poderia criar uma consulta que recuperasse os dados mais recentes e executá-la a intervalos determinados para recuperar os resultados atualizados.

Os operadores de consulta que usam a execução adiada podem, adicionalmente, ser classificados como "com fluxo de dados" e "sem fluxo de dados".

Streaming

Os operadores com fluxos de dados não precisam ler todos os dados da origem antes de gerar elementos. No momento da execução, um operador streaming realiza sua operação em cada elemento de origem enquanto eles são lidos, gerando o elemento, se apropriado. Um operador streaming continua a ler os elementos de origem até que um elemento de resultado possa ser produzido. Isso significa que mais de um elemento de origem poderá ser lido para produzir um elemento de resultado.

Sem fluxo de dados

Os operadores sem fluxo de dados precisam ler todos os dados da origem antes de gerar um elemento resultante. Operações como classificação ou agrupamento se enquadram nesta categoria. No momento da execução, os operadores de consulta sem fluxo de dados leem todos os dados da origem, os colocam em uma estrutura de dados, executam a operação e geram os elementos resultantes.

Tabela de classificação

A tabela a seguir classifica cada método de operador de consulta padrão de acordo com o respectivo método de execução.

Observação

Se um operador estiver marcado em duas colunas, duas sequências de entrada estarão envolvidas na operação e cada sequência será avaliada de forma diferente. Nesses casos, a primeira sequência na lista de parâmetros é a que sempre será avaliada de maneira adiada e em modo streaming.

Operador de consulta padrão Tipo de retorno Execução imediata Execução adiada de streaming Execução sem fluxo de dados adiada
Aggregate TSource X
All Boolean X
Any Boolean X
AsEnumerable IEnumerable<T> X
Average Valor numérico único X
Cast IEnumerable<T> X
Concat IEnumerable<T> X
Contains Boolean X
Count Int32 X
DefaultIfEmpty IEnumerable<T> X
Distinct IEnumerable<T> X
ElementAt TSource X
ElementAtOrDefault TSource? X
Empty IEnumerable<T> X
Except IEnumerable<T> X X
First TSource X
FirstOrDefault TSource? X
GroupBy IEnumerable<T> X
GroupJoin IEnumerable<T> X X
Intersect IEnumerable<T> X X
Join IEnumerable<T> X X
Last TSource X
LastOrDefault TSource? X
LongCount Int64 X
Max Valor numérico único, TSource ou TResult? X
Min Valor numérico único, TSource ou TResult? X
OfType IEnumerable<T> X
OrderBy IOrderedEnumerable<TElement> X
OrderByDescending IOrderedEnumerable<TElement> X
Range IEnumerable<T> X
Repeat IEnumerable<T> X
Reverse IEnumerable<T> X
Select IEnumerable<T> X
SelectMany IEnumerable<T> X
SequenceEqual Boolean X
Single TSource X
SingleOrDefault TSource? X
Skip IEnumerable<T> X
SkipWhile IEnumerable<T> X
Sum Valor numérico único X
Take IEnumerable<T> X
TakeWhile IEnumerable<T> X
ThenBy IOrderedEnumerable<TElement> X
ThenByDescending IOrderedEnumerable<TElement> X
ToArray Matriz TSource[] X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X

LINQ to objects

"LINQ to Objects" se refere ao uso de consultas LINQ com qualquer coleta IEnumerable ou IEnumerable<T> diretamente. Você pode usar o LINQ para consultar qualquer coleção enumerável, ​​como List<T>, Array ou Dictionary<TKey,TValue>. A coleção pode ser definida pelo usuário ou um tipo retornado por uma API do .NET. Na abordagem da LINQ, você escreve o código declarativo que descreve o que você deseja recuperar. O recurso LINQ to Objects fornece uma ótima introdução à programação com o LINQ.

As consultas LINQ oferecem três vantagens principais sobre os loops foreach tradicionais:

  • São mais concisas e mais legíveis, especialmente quando você filtra várias condições.
  • Elas fornecem poderosos recursos de filtragem, ordenação e agrupamento com um mínimo de código do aplicativo.
  • Elas podem ser movidas para outras fontes de dados com pouca ou nenhuma modificação.

Quanto mais complexa for a operação que você quiser executar nos dados, maior será a vantagem obtida ao usar o LINQ em vez de técnicas de iteração tradicionais.

Armazenar os resultados de uma consulta na memória

Uma consulta é basicamente um conjunto de instruções sobre como recuperar e organizar os dados. As consultas são executadas lentamente, conforme cada item subsequente no resultado é solicitado. Quando você usa foreach para iterar os resultados, os itens são retornados conforme acessado. Para avaliar uma consulta e armazenar os resultados sem executar um loop foreach, basta chamar um dos métodos a seguir na variável de consulta:

Ao armazenar os resultados da sua consulta, você deve atribuir o objeto da coleta retornado a uma nova variável, conforme mostrado no exemplo a seguir:

List<int> numbers = [1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20];

IEnumerable<int> queryFactorsOfFour =
    from num in numbers
    where num % 4 == 0
    select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

Confira também