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


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

В этом руководстве вы создадите источник данных и напишите несколько запросов LINQ. Вы можете поэкспериментировать с выражениями запроса и увидеть различия в результатах. В этом пошаговом руководстве показаны функции языка C#, используемые для записи выражений запросов LINQ. Вы можете следить за процессом, создавать приложение и самостоятельно экспериментировать с запросами. В этой статье предполагается, что вы установили последний пакет SDK для .NET. В противном случае перейдите на страницу загрузки .NET и установите последнюю версию на компьютере.

Сначала создайте приложение. В консоли введите следующую команду:

dotnet new console -o WalkthroughWritingLinqQueries

Или, если вы предпочитаете Visual Studio, создайте консольное приложение с именем WalkthroughWritingLinqQueries.

Создание источника данных в памяти

Первым шагом является создание источника данных для запросов. Источник данных для запросов — это простой список Student записей. Каждая Student запись имеет имя, фамилию и массив целых чисел, который представляет их оценки тестов в классе. Добавьте новый файл с именем students.cs и скопируйте следующий код в этот файл:

namespace WalkthroughWritingLinqQueries;

public record Student(string First, string Last, int ID, int[] Scores);

Обратите внимание на следующие характеристики:

  • Запись Student состоит из автоматически реализованных свойств.
  • Каждый студент в списке инициализируется основным конструктором.
  • Последовательность оценок для каждого студента инициализируется основным конструктором.

Затем создайте последовательность записей Student , которые служат источником этого запроса. Откройте Program.cs и удалите следующий стандартный код:

// See https://aka.ms/new-console-template for more information
Console.WriteLine("Hello, World!");

Замените его следующим кодом, который создает последовательность записей Student :

using WalkthroughWritingLinqQueries;

// Create a data source by using a collection initializer.
IEnumerable<Student> students =
[
    new Student(First: "Svetlana", Last: "Omelchenko", ID: 111, Scores: [97, 92, 81, 60]),
    new Student(First: "Claire",   Last: "O'Donnell",  ID: 112, Scores: [75, 84, 91, 39]),
    new Student(First: "Sven",     Last: "Mortensen",  ID: 113, Scores: [88, 94, 65, 91]),
    new Student(First: "Cesar",    Last: "Garcia",     ID: 114, Scores: [97, 89, 85, 82]),
    new Student(First: "Debra",    Last: "Garcia",     ID: 115, Scores: [35, 72, 91, 70]),
    new Student(First: "Fadi",     Last: "Fakhouri",   ID: 116, Scores: [99, 86, 90, 94]),
    new Student(First: "Hanying",  Last: "Feng",       ID: 117, Scores: [93, 92, 80, 87]),
    new Student(First: "Hugo",     Last: "Garcia",     ID: 118, Scores: [92, 90, 83, 78]),

    new Student("Lance",   "Tucker",      119, [68, 79, 88, 92]),
    new Student("Terry",   "Adams",       120, [99, 82, 81, 79]),
    new Student("Eugene",  "Zabokritski", 121, [96, 85, 91, 60]),
    new Student("Michael", "Tucker",      122, [94, 92, 91, 91])
];
  • Последовательность студентов инициализируется коллекционным выражением.
  • Тип Student записи содержит статический список всех учащихся.
  • Некоторые вызовы конструктора используют именованные аргументы для уточнения того, какой аргумент соответствует параметру конструктора.

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

Создание запроса

Затем создайте первый запрос. При выполнении запроса создается список всех учащихся, оценка которых на первом тесте превышает 90. Так как выбран весь Student объект, тип запроса .IEnumerable<Student> Хотя код также может использовать неявную типизацию с помощью ключевого слова var, явная типизация используется для четкой иллюстрации результатов. (Дополнительные сведения см. в varразделе "Неявно типизированные локальные переменные".) Добавьте следующий код в Program.cs после кода, создающего последовательность учащихся:

