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:
- Obtenha a fonte de dados.
- Crie a consulta.
- 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.
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
, where
e 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.
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]);