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

В этой статье представлены основные понятия, связанные с выражениями запроса на языке C#.This article introduces the basic concepts related to query expressions in C#.

Что такое запрос и для чего он нуженWhat is a query and what does it do?

Запрос — это набор инструкций, которые описывают, какие данные необходимо извлечь из указанного источника (или источников) данных, а также описывают форму и организацию извлекаемых данных.A query is a set of instructions that describes what data to retrieve from a given data source (or sources) and what shape and organization the returned data should have. Запрос отличается от полученного с его помощью результата.A query is distinct from the results that it produces.

Обычно исходные данные логически организованы как последовательность элементов одного вида.Generally, the source data is organized logically as a sequence of elements of the same kind. Например, таблица базы данных SQL содержит последовательность строк.For example, a SQL database table contains a sequence of rows. В файле XML содержится "последовательность" элементов XML (они организованы иерархически в древовидную структуру).In an XML file, there is a "sequence" of XML elements (although these are organized hierarchically in a tree structure). Коллекция в памяти содержит последовательность объектов.An in-memory collection contains a sequence of objects.

С точки зрения приложения определенные тип и структура оригинальных исходных данных не важны.From an application's viewpoint, the specific type and structure of the original source data is not important. Приложение всегда видит исходные данные в виде коллекции IEnumerable<T> или IQueryable<T>.The application always sees the source data as an IEnumerable<T> or IQueryable<T> collection. Например, в LINQ to XML исходные данные становятся видимыми как IEnumerable<XElement>.For example, in LINQ to XML, the source data is made visible as an IEnumerable<XElement>.

При такой исходной последовательности, запрос может выполнять одно из трех возможных действий.Given this source sequence, a query may do one of three things:

  • Извлечение подмножества элементов для получения новой последовательности без изменения отдельных элементов.Retrieve a subset of the elements to produce a new sequence without modifying the individual elements. Затем запрос может отсортировать или сгруппировать возвращаемую последовательность различными способами, как показано в следующем примере (предположим, что scores является int[]):The query may then sort or group the returned sequence in various ways, as shown in the following example (assume scores is an int[]):

    IEnumerable<int> highScoresQuery =
        from score in scores
        where score > 80
        orderby score descending
        select score;
    
  • Извлечение последовательности элементов, как и в предыдущем примере, но с преобразованием элементов в новый тип объекта.Retrieve a sequence of elements as in the previous example but transform them to a new type of object. Например, запрос может извлекать только фамилии из определенных записей клиентов в источнике данных.For example, a query may retrieve only the last names from certain customer records in a data source. Запрос также может извлекать полную запись и использовать ее для создания другого типа объекта в памяти или даже данных XML перед созданием заключительной последовательности результатов.Or it may retrieve the complete record and then use it to construct another in-memory object type or even XML data before generating the final result sequence. В следующем примере показана трансформация int в string.The following example shows a projection from an int to a string. Обратите внимание на новый тип highScoresQuery.Note the new type of highScoresQuery.

    IEnumerable<string> highScoresQuery2 =
        from score in scores
        where score > 80
        orderby score descending
        select $"The score is {score}";
    
  • Извлечение одноэлементного значения исходных данных, таких как:Retrieve a singleton value about the source data, such as:

    • Количество элементов, соответствующих определенному условию.The number of elements that match a certain condition.

    • Элемент с наибольшим или наименьшим значением.The element that has the greatest or least value.

    • Первый элемент, соответствующий условию, или сумма определенных значений в указанном наборе элементов.The first element that matches a condition, or the sum of particular values in a specified set of elements. Например, следующий запрос возвращает количество оценок выше 80 из целочисленного массива scores:For example, the following query returns the number of scores greater than 80 from the scores integer array:

      int highScoreCount =
          (from score in scores
           where score > 80
           select score)
           .Count();
      

      В предыдущем примере обратите внимание на использование скобок вокруг выражения запроса перед вызовом метода Count.In the previous example, note the use of parentheses around the query expression before the call to the Count method. Его также можно выразить, используя новую переменную для сохранения конкретного результата.You can also express this by using a new variable to store the concrete result. Этот метод является более удобочитаемым, так как переменная, в которой хранится запрос, хранится отдельно от запроса, в котором хранится результат.This technique is more readable because it keeps the variable that stores the query separate from the query that stores a result.

      IEnumerable<int> highScoresQuery3 =
          from score in scores
          where score > 80
          select score;
      
      int scoreCount = highScoresQuery3.Count();
      