// Create the query.
// The first line could also be written as "var studentQuery ="
IEnumerable<Student> studentQuery =
    from student in students
    where student.Scores[0] > 90
    select student;

Переменная диапазона запроса student служит ссылкой на каждый Student в источнике, предоставляя доступ к членам каждого объекта.

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

Теперь напишите foreach цикл, который приводит к выполнению запроса. Доступ к каждому элементу в возвращаемой последовательности осуществляется через переменную итерации в цикле foreach . Тип этой переменной — Student, и тип переменной запроса совместим — IEnumerable<Student>. После добавления следующего кода выполните сборку и запустите приложение, чтобы просмотреть результаты в окне консоли .

// Execute the query.
// var could be used here also.
foreach (Student student in studentQuery)
{
    Console.WriteLine($"{student.Last}, {student.First}");
}

// Output:
// Omelchenko, Svetlana
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael

Чтобы дополнительно уточнить запрос, можно объединить несколько логических условий в предложении where . Следующий код добавляет условие, чтобы запрос возвращал тех учащихся, чьи первые оценки были более 90 и чьи последние оценки были менее 80. Условие where должно соответствовать следующему коду.

where student.Scores[0] > 90 && student.Scores[3] < 80  

Попробуйте предыдущее where предложение или поэкспериментируйте с другими условиями фильтра. Для получения дополнительной информации см. раздел , где упоминается условие.

Упорядочить результаты запроса

Проще проверить результаты, если они находятся в каком-то порядке. Вы можете упорядочить возвращаемую последовательность любым доступным полем в исходных элементах. Например, следующее orderby предложение упорядочивает результаты в алфавитном порядке от A до Z в соответствии с именем семьи каждого учащегося. Добавьте следующее orderby предложение в запрос сразу после инструкции where и перед инструкцией select :

orderby student.Last ascending

Теперь измените orderby предложение таким образом, чтобы оно упорядочивало результаты в обратном порядке по оценке на первом тесте, от самой высокой оценки к самой низкой.

orderby student.Scores[0] descending

Измените WriteLine строку формата, чтобы увидеть оценки:

Console.WriteLine($"{student.Last}, {student.First} {student.Scores[0]}");

Дополнительные сведения см. в предложении orderby .

Группирование результатов

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

IEnumerable<IGrouping<char, Student>> studentQuery =
    from student in students
    group student by student.Last[0];

Тип запроса изменился. Теперь он создает последовательность групп с типом char в качестве ключа и последовательностью Student объектов. Код в цикле foreach выполнения также должен измениться:

foreach (IGrouping<char, Student> studentGroup in studentQuery)
{
    Console.WriteLine(studentGroup.Key);
    foreach (Student student in studentGroup)
    {
        Console.WriteLine($"   {student.Last}, {student.First}");
    }
}
// Output:
// O
//   Omelchenko, Svetlana
//   O'Donnell, Claire
// M
//   Mortensen, Sven
// G
//   Garcia, Cesar
//   Garcia, Debra
//   Garcia, Hugo
// F
//   Fakhouri, Fadi
//   Feng, Hanying
// T
//   Tucker, Lance
//   Tucker, Michael
// A
//   Adams, Terry
// Z
//   Zabokritski, Eugene

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

Явное кодирование IEnumerablesIGroupings может быстро стать утомительным. Создание одного и того же запроса и foreach цикла гораздо удобнее с помощью var. Ключевое слово var не изменяет типы ваших объектов; оно просто указывает компилятору выводить типы. Измените тип studentQuery и тип переменной итерации group на var, затем повторно выполните запрос. В внутреннем foreach цикле переменная итерации по-прежнему вводится как Student, и запрос работает как раньше. Измените переменную итерации student на var и снова запустите запрос. Вы видите, что вы получаете точно те же результаты.

IEnumerable<IGrouping<char, Student>> studentQuery =
    from student in students
    group student by student.Last[0];

