Основы выражения запроса
В этой статье представлены основные понятия, связанные с выражениями запроса на языке 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. Между первым from
предложением и последним select
или group
предложением он может содержать одно или несколько из этих необязательных предложений: где, orderby, join, let и даже другое из предложений. Вы также можете использовать в ключевое слово, чтобы включить результат 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 > 100000
select city;
// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
Console.WriteLine(city);
}
// Output:
// City { Population = 120000 }
// City { Population = 112000 }
// City { Population = 150340 }
// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);
С другой стороны, в следующих двух примерах показаны переменные, которые не являются переменными запроса, даже если каждый из них инициализирован с помощью запроса. Они не являются переменными запроса, так как они хранят результаты:
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();
Явная и неявная типизация переменных запроса
В этой документации обычно явно указывается тип переменной запроса для того, чтобы продемонстрировать типичное отношение между переменной запроса и предложением select. Однако можно также использовать ключевое слов var, чтобы указать компилятору вывести тип переменной запроса (или любой другой локальной переменной) во время компиляции. Например, пример запроса, показанный ранее в этой статье, также можно выразить с помощью неявного ввода:
var queryCities =
from city in cities
where city.Population > 100000
select city;
В предыдущем примере использование var является необязательным. queryCities
IEnumerable<City>
является ли неявно или явно типизированным.
Начало выражения запроса
Выражение запроса должно начинаться с предложения from
. Оно задает источник данных вместе с переменной диапазона. Переменная диапазона предоставляет каждый последующий элемент в исходной последовательности во время ее обзора. Переменная диапазона строго типизируется на основе типа элементов в источнике данных. В следующем примере переменная диапазона типизируется как countries
, так как Country
является массивом объектов Country
. Так как переменная диапазона строго типизируется, для доступа к любым доступным элементам типа можно использовать оператор-точку.
IEnumerable<Country> countryAreaQuery =
from country in countries
where country.Area > 500000 //sq km
select country;
Переменная диапазона находится в области до тех пор, пока запрос не завершится с помощью точки с запятой или предложения continuation.
Выражение запроса может содержать несколько 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;
Дополнительные сведения см. в разделе Предложение from.
Окончание выражения запроса
Выражение запроса должно завершаться предложением group
или select
.
Предложение group
Используйте предложение group
для получения последовательности групп, упорядоченных по указанному ключу. Ключом могут быть данные любого типа. Например, следующий запрос создает последовательность групп, содержащую один или несколько объектов Country
, ключ для которых имеет тип char
со значением, соответствующим первым буквам названий стран.
var queryCountryGroups =
from country in countries
group country by country.Name[0];
Дополнительные сведения о группировании см. в разделе Предложение group.
предложение 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
Ключевое слово 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 / 10_000_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 < 200000 and > 100000
select city;
Дополнительные сведения см. в разделе Предложение where.
предложение 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. Дополнительные сведения см. в разделе Предложение join.
Предложение 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()
};
Дополнительные сведения см. в руководстве по выполнению вложенного запроса в операции группирования.