В предыдущем примере запрос выполняется в вызове Count, так как Count должен выполнить итерацию результатов, чтобы определить количество элементов, возвращенных методом highScoresQuery.In the previous example, the query is executed in the call to Count, because Count must iterate over the results in order to determine the number of elements returned by highScoresQuery.

Что такое выражение запросаWhat is a query expression?

Выражение запроса — запрос, выраженный с помощью синтаксиса запроса.A query expression is a query expressed in query syntax. Выражение запроса является конструкцией языка первого класса.A query expression is a first-class language construct. Оно похоже на любое другое выражение и может использоваться в любом контексте, в котором выражение C# является допустимым.It is just like any other expression and can be used in any context in which a C# expression is valid. Выражение запроса состоит из набора предложений, написанных в декларативном синтаксисе, аналогичном SQL или XQuery.A query expression consists of a set of clauses written in a declarative syntax similar to SQL or XQuery. Каждое предложение, в свою очередь, содержит одно или несколько выражений C#, которые могут являться выражениями запроса или содержать выражение запроса.Each clause in turn contains one or more C# expressions, and these expressions may themselves be either a query expression or contain a query expression.

Выражение запроса должно начинаться предложением from и заканчиваться предложением select или group.A query expression must begin with a from clause and must end with a select or group clause. Между первым предложением from и последним предложением select или group может содержаться одно или несколько необязательных предложений: where, orderby, join, let и даже дополнительных предложений from.Between the first from clause and the last select or group clause, it can contain one or more of these optional clauses: where, orderby, join, let and even additional from clauses. Можно также использовать ключевое слово into, чтобы результат предложения join или group мог служить источником дополнительных предложений запроса в том же выражении запроса.You can also use the into keyword to enable the result of a join or group clause to serve as the source for additional query clauses in the same query expression.

Переменная запросаQuery variable

В LINQ переменная запроса — это любая переменная, сохраняющая запрос вместо результатов запроса.In LINQ, a query variable is any variable that stores a query instead of the results of a query. Говоря точнее, переменная запроса всегда является перечислимым типом и производит последовательность элементов, когда она используется в итерации оператора foreach или прямом вызове ее метода IEnumerator.MoveNext.More specifically, a query variable is always an enumerable type that will produce a sequence of elements when it is iterated over in a foreach statement or a direct call to its IEnumerator.MoveNext method.

В следующем примере кода показано простое выражение запроса с одним источником данных, одним предложением фильтрации, одним предложением упорядочения и без трансформации исходных элементов.The following code example shows a simple query expression with one data source, one filtering clause, one ordering clause, and no transformation of the source elements. Предложение select завершает запрос.The select clause ends the query.

static void Main()
{
    // 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 (int testScore in scoreQuery)
    {
        Console.WriteLine(testScore);
    }
}
// Outputs: 93 90 82 82

В предыдущем примере scoreQueryпеременная запроса, которую иногда называют просто запросом.In the previous example, scoreQuery is a query variable, which is sometimes referred to as just a query. В переменной запроса не хранятся фактические данные результата, которые получаются с помощью цикла foreach.The query variable stores no actual result data, which is produced in the foreach loop. Когда выполняется оператор foreach, результаты запроса не возвращаются с помощью переменной запроса scoreQuery.And when the foreach statement executes, the query results are not returned through the query variable scoreQuery. В этом случае они возвращаются с помощью переменной итерации testScore.Rather, they are returned through the iteration variable testScore. Итерация переменной scoreQuery может выполняться во втором цикле foreach.The scoreQuery variable can be iterated in a second foreach loop. Результаты будет теми же, если ни они, ни источник данных не изменяются.It will produce the same results as long as neither it nor the data source has been modified.

