Compartilhar via


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 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

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. Executar 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 de inteiros como uma fonte de dados para 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 consulta em si. Em outras palavras, você não recupera dados 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 dá suporte à interface genérica IEnumerable<T> . Esse fato significa que ele pode ser consultado com LINQ. Uma consulta é executada em uma foreach instrução e foreach requer IEnumerable ou IEnumerable<T>. Tipos que dão suporte IEnumerable<T> ou a uma interface derivada, como o genérico IQueryable<T> , são chamados de tipos queriráveis.

Um tipo que pode ser consultado 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 queryable, o provedor LINQ deverá representá-los como tal. Por exemplo, LINQ to XML carrega um documento XML em um tipo que pode ser objeto de consulta 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 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 dos vários 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 herda dela, normalmente IQueryable<T>.

Observação

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

A consulta

A consulta especifica quais informações recuperar da fonte de dados ou das fontes. 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 par da matriz de inteiros. A expressão de consulta contém três cláusulas: from, wheree select. (Se você estiver familiarizado com o SQL, notou que a ordenação das cláusulas é revertida da ordem no 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 toma 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#).

Observação

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 de operadores de consulta padrão por meio de execução

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

Imediato

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, Maxe AverageFirst. Esses métodos são executados sem uma instrução explícita foreach porque a consulta em si deve ser usada foreach para retornar um resultado. Essas consultas retornam um único valor, não uma coleção IEnumerable . Você pode forçar qualquer consulta a ser executada imediatamente usando os métodos Enumerable.ToList ou Enumerable.ToArray. A execução imediata fornece reutilização dos resultados da consulta, não da declaração de 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 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 foreach loop imediatamente após a expressão de consulta. No entanto, ao chamar ToList ou ToArray, você também armazena 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 no 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 em vez de quando a consulta é definida. Se a variável de consulta for enumerada várias vezes, os resultados poderão ser diferentes sempre. Quase todos os operadores de consulta padrão cujo tipo de retorno é IEnumerable<T> ou IOrderedEnumerable<TElement> são executados de forma adiada. A execução adiada fornece a facilidade 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 declaração também é onde os resultados da consulta são obtidos. Por exemplo, na consulta anterior, a variável num de iteração conté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á-los em intervalos 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".

Transmissão ao vivo

Os operadores de streaming não precisam ler todos os dados de origem antes de produzirem elementos. No momento da execução, um operador de streaming executa sua operação em cada elemento de origem conforme ele é lido e produz o elemento, se apropriado. Um operador de streaming continua a ler 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.

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 nessa categoria. No momento da execução, os operadores de consulta que não são de fluxo leem todos os dados de origem, os colocam 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.

Observação

Se um operador for 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, é sempre a primeira sequência na lista de parâmetros que é avaliada de maneira adiada e de 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
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average Valor numérico único
Cast IEnumerable<T>
Concat IEnumerable<T>
Contains Boolean
Count Int32
DefaultIfEmpty IEnumerable<T>
Distinct IEnumerable<T>
ElementAt TSource
ElementAtOrDefault TSource?
Empty IEnumerable<T>
Except IEnumerable<T>
First TSource
FirstOrDefault TSource?
GroupBy IEnumerable<T>
GroupJoin IEnumerable<T>
Intersect IEnumerable<T>
Join IEnumerable<T>
Last TSource
LastOrDefault TSource?
LongCount Int64
Max Valor numérico único, TSourceou TResult?
Min Valor numérico único, TSourceou TResult?
OfType IEnumerable<T>
OrderBy IOrderedEnumerable<TElement>
OrderByDescending IOrderedEnumerable<TElement>
Range IEnumerable<T>
Repeat IEnumerable<T>
Reverse IEnumerable<T>
Select IEnumerable<T>
SelectMany IEnumerable<T>
SequenceEqual Boolean
Single TSource
SingleOrDefault TSource?
Skip IEnumerable<T>
SkipWhile IEnumerable<T>
Sum Valor numérico único
Take IEnumerable<T>
TakeWhile IEnumerable<T>
ThenBy IOrderedEnumerable<TElement>
ThenByDescending IOrderedEnumerable<TElement>
ToArray TSource[] matriz
ToDictionary Dictionary<TKey,TValue>
ToList IList<T>
ToLookup ILookup<TKey,TElement>
Union IEnumerable<T>
Where IEnumerable<T>

LINQ para objetos

"LINQ to Objects" refere-se ao uso de consultas LINQ diretamente com qualquer coleção representada por IEnumerable ou IEnumerable<T>. Você pode usar 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 do .NET. Na abordagem LINQ, você escreve um código declarativo que descreve o que deseja recuperar. LINQ to Objects fornece uma ótima introdução à programação com 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 funcionalidades avançadas 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 a operação que você deseja executar nos dados, mais benefício você percebe usando LINQ em vez de 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 de forma preguiçosa, pois cada item subsequente no resultado é solicitado. Quando você usa foreach para iterar os resultados, os itens são retornados na ordem de acesso. 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