foreach (IGrouping<char, Student> studentGroup in studentQuery)
{
    Console.WriteLine(studentGroup.Key);
    foreach (Student student in studentGroup)
    {
        Console.WriteLine($"   {student.Last}, {student.First}");
    }
}

Дополнительные сведения см. в varразделе "Неявно типизированные локальные переменные".

Упорядочить группы по значению ключа

Группы в предыдущем запросе не в алфавитном порядке. После group условия можно указать orderby условие. Но для использования orderby предложения сначала требуется идентификатор, который служит ссылкой на группы, созданные предложением group . Вы указываете идентификатор, используя ключевое слово into, следующим образом:

var studentQuery4 =
    from student in students
    group student by student.Last[0] into studentGroup
    orderby studentGroup.Key
    select studentGroup;

foreach (var groupOfStudents in studentQuery4)
{
    Console.WriteLine(groupOfStudents.Key);
    foreach (var student in groupOfStudents)
    {
        Console.WriteLine($"   {student.Last}, {student.First}");
    }
}

// Output:
//A
//   Adams, Terry
//F
//   Fakhouri, Fadi
//   Feng, Hanying
//G
//   Garcia, Cesar
//   Garcia, Debra
//   Garcia, Hugo
//M
//   Mortensen, Sven
//O
//   Omelchenko, Svetlana
//   O'Donnell, Claire
//T
//   Tucker, Lance
//   Tucker, Michael
//Z
//   Zabokritski, Eugene

Выполните этот запрос, и группы теперь отсортированы в алфавитном порядке.

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

// This query returns those students whose
// first test score was higher than their
// average score.
var studentQuery5 =
    from student in students
    let totalScore = student.Scores[0] + student.Scores[1] +
        student.Scores[2] + student.Scores[3]
    where totalScore / 4 < student.Scores[0]
    select $"{student.Last}, {student.First}";

foreach (string s in studentQuery5)
{
    Console.WriteLine(s);
}

// Output:
// Omelchenko, Svetlana
// O'Donnell, Claire
// Mortensen, Sven
// Garcia, Cesar
// Fakhouri, Fadi
// Feng, Hanying
// Garcia, Hugo
// Adams, Terry
// Zabokritski, Eugene
// Tucker, Michael

Дополнительные сведения см. в статье о положенииlet.

Использование синтаксиса метода в выражении запроса

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

var studentQuery =
    from student in students
    let totalScore = student.Scores[0] + student.Scores[1] +
        student.Scores[2] + student.Scores[3]
    select totalScore;

double averageScore = studentQuery.Average();
Console.WriteLine($"Class average score = {averageScore}");

// Output:
// Class average score = 334.166666666667

Выполните преобразование или проекцию в операторе select

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

IEnumerable<string> studentQuery =
    from student in students
    where student.Last == "Garcia"
    select student.First;

Console.WriteLine("The Garcias in the class are:");
foreach (string s in studentQuery)
{
    Console.WriteLine(s);
}

// Output:
// The Garcias in the class are:
// Cesar
// Debra
// Hugo

Код, приведенный ранее в этом пошаговом руководстве, указал, что средняя оценка класса составляет примерно 334. Чтобы создать последовательность Students, общая оценка которой выше среднего по классу, вместе с их Student ID, можно использовать анонимный тип в операторе select.

var aboveAverageQuery =
    from student in students
    let x = student.Scores[0] + student.Scores[1] +
        student.Scores[2] + student.Scores[3]
    where x > averageScore
    select new { id = student.ID, score = x };

foreach (var item in aboveAverageQuery)
{
    Console.WriteLine("Student ID: {0}, Score: {1}", item.id, item.score);
}

// Output:
// Student ID: 113, Score: 338
// Student ID: 114, Score: 353
// Student ID: 116, Score: 369
// Student ID: 117, Score: 352
// Student ID: 118, Score: 343
// Student ID: 120, Score: 341
// Student ID: 122, Score: 368