В переменной запроса может храниться запрос, выраженный с помощью синтаксиса запроса или метода запроса, или их комбинации.A query variable may store a query that is expressed in query syntax or method syntax, or a combination of the two. В следующих примерах queryMajorCities и queryMajorCities2 являются переменными запроса.In the following examples, both queryMajorCities and queryMajorCities2 are query variables:

//Query syntax
IEnumerable<City> queryMajorCities =
    from city in cities
    where city.Population > 100000
    select city;

// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

С другой стороны, в следующих примерах показаны переменные, которые не являются переменными запроса даже несмотря на то, что все они инициализируются запросом.On the other hand, the following two examples show variables that are not query variables even though each is initialized with a query. Они не являются переменными запроса, так как в них хранятся результаты.They are not query variables because they store results:

int highestScore =
    (from score in scores
     select score)
    .Max();

// or split the expression
IEnumerable<int> scoreQuery =
    from score in scores
    select score;

int highScore = scoreQuery.Max();
// the following returns the same result
int highScore = scores.Max();

List<City> 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;

List<City> largeCitiesList2 = largeCitiesQuery.ToList();

Дополнительные сведения о различных способах выражения запросов см. в разделе Синтаксис запросов и синтаксис методов в LINQ.For more information about the different ways to express queries, see Query syntax and method syntax in LINQ.

Явная и неявная типизация переменных запросаExplicit and implicit typing of query variables

В этой документации обычно явно указывается тип переменной запроса для того, чтобы продемонстрировать типичное отношение между переменной запроса и предложением select.This documentation usually provides the explicit type of the query variable in order to show the type relationship between the query variable and the select clause. Однако можно также использовать ключевое слов var, чтобы указать компилятору вывести тип переменной запроса (или любой другой локальной переменной) во время компиляции.However, you can also use the var keyword to instruct the compiler to infer the type of a query variable (or any other local variable) at compile time. Например, ранее приведенный в данном разделе пример запроса также может быть выражен путем неявной типизации:For example, the query example that was shown previously in this topic can also be expressed by using implicit typing:

// Use of var is optional here and in all queries.
// queryCities is an IEnumerable<City> just as
// when it is explicitly typed.
var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

Дополнительные сведения см. в разделах Неявно типизированные локальные переменные и Связи типов в операциях запроса LINQ.For more information, see Implicitly typed local variables and Type relationships in LINQ query operations.

Начало выражения запросаStarting a query expression

Выражение запроса должно начинаться с предложения from.A query expression must begin with a from clause. Оно задает источник данных вместе с переменной диапазона.It specifies a data source together with a range variable. Переменная диапазона предоставляет каждый последующий элемент в исходной последовательности во время ее обзора.The range variable represents each successive element in the source sequence as the source sequence is being traversed. Переменная диапазона строго типизируется на основе типа элементов в источнике данных.The range variable is strongly typed based on the type of elements in the data source. В следующем примере переменная диапазона типизируется как countries, так как Country является массивом объектов Country.In the following example, because countries is an array of Country objects, the range variable is also typed as Country. Так как переменная диапазона строго типизируется, для доступа к любым доступным элементам типа можно использовать оператор-точку.Because the range variable is strongly typed, you can use the dot operator to access any available members of the type.

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //sq km
    select country;

Переменная диапазона находится в области до тех пор, пока запрос не завершится с помощью точки с запятой или предложения continuation.The range variable is in scope until the query is exited either with a semicolon or with a continuation clause.

Выражение запроса может содержать несколько предложений from.A query expression may contain multiple from clauses. Используйте дополнительные предложения from, если каждый элемент в исходной последовательности является коллекцией или содержит коллекцию.Use additional from clauses when each element in the source sequence is itself a collection or contains a collection. Например, предположим, что имеется коллекция объектов Country, каждый из которых содержит коллекцию объектов City с именем Cities.For example, assume that you have a collection of Country objects, each of which contains a collection of City objects named Cities. Для выполнения запросов к объектам City в каждой коллекции Country используйте два предложения from, как показано ниже.To query the City objects in each Country, use two from clauses as shown here:

IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

Дополнительные сведения см. в разделе Предложение from.For more information, see from clause.

