Основы выражения запроса

В этой статье представлены основные понятия, связанные с выражениями запроса на языке C#.

Что такое запрос и для чего он нужен

Запрос — это набор инструкций, которые описывают, какие данные необходимо извлечь из указанного источника (или источников) данных, а также описывают форму и организацию извлекаемых данных. Запрос отличается от полученного с его помощью результата.

Обычно исходные данные логически организованы как последовательность элементов одного вида. Например, таблица базы данных SQL содержит последовательность строк. В XML-файле есть "последовательность" XML-элементов (хотя XML-элементы организованы иерархически в структуре дерева). Коллекция в памяти содержит последовательность объектов.

С точки зрения приложения конкретный тип и структура исходных исходных данных не важна. Приложение всегда видит исходные данные в виде коллекции IEnumerable<T> или IQueryable<T>. Например, в xml-коде LINQ to XML исходные данные отображаются как IEnumerableXElement<>.

С учетом этой исходной последовательности запрос может выполнять одно из трех действий:

  • Извлечение подмножества элементов для получения новой последовательности без изменения отдельных элементов. Затем запрос может сортировать или группировать возвращаемую последовательность различными способами, как показано в следующем примере (предположим 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 является необязательным. queryCitiesIEnumerable<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()
    };

Дополнительные сведения см. в руководстве по выполнению вложенного запроса в операции группирования.

См. также