Основы выражения запроса
В этой статье представлены основные понятия, связанные с выражениями запросов в C#.
Что такое запрос и для чего он нужен?
запроса
Как правило, исходные данные упорядочены логически как последовательность элементов одного типа. Например, таблица базы данных SQL содержит последовательность строк. В XML-файле есть "последовательность" XML-элементов (хотя XML-элементы организованы иерархически в структуре дерева). Коллекция в памяти содержит последовательность объектов.
С точки зрения приложения конкретный тип и структура исходных исходных данных не важна. Приложение всегда видит исходные данные как коллекцию IEnumerable<T> или IQueryable<T>. Например, в xml-коде LINQ to XML исходные данные отображаются как IEnumerable
<XElement>.
С учетом этой исходной последовательности запрос может выполнять одно из трех действий:
Получение подмножества элементов для создания новой последовательности без изменения отдельных элементов. Затем запрос может сортировать или группировать возвращаемую последовательность различными способами, как показано в следующем примере (предположим, что
scores
являетсяint[]
):IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
Получите последовательность элементов, как в предыдущем примере, но преобразуйте их в новый тип объекта. Например, запрос может получить только имена семейств из определенных записей клиента в источнике данных. Или он может получить полную запись, а затем использовать ее для создания другого типа объекта в памяти или даже XML-данных перед созданием конечной последовательности результатов. В следующем примере показана проекция от
int
кstring
. Обратите внимание на новый типhighScoresQuery
.IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select $"The score is {score}";
Извлеките одноэлементное значение о исходных данных, например:
Количество элементов, соответствующих определенному условию.
Элемент, имеющий наибольшее или наименьшее значение.
Первый элемент, соответствующий условию или сумме определенных значений в указанном наборе элементов. Например, следующий запрос возвращает количество показателей, превышающее 80 из массива целых чисел
scores
:var highScoreCount = ( from score in scores where score > 80 select score ).Count();
В предыдущем примере обратите внимание на использование круглых скобок вокруг выражения запроса перед вызовом метода Enumerable.Count. Можно также использовать новую переменную для хранения конкретного результата.
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; var scoreCount = highScoresQuery3.Count();
В предыдущем примере запрос выполняется в вызове Count
, потому что Count
должен итерировать результаты, чтобы выяснить, сколько элементов возвращает highScoresQuery
.
Что такое выражение запроса?
Выражение запроса — это запрос, выраженный в синтаксисе запроса. Выражение запроса — это конструкция языка первого класса. Он похож на любое другое выражение и может использоваться в любом контексте, в котором допустимо выражение C#. Выражение запроса состоит из набора предложений, написанных в декларативном синтаксисе, аналогичном SQL или XQuery. Каждое предложение, в свою очередь, содержит одно или несколько выражений C#, и эти выражения могут быть либо выражением запроса, либо содержать выражение запроса.
Выражение запроса должно начинаться с из предложения и должно заканчиваться предложением выбрать или группу . Между первым предложением from
и последним предложением select
или group
оно может содержать одно или несколько из этих необязательных предложений: , где, orderby, присоединиться, позволить и даже другим из предложений. Вы также можете использовать ключевое слово into
, чтобы включить результат предложения join
или group
, чтобы служить источником для дополнительных предложений запросов в том же выражении запроса.
Переменная запроса
В LINQ переменная запроса — это любая переменная, которая хранит запроса foreach
или прямом вызове метода IEnumerator.MoveNext().
Заметка
Примеры в этой статье используют следующий источник данных и примерные данные.
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)])
];
В следующем примере кода показан простое выражение запроса с одним источником данных, одним предложением фильтрации, одним предложением упорядочения и без преобразования исходных элементов. Предложение select
заканчивает запрос.
// 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
В предыдущем примере scoreQuery
— это переменная запроса ,, которая иногда упоминается просто как запроса. Переменная запроса не хранит фактические данные результатов, которые создаются в цикле foreach
. При выполнении инструкции foreach
результаты запроса не возвращаются с помощью переменной запроса scoreQuery
. Скорее, они возвращаются через переменную итерации testScore
. Переменная scoreQuery
может быть итерирована во втором цикле foreach
. Он создает те же результаты, пока ни он, ни источник данных не были изменены.
Переменная запроса может хранить запрос, выраженный в синтаксисе запроса или синтаксисе метода, или сочетание двух. В следующих примерах queryMajorCities
и queryMajorCities2
являются переменными запроса:
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 }
С другой стороны, в следующих двух примерах показаны переменные, которые не являются переменными запроса, даже если каждый из них инициализирован с помощью запроса. Они не являются переменными запроса, так как они хранят результаты:
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();
Явная и неявная типизация переменных запроса
Эта документация обычно предоставляет явный тип переменной запроса, чтобы отобразить связь типа между переменной запроса и предложением выбора . Однако можно также использовать ключевое слово var
var queryCities =
from city in cities
where city.Population > 100000
select city;
В предыдущем примере использование var является необязательным.
queryCities
— это IEnumerable<City>
, типизированный неявно или явно.
Запуск выражения запроса
Выражение запроса должно начинаться с предложения from
. Он задает источник данных вместе с переменной диапазона. Переменная диапазона представляет каждый последовательный элемент в исходной последовательности по мере прохождения исходной последовательности. Переменная диапазона строго типизирована на основе типа элементов в источнике данных. В следующем примере, так как countries
является массивом объектов Country
, переменная диапазона также вводится как Country
. Так как переменная диапазона строго типизирована, оператор dot можно использовать для доступа к любым доступным членам типа.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 20 //sq km
select country;
Переменная диапазона остаётся в области до тех пор, пока запрос не завершится точкой с запятой или предложением продолжения.
Выражение запроса может содержать несколько условий from
. Используйте дополнительные предложения from
, если каждый элемент в исходной последовательности является коллекцией или содержит коллекцию. Например, предположим, что у вас есть коллекция объектов Country
, каждая из которых содержит коллекцию объектов City
с именем Cities
. Чтобы запросить объекты City
в каждом Country
, используйте два предложения from
, как показано ниже:
IEnumerable<City> cityQuery =
from country in countries
from city in country.Cities
where city.Population > 10000
select city;
Дополнительные сведения см. в пункта.
Завершение выражения запроса
Выражение запроса должно заканчиваться предложением group
или предложением select
.
Групповая клаузула
Используйте предложение group
для создания последовательности групп, упорядоченных по заданному ключу. Ключ может быть любым типом данных. Например, следующий запрос создает последовательность групп, содержащих один или несколько объектов Country
, и ключ которого является типом char
со значением, которое является первой буквой названий стран.
var queryCountryGroups =
from country in countries
group country by country.Name[0];
Дополнительные сведения о группировке см. в пункте группы.
Предложение select
Используйте предложение select
для создания всех других типов последовательностей. Простое предложение select
просто создает последовательность объектов того же типа, что и объекты, содержащиеся в источнике данных. В этом примере источник данных содержит объекты Country
. Предложение orderby
просто сортирует элементы в новый порядок, а предложение select
создает последовательность переупорядоченных Country
объектов.
IEnumerable<Country> sortedQuery =
from country in countries
orderby country.Area
select country;
Предложение select
можно использовать для преобразования исходных данных в последовательности новых типов. Это преобразование также называется проекцией . В следующем примере предложение select
проецирует последовательность анонимных типов, которая содержит только подмножество полей в исходном элементе. Новые объекты инициализированы с помощью инициализатора объектов.
var queryNameAndPop =
from country in countries
select new
{
Name = country.Name,
Pop = country.Population
};
Поэтому в этом примере требуется var
, так как запрос создает анонимный тип.
Дополнительные сведения обо всех способах использования предложения select
для преобразования исходных данных см. в предложении select.
Продолжение с до
Ключевое слово into
можно использовать в предложении select
или group
для создания временного идентификатора, включающего запрос. Используйте предложение into
, если необходимо выполнить дополнительные операции запроса после группировки или выборки. В следующем примере countries
группируются в соответствии с населением в диапазоне от 10 миллионов. После создания этих групп дополнительные условия фильтруют некоторые из них, а затем упорядочивают группы в порядке возрастания. Для выполнения этих дополнительных операций требуется продолжение, представленное 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);
}
}
Дополнительные сведения см. в разделе into
.
Фильтрация, упорядочивание и присоединение
Между начальным предложением from
и конечным предложением select
или group
все остальные предложения (where
, join
, orderby
, from
, let
) являются необязательными. Любой из необязательных предложений может использоваться ноль раз или несколько раз в тексте запроса.
Условие WHERE
Используйте предложение where
для фильтрации элементов из исходных данных на основе одного или нескольких выражений предиката. Предложение where
в следующем примере содержит один предикат с двумя условиями.
IEnumerable<City> queryCityPop =
from city in cities
where city.Population is < 15_000_000 and > 10_000_000
select city;
Для получения дополнительной информации см. раздел , где упоминается условие.
Предложение orderby
Используйте предложение orderby
для сортировки результатов в порядке возрастания или убывания. Вы также можете указать вторичные порядки сортировки. В следующем примере выполняется первичная сортировка объектов country
с помощью свойства Area
. Затем он выполняет вторичную сортировку с помощью свойства Population
.
IEnumerable<Country> querySortedCountries =
from country in countries
orderby country.Area, country.Population descending
select country;
Ключевое слово ascending
является необязательным; Это порядок сортировки по умолчанию, если порядок сортировки не указан. Дополнительные сведения см. в предложении orderby .
Предложение join
Используйте предложение join
для связывания и (или) объединения элементов из одного источника данных с элементами из другого источника данных на основе сравнения между указанными ключами в каждом элементе. В LINQ операции соединения выполняются на последовательностях объектов, элементы которых являются разными типами. После объединения двух последовательностей необходимо использовать инструкцию select
или group
, чтобы указать, какой элемент будет храниться в выходной последовательности. Можно также использовать анонимный тип для объединения свойств из каждого набора связанных элементов в новый тип для выходной последовательности. В следующем примере сопоставляются объекты prod
, чьё свойство Category
соответствует одной из категорий в строковом массиве categories
. Продукты, Category
которых не соответствуют ни одной строке в categories
, отфильтровываются. Оператор select
проектирует новый тип, свойства которого взяты как из cat
, так и из prod
.
var categoryQuery =
from cat in categories
join prod in products on cat equals prod.Category
select new
{
Category = cat,
Name = prod.Name
};
Вы также можете выполнить присоединение к группе, сохраняя результаты операции join
во временную переменную с помощью ключевого слова into
. Для получения дополнительных сведений см. предложение соединения .
Конструкция let
Используйте предложение let
для хранения результата выражения, например вызова метода, в новой переменной диапазона. В следующем примере переменная диапазона firstName
сохраняет первый элемент массива строк, возвращаемых 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
Дополнительные сведения см. в предложении 'let' .
Подзапросы в выражении запроса
Предложение запроса может содержать выражение запроса, которое иногда называется внутренним запросом. Каждый вложенный запрос начинается с собственного предложения from
, которое не обязательно указывает на тот же источник данных в первом предложении from
. Например, следующий запрос показывает выражение запроса, которое используется в инструкции select для получения результатов операции группировки.
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()
};
Дополнительную информацию см. в теме Выполнение подзапроса для операции группировки.
См. также
- ключевые слова запросов (LINQ)
- Обзор стандартных операторов запросов