Noções básicas de expressão de consulta
Este artigo apresenta os conceitos básicos relacionados a expressões de consulta em C#.
O que é uma consulta e para que serve?
Um de consulta
Geralmente, os dados de origem são organizados logicamente como uma sequência de elementos do mesmo tipo. Por exemplo, uma tabela de banco de dados SQL contém uma sequência de linhas. Em um arquivo XML, há uma "sequência" de elementos XML (embora os elementos XML sejam organizados hierarquicamente em uma estrutura de árvore). Uma coleção na memória contém uma sequência de objetos.
Do ponto de vista de um aplicativo, o tipo e a estrutura específicos dos dados de origem originais não são importantes. A aplicação vê sempre os dados de origem como uma coleção IEnumerable<T> ou IQueryable<T>. Por exemplo, em LINQ to XML, os dados de origem são tornados visíveis como um IEnumerable
<XElement>.
Dada essa sequência de origem, uma consulta pode fazer uma de três coisas:
Recupere um subconjunto dos elementos para produzir uma nova sequência sem modificar os elementos individuais. A consulta pode então classificar ou agrupar a sequência retornada de várias maneiras, conforme mostrado no exemplo a seguir (suponha que
scores
é umint[]
):IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
Recupere uma sequência de elementos como no exemplo anterior, mas transforme-os em um novo tipo de objeto. Por exemplo, uma consulta pode recuperar apenas os nomes de família de determinados registros de clientes em uma fonte de dados. Ou ele pode recuperar o registro completo e, em seguida, usá-lo para construir outro tipo de objeto na memória ou até mesmo dados XML antes de gerar a sequência de resultado final. O exemplo a seguir mostra uma projeção de um
int
para umstring
. Observe o novo tipo dehighScoresQuery
.IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";
Recupere um valor único acerca dos dados de origem, como:
O número de elementos que correspondem a uma determinada condição.
O elemento que tem o maior ou menor valor.
O primeiro elemento que corresponde a uma condição ou a soma de valores particulares em um conjunto especificado de elementos. Por exemplo, a consulta a seguir retorna o número de pontuações maiores que 80 da matriz inteira
scores
:var highScoreCount = ( from score in scores where score > 80 select score ).Count();
No exemplo anterior, observe o uso de parênteses ao redor da expressão de consulta antes da chamada para o método Enumerable.Count. Você também pode usar uma nova variável para armazenar o resultado concreto.
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; var scoreCount = highScoresQuery3.Count();
No exemplo anterior, a consulta é executada na chamada para Count
, porque Count
deve iterar sobre os resultados para determinar o número de elementos retornados por highScoresQuery
.
O que é uma expressão de consulta?
Uma expressão de consulta é uma consulta expressa em sintaxe de consulta. Uma expressão de consulta é uma construção de linguagem de primeira classe. É como qualquer outra expressão e pode ser usada em qualquer contexto em que uma expressão C# seja válida. Uma expressão de consulta consiste em um conjunto de cláusulas escritas em uma sintaxe declarativa semelhante a SQL ou XQuery. Cada cláusula, por sua vez, contém uma ou mais expressões C#, e essas expressões podem ser uma expressão de consulta ou conter uma expressão de consulta.
Uma expressão de consulta deve começar com uma cláusula de e deve terminar com uma cláusula select ou uma cláusula group. Entre a primeira from
cláusula e a última cláusula select
ou group
, pode conter uma ou mais destas cláusulas facultativas: em que, ordem por, juntar, deixar e até mesmo outro de cláusulas. Você também pode usar a palavra-chave into
para habilitar o resultado de uma cláusula join
ou group
para servir como fonte para mais cláusulas de consulta na mesma expressão de consulta.
Variável de consulta
No LINQ, uma variável de consulta é qualquer variável que armazene uma consulta foreach
ou uma chamada direta para seu método IEnumerator.MoveNext().
Observação
Os exemplos neste artigo usam a seguinte fonte de dados e dados de exemplo.
record City(string Name, long Population);
record Country(string Name, double Area, long Population, List<City> Cities);
record Product(string Name, string Category);
static readonly City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000),
new City("Mumbai", 20_412_000),
new City("Beijing", 20_384_000),
new City("Cairo", 18_772_000),
new City("Dhaka", 17_598_000),
new City("Osaka", 19_281_000),
new City("New York-Newark", 18_604_000),
new City("Karachi", 16_094_000),
new City("Chongqing", 15_872_000),
new City("Istanbul", 15_029_000),
new City("Buenos Aires", 15_024_000),
new City("Kolkata", 14_850_000),
new City("Lagos", 14_368_000),
new City("Kinshasa", 14_342_000),
new City("Manila", 13_923_000),
new City("Rio de Janeiro", 13_374_000),
new City("Tianjin", 13_215_000)
];
static readonly Country[] countries = [
new Country ("Vatican City", 0.44, 526, [new City("Vatican City", 826)]),
new Country ("Monaco", 2.02, 38_000, [new City("Monte Carlo", 38_000)]),
new Country ("Nauru", 21, 10_900, [new City("Yaren", 1_100)]),
new Country ("Tuvalu", 26, 11_600, [new City("Funafuti", 6_200)]),
new Country ("San Marino", 61, 33_900, [new City("San Marino", 4_500)]),
new Country ("Liechtenstein", 160, 38_000, [new City("Vaduz", 5_200)]),
new Country ("Marshall Islands", 181, 58_000, [new City("Majuro", 28_000)]),
new Country ("Saint Kitts & Nevis", 261, 53_000, [new City("Basseterre", 13_000)])
];
O exemplo de código a seguir mostra uma expressão de consulta simples com uma fonte de dados, uma cláusula de filtragem, uma cláusula de ordenação e nenhuma transformação dos elementos de origem. A cláusula select
encerra a consulta.
// Data source.
int[] scores = [90, 71, 82, 93, 75, 82];
// Query Expression.
IEnumerable<int> scoreQuery = //query variable
from score in scores //required
where score > 80 // optional
orderby score descending // optional
select score; //must end with select or group
// Execute the query to produce the results
foreach (var testScore in scoreQuery)
{
Console.WriteLine(testScore);
}
// Output: 93 90 82 82
No exemplo anterior, scoreQuery
é uma variável de consulta , que às vezes é referida como apenas uma consulta . A variável de consulta não armazena dados de resultado reais, que são produzidos no loop foreach
. E quando a instrução foreach
é executada, os resultados da consulta não são retornados por meio da variável de consulta scoreQuery
. Em vez disso, eles são retornados por meio da variável de iteração testScore
. A variável scoreQuery
pode ser iterada em um segundo loop foreach
. Produz os mesmos resultados, desde que nem ele nem a fonte de dados tenham sido modificados.
Uma variável de consulta pode armazenar uma consulta expressa em sintaxe de consulta ou sintaxe de método, ou uma combinação das duas. Nos exemplos a seguir, queryMajorCities
e queryMajorCities2
são variáveis de consulta:
City[] cities = [
new City("Tokyo", 37_833_000),
new City("Delhi", 30_290_000),
new City("Shanghai", 27_110_000),
new City("São Paulo", 22_043_000)
];
//Query syntax
IEnumerable<City> queryMajorCities =
from city in cities
where city.Population > 30_000_000
select city;
// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 30_000_000);
// Execute the query to produce the results
foreach (City city in queryMajorCities2)
{
Console.WriteLine(city);
}
// Output:
// City { Name = Tokyo, Population = 37833000 }
// City { Name = Delhi, Population = 30290000 }
Por outro lado, os dois exemplos a seguir mostram variáveis que não são variáveis de consulta, embora cada uma seja inicializada com uma consulta. Não são variáveis de consulta porque armazenam resultados:
var highestScore = (
from score in scores
select score
).Max();
// or split the expression
IEnumerable<int> scoreQuery =
from score in scores
select score;
var highScore = scoreQuery.Max();
// the following returns the same result
highScore = scores.Max();
var largeCitiesList = (
from country in countries
from city in country.Cities
where city.Population > 10000
select city
).ToList();
// or split the expression
IEnumerable<City> largeCitiesQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
var largeCitiesList2 = largeCitiesQuery.ToList();
Digitação explícita e implícita de variáveis de consulta
Esta documentação geralmente fornece o tipo explícito da variável de consulta para mostrar a relação de tipo entre a variável de consulta e a cláusula select. No entanto, você também pode usar a palavra-chave var para instruir o compilador a inferir o tipo de uma variável de consulta (ou qualquer outra variável local) em tempo de compilação. Por exemplo, o exemplo de consulta mostrado anteriormente neste artigo também pode ser expresso usando digitação implícita:
var queryCities =
from city in cities
where city.Population > 100000
select city;
No exemplo anterior, o uso de var é opcional.
queryCities
é um IEnumerable<City>
digitado implícita ou explicitamente.
Iniciando uma expressão de consulta
Uma expressão de consulta deve começar com uma cláusula from
. Ele especifica uma fonte de dados juntamente com uma variável de intervalo. A variável range representa cada elemento sucessivo na sequência de origem à medida que a sequência de origem está sendo percorrida. A variável de intervalo é do tipo fortemente definido com base no tipo de elementos na fonte de dados. No exemplo a seguir, como countries
é uma matriz de objetos Country
, a variável range também é digitada como Country
. Como a variável range é fortemente tipada, você pode usar o operador dot para acessar quaisquer membros disponíveis do tipo.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 20 //sq km
select country;
A variável range está no escopo até que a consulta seja encerrada com um ponto-e-vírgula ou com uma continuação cláusula.
Uma expressão de consulta pode conter várias cláusulas from
. Use mais cláusulas from
quando cada elemento na sequência de origem for uma coleção por si só ou se contiver uma coleção. Por exemplo, suponha que você tenha uma coleção de objetos Country
, cada um dos quais contém uma coleção de objetos City
chamados Cities
. Para consultar os objetos City
em cada Country
, use duas cláusulas from
, conforme mostrado aqui:
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
Para obter mais informações, consulte da cláusula.
Terminando uma expressão de consulta
Uma expressão de consulta deve terminar com uma cláusula group
ou uma cláusula select
.
A cláusula de grupo
Use a cláusula group
para produzir uma sequência de grupos organizados por uma chave que você especificar. A chave pode ser qualquer tipo de dados. Por exemplo, a consulta a seguir cria uma sequência de grupos que contém um ou mais objetos Country
e cuja chave é um tipo char
com valor sendo a primeira letra dos nomes dos países.
var queryCountryGroups =
from country in countries
group country by country.Name[0];
Para obter mais informações sobre agrupamento, consulte cláusula de agrupamento.
Cláusula de seleção
Use a cláusula select
para produzir todos os outros tipos de sequências. Uma cláusula select
simples apenas produz uma sequência do mesmo tipo de objetos que os objetos contidos na fonte de dados. Neste exemplo, a fonte de dados contém Country
objetos. A cláusula orderby
apenas classifica os elementos em uma nova ordem e a cláusula select
produz uma sequência dos objetos Country
reordenados.
IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;
A cláusula select
pode ser usada para transformar dados de origem em sequências de novos tipos. Essa transformação também é chamada de projeção . No exemplo a seguir, a cláusula select
projetos uma sequência de tipos anônimos que contém apenas um subconjunto dos campos no elemento original. Os novos objetos são inicializados usando um inicializador de objeto.
var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};
Portanto, neste exemplo, o var
é necessário porque a consulta produz um tipo anônimo.
Para obter mais informações sobre todas as maneiras como uma cláusula select
pode ser usada para transformar dados de origem, consulte a cláusula select.
Continuações com para
Você pode usar a palavra-chave into
em uma cláusula select
ou group
para criar um identificador temporário que armazena uma consulta. Use a cláusula into
quando precisar executar operações de consulta adicionais em uma consulta após uma operação de agrupamento ou seleção. No exemplo a seguir, countries
são agrupadas de acordo com a população em intervalos de 10 milhões. Depois que esses grupos são criados, mais cláusulas filtram alguns grupos e, em seguida, classificam os grupos em ordem crescente. Para realizar essas operações extras, é necessária a continuação representada por countryGroup
.
// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
from country in countries
let percentile = (int)country.Population / 1_000
group country by percentile into countryGroup
where countryGroup.Key >= 20
orderby countryGroup.Key
select countryGroup;
// grouping is an IGrouping<int, Country>
foreach (var grouping in percentileQuery)
{
Console.WriteLine(grouping.Key);
foreach (var country in grouping)
{
Console.WriteLine(country.Name + ":" + country.Population);
}
}
Para obter mais informações, consulte into
.
Filtragem, ordenação e junção
Entre a cláusula inicial from
e a cláusula final select
ou group
, todas as outras cláusulas (where
, join
, orderby
, from
, let
) são opcionais. Qualquer cláusula opcional pode ser usada zero vezes ou várias vezes num corpo de consulta.
A cláusula 'onde'
Use a cláusula where
para filtrar elementos dos dados de origem com base em uma ou mais expressões de predicados. A cláusula where
no exemplo a seguir tem um predicado com duas condições.
IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 15_000_000 and > 10_000_000
select city;
Para obter mais informações, consulte onde a cláusula.
A cláusula OrderBy
Use a cláusula orderby
para classificar os resultados em ordem crescente ou decrescente. Você também pode especificar ordens de classificação secundárias. O exemplo a seguir executa uma classificação primária nos objetos country
usando a propriedade Area
. Em seguida, ele executa uma classificação secundária usando a propriedade Population
.
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;
A palavra-chave ascending
é opcional; é a ordem de classificação padrão se nenhuma ordem for especificada. Para obter mais informações, consulte a cláusula de ordenação por .
A cláusula de adesão
Use a cláusula join
para associar e/ou combinar elementos de uma fonte de dados com elementos de outra fonte de dados com base em uma comparação de igualdade entre chaves especificadas em cada elemento. No LINQ, as operações de junção são executadas em sequências de objetos cujos elementos são de tipos diferentes. Depois de unir duas sequências, você deve usar uma instrução select
ou group
para especificar qual elemento armazenar na sequência de saída. Você também pode usar um tipo anônimo para combinar propriedades de cada conjunto de elementos associados em um novo tipo para a sequência de saída. O exemplo a seguir associa prod
objetos cuja propriedade Category
corresponde a uma das categorias na matriz de cadeia de caracteres categories
. Os produtos cuja Category
não correspondem a nenhuma cadeia de caracteres em categories
são filtrados. A instrução select
projeta um novo tipo cujas propriedades são obtidas tanto de cat
quanto de prod
.
var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new
{
Category = cat,
Name = prod.Name
};
Você também pode executar uma associação de grupo armazenando os resultados da operação join
em uma variável temporária usando a palavra-chave into
. Para obter mais informações, consulte cláusula de junção.
A cláusula de arrendamento
Use a cláusula let
para armazenar o resultado de uma expressão, como uma chamada de método, em uma nova variável de intervalo. No exemplo a seguir, a variável range firstName
armazena o primeiro elemento da matriz de cadeias de caracteres retornadas por Split
.
string[] names = ["Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia"];
IEnumerable<string> queryFirstNames =
from name in names
let firstName = name.Split(' ')[0]
select firstName;
foreach (var s in queryFirstNames)
{
Console.Write(s + " ");
}
//Output: Svetlana Claire Sven Cesar
Para obter mais informações, consulte cláusula let.
Subconsultas em uma expressão de consulta
Uma cláusula de consulta pode conter uma expressão de consulta, que às vezes é chamada de subconsulta . Cada subconsulta começa com a sua própria cláusula from
que não aponta necessariamente para a mesma fonte de dados na primeira cláusula from
. Por exemplo, a consulta a seguir mostra uma expressão de consulta usada na instrução select para recuperar os resultados de uma operação de agrupamento.
var queryGroupMax =
from student in students
group student by student.Year into studentGroup
select new
{
Level = studentGroup.Key,
HighestScore = (
from student2 in studentGroup
select student2.ExamScores.Average()
).Max()
};
Para obter mais informações, consulte Executar uma subconsulta em uma operação de agrupamento.
Ver também
- Palavras-chave de consulta (LINQ)
- Visão geral dos operadores de consulta padrão