Основы выражения запроса (Руководство по программированию в C#)

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

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

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

С точки зрения приложения определенные тип и структура оригинальных исходных данных не важны. Исходные данные всегда представляются приложению как коллекция IEnumerable или IQueryable. В LINQ to XML исходные становятся видимыми как IEnumerable<XElement>. В LINQ to DataSet — как IEnumerable<DataRow>. В LINQ to SQL — как IEnumerable или IQueryable любого из пользовательских объектов, которые были определены для представления данных в таблице SQL.

При такой исходной последовательности запрос может выполнять одно из трех возможных действий.

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

    • Количество элементов, которое соответствует определенному условию.

    • Элемент, обладающий наибольшим или наименьшим значением.

    • Первый элемент, соответствующий условию, или сумма определенных значений в заданном наборе элементов. Например, следующий запрос возвращает количество оценок выше 80 из целочисленного массива scores:

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

    В предыдущем примере обратите внимание на использование скобок вокруг выражения запроса перед вызовом метода Count. Его также можно выразить, используя новую переменную для сохранения конкретного результата. Этот метод является более удобочитаемым, так как переменная, в которой хранится запрос, хранится отдельно от запроса, в котором хранится результат.

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

В предыдущем примере запрос выполняется в вызове Count, так как Count должен выполнить итерацию результатов, чтобы определить количество элементов, возвращенных highScoresQuery.

Что такое выражение запроса

Выражение запроса — запрос, выраженный с помощью синтаксиса запроса. Выражение запроса является конструкцией языка первого класса. Выражение запроса похоже на любое другое выражение, оно может использоваться в любом контексте, в котором выражение C# является допустимым. Выражение запроса состоит их набора предложений, написанных в декларативном синтаксисе, аналогичном SQL или XQuery. Каждое предложение, в свою очередь, содержит одно или несколько выражений C#, которые могут являться выражениями запроса или могут содержать выражение запроса.

Выражение запроса должно начинаться предложением from и оканчиваться предложением select или group. Между первым предложением from и последним предложением select или group может содержаться одно или несколько необязательных предложений where, orderby, join, let или даже дополнительных предложений from. Также можно использовать ключевое слово into, чтобы результат предложения join или group мог служить источником дополнительных предложений запроса в том же выражении запроса.

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

В LINQ переменная запроса — это любая переменная, сохраняющая запрос вместо результатов запроса. Говоря точнее, переменная запроса всегда является перечислимым типом и производит последовательность элементов, когда она используется в итерации оператора foreach или прямом вызове ее метода IEnumerator.MoveNext.

В следующем примере кода показано простое выражение запроса с одним источником данных, одним предложением фильтрации, одним предложением упорядочения и без трансформации исходных элементов. Предложение select завершает запрос.

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 — переменная запроса, которую иногда называют просто запросом. В переменной запроса не хранятся фактические данные результата, которые получаются с помощью цикла foreach. Когда выполняется оператор foreach, результаты запроса не возвращаются с помощью переменной запроса scoreQuery. В таком случае они возвращаются с помощью переменной итерации testScore. Итерация переменно scoreQuery может выполняться во втором цикле foreach. Результаты будет теми же, если ни они, ни источник данных не изменяются.

В переменной запроса может храниться запрос, выраженный с помощью синтаксиса запроса или метода запроса, или их комбинации. В следующих примерах queryMajorCities и queryMajorCities2 являются переменными запроса.

//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);

С другой стороны, в следующих примерах показаны переменные, которые не являются переменными запроса даже несмотря на то, что все они инициализируются запросом. Они не являются переменными запроса, так как в них хранятся результаты запроса.

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();

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 к именам переменных, в которых хранятся запросы, добавляется слово "query" (запрос).В именах переменных, в которых хранятся фактические результаты, слово "query" (запрос) отсутствует.

Дополнительные сведения о различных способах выражения запросов содержатся в разделе Синтаксис запросов и синтаксис методов в LINQ (C#).

Явная и неявная типизация переменных запроса

В данной документации обычно явно указывается тип переменной запроса для того, чтобы продемонстрировать типичное отношение между переменной запроса и предложением select. Кроме того, также можно использовать ключевое слово var, для указания компилятору вывести тип переменной запроса (или любой другой локальной переменной) во время компиляции. Например, ранее приведенный в данном разделе пример запроса также может быть выражен путем неявной типизации:

// 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;

Дополнительные сведения см. в разделах Неявно типизированные локальные переменные (Руководство по программированию в C#) и Отношения между типами в операциях запросов LINQ (C#).

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

Выражение запроса должно начинаться с предложения from. Оно задает источник данных вместе с переменной диапазона. Переменная диапазона предоставляет каждый последующий элемент в исходной последовательности во время ее обзора. Переменная диапазона строго типизируется на основе типа элементов в источнике данных. В следующем примере переменная диапазона типизируется как Country, так как countries является массивом объектов Country. Так как переменная диапазона строго типизируется, для доступа к любым доступным элементам типа можно использовать оператор-точку.

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //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;

Дополнительные сведения см. в разделе Предложение from (справочник по C#).

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

Выражение запроса должно завершаться предложением select или group.

Предложение "group"

Используйте предложение group для получения последовательности групп, организованной на основе указанного ключа. Ключом могут быть данные любого типа. Например, следующий запрос создает последовательность групп, содержащую один или несколько объектов Country, ключ для которых имеет значение char.

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

Дополнительные сведения о группировании см. в разделе Предложение group (Справочник по C#).

Предложение "select"

Используйте предложение select для получения всех других типов последовательностей. Простое предложение select просто создает последовательность с тем же типом объектов, что и у объектов, которые содержатся в источнике данных. В этом примере источник данных содержит объекты типа Country. Предложение orderby просто сортирует элементы в новом порядке, а предложение select создает последовательность переупорядоченных объектов Country.

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

Предложение select может использоваться для преобразования исходных данных в последовательности новых типов. Такое преобразование также называют проекцией. В следующем примере предложение select создает проекцию последовательности анонимных типов, содержащую только подмножество полей оригинального элемента. Обратите внимание, что новые объекты инициализируются с помощью инициализатора объекта.

// 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" (справочник по C#).

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

Ключевое слово into можно использовать в предложении select или group для создания временного идентификатора, в котором хранится запрос. Это действие рекомендуется выполнять, если требуется выполнить в запросе дополнительные операции запроса после операции группирования или выбора. В следующем примере объекты countries группируются в соответствии с численностью населения в диапазоны по 10 миллионов. После создания этих групп дополнительные предложения отфильтровывают некоторые группы, а затем сортируют группы в порядке возрастания. Чтобы выполнить эти дополнительные операции, требуется продолжение, представляемое с помощью countryGroup.

// percentileQuery is an IEnumerable<IGrouping<int, Country>> 
var percentileQuery =
    from country in countries
    let percentile = (int) country.Population / 10000000
    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 (Справочник по C#).

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

Между открывающим предложением from и завершающим предложением select или group могут размещаться все остальные, необязательные предложения (where, join, orderby, from, let). Любое необязательное предложение может использоваться в теле запроса несколько раз или отсутствовать вообще.

Предложение "where"

Используйте предложение where для фильтрации элементов из источника данных по одному или нескольким выражениям предиката. У предложения where в следующем примере имеются два предиката.

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

Дополнительные сведения см. в разделе Предложение where (Справочник по C#).

Предложение "orderby"

Используйте предложение orderby, чтобы сортировать результаты в порядке возрастания или убывания. Также можно задать порядок дополнительной сортировки. В следующем примере выполняется основная сортировка объектов country по свойству Area. Затем выполняется дополнительная сортировка по свойству Population.

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

Ключевое слово ascending является необязательным, так как сортировка по умолчанию происходит по возрастанию, если не задан порядок сортировки. Дополнительные сведения см. в разделе Предложение orderby (Справочник по C#).

Предложение "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 (Справочник по C#).

Предложение "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(new char[] { ' ' })[0]
    select firstName;

foreach (string s in queryFirstNames)
    Console.Write(s + " ");
//Output: Svetlana Claire Sven Cesar

Дополнительные сведения см. в разделе Предложение let (справочник по C#).

Вложенные запросы в выражении запроса

Предложение запроса может само содержать выражение запроса, которое иногда называют вложенным запросом. Каждый вложенный запрос начинается собственным предложением from, которое может указывать на источник данных, отличный от источника данных первого предложения from. Например, в следующем запросе показано выражение запроса, которое используется в операторе "select" для извлечения результатов операции группирования.

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()
    };

Дополнительные сведения см. в разделе Практическое руководство. Вложенный запрос в операции группирования (Руководство по программированию на C#).

См. также

Основные понятия

Руководство по программированию на C#

Выражения запросов LINQ (Руководство по программированию на C#)

Общие сведения о стандартных операторах запроса

Другие ресурсы

LINQ

Ключевые слова запроса (Справочник по C#)