Поделиться через


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

В этой статье представлены основные понятия, связанные с выражениями запросов в 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)
  • Обзор стандартных операторов запросов