Окончание выражения запросаEnding a query expression

Выражение запроса должно завершаться предложением group или select.A query expression must end with either a group clause or a select clause.

Предложение groupgroup clause

Используйте предложение group для получения последовательности групп, упорядоченных по указанному ключу.Use the group clause to produce a sequence of groups organized by a key that you specify. Ключом могут быть данные любого типа.The key can be any data type. Например, следующий запрос создает последовательность групп, содержащую один или несколько объектов Country, ключ для которых имеет значение char.For example, the following query creates a sequence of groups that contains one or more Country objects and whose key is a char value.

var queryCountryGroups =
    from country in countries
    group country by country.Name[0];

Дополнительные сведения о группировании см. в разделе Предложение group.For more information about grouping, see group clause.

Предложение selectselect clause

Используйте предложение select для получения всех других типов последовательностей.Use the select clause to produce all other types of sequences. Простое предложение select просто создает последовательность с тем же типом объектов, что и у объектов, которые содержатся в источнике данных.A simple select clause just produces a sequence of the same type of objects as the objects that are contained in the data source. В этом примере источник данных содержит объекты типа Country.In this example, the data source contains Country objects. Предложение orderby просто сортирует элементы в новом порядке, а предложение select создает последовательность переупорядоченных объектов Country.The orderby clause just sorts the elements into a new order and the select clause produces a sequence of the reordered Country objects.

IEnumerable<Country> sortedQuery =
    from country in countries
    orderby country.Area
    select country;

Предложение select может использоваться для преобразования исходных данных в последовательности новых типов.The select clause can be used to transform source data into sequences of new types. Такое преобразование также называется проекцией.This transformation is also named a projection. В следующем примере предложение select создает проекцию последовательности анонимных типов, которая содержит только подмножество полей оригинального элемента.In the following example, the select clause projects a sequence of anonymous types which contains only a subset of the fields in the original element. Обратите внимание, что новые объекты инициализируются с помощью инициализатора объекта.Note that the new objects are initialized by using an object initializer.

// Here var is required because the query
// produces an anonymous type.
var queryNameAndPop =
    from country in countries
    select new { Name = country.Name, Pop = country.Population };

Дополнительные сведения обо всех методах использования предложения select для преобразования исходных данных см. в разделе Предложение select.For more information about all the ways that a select clause can be used to transform source data, see select clause.

Продолжения с использованием ключевого слова "into"Continuations with "into"

Ключевое слово into можно использовать в предложении select или group для создания временного идентификатора, в котором хранится запрос.You can use the into keyword in a select or group clause to create a temporary identifier that stores a query. Это действие рекомендуется выполнять, если требуется выполнить в запросе дополнительные операции запроса после операции группирования или выбора.Do this when you must perform additional query operations on a query after a grouping or select operation. В следующем примере объекты countries группируются в соответствии с численностью населения в диапазоны по 10 миллионов.In the following example countries are grouped according to population in ranges of 10 million. После создания этих групп дополнительные предложения отфильтровывают некоторые группы, а затем сортируют группы в порядке возрастания.After these groups are created, additional clauses filter out some groups, and then to sort the groups in ascending order. Чтобы выполнить эти дополнительные операции, требуется продолжение, предоставляемое с помощью countryGroup.To perform those additional operations, the continuation represented by countryGroup is required.

// 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.For more information, see into.

Фильтрация, упорядочение и присоединениеFiltering, ordering, and joining

Между открывающим предложением from и завершающим предложением select или group могут размещаться все остальные необязательные предложения (where, join, orderby, from, let).Between the starting from clause, and the ending select or group clause, all other clauses (where, join, orderby, from, let) are optional. Любое необязательное предложение может использоваться в теле запроса несколько раз или отсутствовать вообще.Any of the optional clauses may be used zero times or multiple times in a query body.

Предложение wherewhere clause

Используйте предложение where для фильтрации элементов из источника данных по одному или нескольким выражениям предиката.Use the where clause to filter out elements from the source data based on one or more predicate expressions. Предложение where в следующем примере имеет один предикат с двумя условиями.The where clause in the following example has one predicate with two conditions.

