Introdução às consultas LINQ em 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 que eles devem suportar. 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#. Você usa 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 está disponível.

Três partes de uma operação de consulta

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

  1. Obtenha a fonte de dados.
  2. Crie a consulta.
  3. Execute a consulta.

O exemplo a seguir mostra como as três partes de uma operação de consulta são expressas no código-fonte. O exemplo usa uma matriz inteira como fonte de dados por conveniência; no entanto, os mesmos conceitos também se aplicam a outras fontes de dados. Este exemplo é referido ao longo do resto 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 consulta em si. Em outras palavras, você não recupera nenhum dado criando uma variável de consulta.

Diagrama da operação de consulta LINQ completa.

A fonte de dados

A fonte de dados no exemplo anterior é uma matriz, que suporta a interface genérica IEnumerable<T> . Este fato significa que ele pode ser consultado com o LINQ. Uma consulta é executada em uma foreach instrução e foreach requer IEnumerable ou IEnumerable<T>. Os tipos que suportam IEnumerable<T> ou uma interface derivada, como o genérico, IQueryable<T> são chamados de tipos consultáveis.

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

// 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 objeto-relacional entre classes C# e seu esquema de banco de dados. Você escreve suas consultas nos objetos e, em tempo de execução, o EntityFramework lida com 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 vários provedores LINQ. No entanto, a regra básica é simples: uma fonte de dados LINQ é qualquer objeto que suporta a interface genérica IEnumerable<T> , ou uma interface que herda dela, normalmente IQueryable<T>.

Nota

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

A Consulta

A consulta especifica quais informações recuperar da(s) fonte(s) 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. Você usa a sintaxe de consulta C# para escrever consultas.

A consulta no exemplo anterior retorna todos os números pares da matriz inteira. A expressão de consulta contém três cláusulas: from, wheree select. (Se você estiver familiarizado com SQL, notou que a ordem das cláusulas é invertida da ordem em SQL.) A from cláusula especifica a fonte de dados, a where cláusula aplica o filtro e a select cláusula 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 em si não realiza nenhuma ação e não retorna dados. Ele apenas armazena as informações necessárias para produzir os resultados quando a consulta é executada em algum momento posterior. Para obter mais informações sobre como as consultas são construídas, consulte Visão geral dos operadores de consulta padrão (C#).

Nota

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

Classificação dos operadores de consulta padrão por modo de execução

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

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 de tais consultas são Count, Max, Average, e First. Esses métodos são executados sem uma instrução explícita foreach porque a própria consulta deve ser usada foreach para retornar um resultado. Essas consultas retornam um único valor, não uma IEnumerable coleção. Você pode forçar qualquer consulta a ser executada imediatamente usando os Enumerable.ToList métodos ou Enumerable.ToArray . A execução imediata fornece reutilização dos resultados da consulta, não da declaração da consulta. Os resultados são recuperados uma vez e, em seguida, armazenados para uso futuro. A consulta a seguir retorna uma contagem dos 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 em cache seus resultados, você pode chamar os ToList métodos 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 foreach loop imediatamente após a expressão de consulta. No entanto, chamando ToList ou ToArray você também armazena em cache todos os dados em um único objeto de coleção.

Adiado

A execução adiada significa que a operação não é executada no ponto do código em que a consulta é declarada. A operação é executada somente quando a variável de consulta é enumerada, por exemplo, usando uma foreach instrução. Os resultados da execução da consulta dependem do conteúdo da fonte de dados quando a consulta é executada e não quando a consulta é definida. Se a variável de consulta for enumerada várias vezes, os resultados poderão diferir sempre. Quase todos os operadores de consulta padrão cujo tipo de retorno é IEnumerable<T> ou IOrderedEnumerable<TElement> são executados de maneira diferida. A execução adiada fornece o recurso de reutilização da consulta, uma vez que a consulta busca os dados atualizados da fonte de dados sempre que os resultados da consulta 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 foreach instrução também é onde os resultados da consulta são recuperados. Por exemplo, na consulta anterior, a variável num de iteração mantém 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 pode atualizar um banco de dados continuamente. Em seu aplicativo, você pode criar uma consulta que recupera os dados mais recentes e pode executá-la em intervalos para recuperar resultados atualizados.

Os operadores de consulta que usam execução adiada podem ser adicionalmente classificados como streaming ou nonstreaming.

Transmissão

As operadoras de streaming não precisam ler todos os dados de origem antes de produzir elementos. No momento da execução, um operador de streaming executa sua operação em cada elemento de origem à medida que é lido e produz o elemento se apropriado. Um operador de 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 pode ser lido para produzir um elemento de resultado.

Não transmissão em fluxo

Os operadores que não fazem streaming devem ler todos os dados de origem antes de produzirem um elemento de resultado. Operações como classificação ou agrupamento se enquadram nessa categoria. No momento da execução, os operadores de consulta que não fazem streaming leem todos os dados de origem, colocam-nos em uma estrutura de dados, executam a operação e produzem os elementos resultantes.

Tabela de classificação

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

Nota

Se um operador é marcado em duas colunas, duas sequências de entrada estão envolvidas na operação, e cada sequência é avaliada de forma diferente. Nestes casos, é sempre a primeira sequência na lista de parâmetros que é avaliada de forma diferida e em streaming.

Operador de consulta padrão Tipo de retorno Execução imediata Execução de streaming adiada Execução não diferida de não streaming
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, TSourceou TResult? X
Min Valor numérico único, TSourceou 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 TSource[] matriz X
ToDictionary Dictionary<TKey,TValue> X
ToList IList<T> X
ToLookup ILookup<TKey,TElement> X
Union IEnumerable<T> X
Where IEnumerable<T> X

LINQ para objetos

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

As consultas LINQ oferecem três vantagens principais em relação aos loops tradicionais foreach :

  • Eles são mais concisos e legíveis, especialmente ao filtrar várias condições.
  • Eles fornecem recursos poderosos de filtragem, ordenação e agrupamento com um mínimo de código de aplicativo.
  • Eles podem ser portados para outras fontes de dados com pouca ou nenhuma modificação.

Quanto mais complexa for a operação que você deseja executar nos dados, mais benefícios você obterá usando o LINQ em vez das técnicas tradicionais de iteração.

Armazenar os resultados de uma consulta na memória

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

Você deve atribuir o objeto de coleção retornado a uma nova variável ao armazenar os resultados da consulta, 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]);

Consulte também