IEnumerable<City> queryCityPop =
    from city in cities
    where city.Population < 200000 && city.Population > 100000
    select city;

Дополнительные сведения см. в разделе Предложение where.For more information, see where clause.

Предложение orderbyorderby clause

Используйте orderby предложение для сортировки результатов по возрастанию или убыванию.Use the orderby clause to sort the results in either ascending or descending order. Также можно задать порядок дополнительной сортировки.You can also specify secondary sort orders. В следующем примере выполняется основная сортировка объектов country по свойству Area.The following example performs a primary sort on the country objects by using the Area property. Затем выполняется дополнительная сортировка по свойству Population.It then performs a secondary sort by using the Population property.

IEnumerable<Country> querySortedCountries =
    from country in countries
    orderby country.Area, country.Population descending
    select country;

Ключевое слово ascending является необязательным, так как сортировка по умолчанию происходит по возрастанию, если не задан порядок сортировки.The ascending keyword is optional; it is the default sort order if no order is specified. Дополнительные сведения см. в разделе Предложение orderby.For more information, see orderby clause.

Предложение joinjoin clause

Используйте предложение join для связи или объединения элементов из одного источника данных с элементами из другого источника данных на основе сравнения на равенство определенных ключей в каждом элементе.Use the join clause to associate and/or combine elements from one data source with elements from another data source based on an equality comparison between specified keys in each element. В LINQ операции объединения выполняются в последовательностях объектов, элементы которых относятся к разным типам.In LINQ, join operations are performed on sequences of objects whose elements are different types. После объединения двух последовательностей необходимо использовать оператор select или group, чтобы указать элемент для сохранения в выходной последовательности.After you have joined two sequences, you must use a select or group statement to specify which element to store in the output sequence. Также можно использовать анонимный тип, чтобы объединить свойства каждого набора связанных элементов в новый тип для выходной последовательности.You can also use an anonymous type to combine properties from each set of associated elements into a new type for the output sequence. В следующем примере связываются объекты prod, свойство Category которых соответствует одной из категорий в массиве строк categories.The following example associates prod objects whose Category property matches one of the categories in the categories string array. Продукты, свойство Category которых не соответствует ни одной строке в categories, отфильтровываются. Оператор select формирует новый тип, свойства которого берутся как из cat, так и из prod.Products whose Category does not match any string in categories are filtered out. The select statement projects a new type whose properties are taken from both cat and 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.You can also perform a group join by storing the results of the join operation into a temporary variable by using the into keyword. Дополнительные сведения см. в разделе Предложение join.For more information, see join clause.

Предложение letlet clause

Используйте предложение let для сохранения результата выражения, например вызов метода, в новую переменную диапазона.Use the let clause to store the result of an expression, such as a method call, in a new range variable. В следующем примере в переменную диапазона firstName сохраняется первый элемент массива строк, возвращенного с помощью Split.In the following example, the range variable firstName stores the first element of the array of strings that is returned by 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 (string s in queryFirstNames)
    Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar

Дополнительные сведения см. в разделе Предложение let.For more information, see let clause.

Вложенные запросы в выражении запросаSubqueries in a query expression

Предложение запроса может само содержать выражение запроса, которое иногда называют вложенным запросом.A query clause may itself contain a query expression, which is sometimes referred to as a subquery. Каждый вложенный запрос начинается с собственным предложением from, которое может указывать на источник данных, отличный от источника данных первого предложения from.Each subquery starts with its own from clause that does not necessarily point to the same data source in the first from clause. Например, в следующем запросе показано выражение запроса, которое используется в операторе "select" для извлечения результатов операции группирования.For example, the following query shows a query expression that is used in the select statement to retrieve the results of a grouping operation.

var queryGroupMax =
    from student in students
    group student by student.GradeLevel into studentGroup
    select new
    {
        Level = studentGroup.Key,
        HighestScore =
            (from student2 in studentGroup
             select student2.Scores.Average())
             .Max()
    };

Дополнительные сведения см. в руководстве по выполнению вложенного запроса в операции группирования.For more information, see Perform a subquery on a grouping operation.

См. такжеSee also