Compartilhar via


Tutorial: Escrever consultas em C# usando consulta integrada à linguagem (LINQ)

Neste tutorial, você cria uma fonte de dados e escreve várias consultas LINQ. Você pode experimentar as expressões de consulta e ver as diferenças nos resultados. Este passo a passo demonstra os recursos da linguagem C# que são usados para escrever expressões de consulta LINQ. Você pode acompanhar e criar o aplicativo e experimentar as consultas por conta própria. Este artigo pressupõe que você tenha instalado o SDK .NET mais recente. Caso contrário, vá para a página Downloads do .NET e instale a versão mais recente em sua máquina.

Primeiro, crie o aplicativo. No console, digite o seguinte comando:

dotnet new console -o WalkthroughWritingLinqQueries

Ou, se preferir o Visual Studio, crie um novo aplicativo de console chamado WalkthroughWritingLinqQueries.

Criar uma fonte de dados na memória

A primeira etapa é criar uma fonte de dados para suas consultas. A fonte de dados para as consultas é uma lista simples de Student registros. Cada Student registro tem um nome, nome de família e uma matriz de inteiros que representa suas pontuações de teste na classe. Adicione um novo arquivo chamado students.cs e copie o seguinte código para esse arquivo:

namespace WalkthroughWritingLinqQueries;

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

Observe as seguintes características:

  • O Student registro consiste em propriedades implementadas automaticamente.
  • Cada aluno na lista é inicializado com o construtor primário.
  • A sequência de pontuações para cada aluno é inicializada com um construtor primário.

Em seguida, crie uma sequência de Student registros que sirva como fonte dessa consulta. Abra Program.cs e remova o seguinte código clichê:

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

Substitua-o pelo seguinte código que cria uma sequência de Student registros:

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])
];
  • A sequência de estudantes é inicializada com uma expressão de coleção.
  • O Student tipo de registro contém a lista estática de todos os alunos.
  • Algumas das chamadas do construtor usam argumentos nomeados para esclarecer qual argumento corresponde a qual parâmetro do construtor.

Tente adicionar mais alguns alunos com pontuações de teste diferentes à lista de alunos para se familiarizar mais com o código até agora.

Criar a consulta

Em seguida, crie sua primeira consulta. A sua consulta, quando a executa, produz uma lista de todos os alunos cuja pontuação no primeiro teste foi superior a 90. Como todo o Student objeto está selecionado, o tipo da consulta é IEnumerable<Student>. Embora o código também possa usar a digitação implícita usando a palavra-chave var , a digitação explícita é usada para ilustrar claramente os resultados. (Para informações adicionais sobre var, consulte Variáveis locais digitadas implicitamente.) Adicione o seguinte código ao Program.cs, após o código que cria a sequência de alunos.

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

A variável de intervalo da consulta, student, serve como referência para cada Student um na fonte, fornecendo acesso de membro para cada objeto.

Executar a consulta

Agora escreva o foreach loop que faz com que a consulta seja executada. Cada elemento na sequência retornada é acessado através da variável de iteração no foreach loop. O tipo desta variável é Student, e o tipo da variável de consulta é compatível, IEnumerable<Student>. Depois de adicionar o código a seguir, compile e execute o aplicativo para ver os resultados na janela Console .

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

Para refinar ainda mais a consulta, você pode combinar várias condições booleanas where na cláusula. O código a seguir adiciona uma condição para que a consulta retorne os alunos cuja primeira pontuação foi superior a 90 e cuja última pontuação foi inferior a 80. A where cláusula deve assemelhar-se ao código seguinte.

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

Experimente a cláusula anterior where ou experimente outras condições de filtro. Para obter mais informações, consulte cláusula WHERE.

Ordenar os resultados da consulta

É mais fácil digitalizar os resultados se eles estiverem em algum tipo de ordem. Você pode ordenar a sequência retornada por qualquer campo acessível nos elementos de origem. Por exemplo, a cláusula a seguir orderby ordena os resultados em ordem alfabética de A a Z de acordo com o nome de família de cada aluno. Adicione a seguinte orderby cláusula à sua consulta, logo após a where instrução e antes da select instrução:

orderby student.Last ascending

Agora altere a orderby cláusula para que ela ordene os resultados em ordem inversa de acordo com a pontuação no primeiro teste, da maior pontuação para a menor pontuação.

orderby student.Scores[0] descending

Altere a cadeia de caracteres de WriteLine formato para que você possa ver as pontuações:

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

Para obter mais informações, consulte a cláusula de ordenação por .

Agrupar os resultados

O agrupamento é um recurso poderoso em expressões de consulta. Uma consulta com uma cláusula de grupo produz uma sequência de grupos, e cada grupo em si contém uma Key e uma sequência que consiste em todos os membros desse grupo. A nova consulta a seguir agrupa os alunos usando a primeira letra do nome da família como chave.

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

O tipo da consulta foi alterado. Ele agora produz uma sequência de grupos que têm um char tipo como chave e uma sequência de Student objetos. O código no loop de execução foreach também deve mudar.

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

Execute o aplicativo e visualize os resultados na janela Console . Para obter mais informações, consulte o grupo de cláusulas .

A codificação IEnumerables explícita de IGroupings pode rapidamente tornar-se entediante. Escreva a mesma consulta e execute o loop de forma muito mais conveniente usando var. A var palavra-chave não altera os tipos de seus objetos, apenas instrui o compilador a inferir os tipos. Altere o tipo de studentQuery e a variável de iteração group para var e execute novamente a consulta. No loop interno foreach , a variável de iteração ainda é digitada como Student, e a consulta funciona como antes. Altere a student variável de iteração para var e execute a consulta novamente. Você vê que obtém exatamente os mesmos resultados.

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

Para obter mais informações sobre var, consulte Variáveis locais tipadas implicitamente.

Ordenar os grupos pelo seu valor-chave

Os grupos na consulta anterior não estão em ordem alfabética. Você pode fornecer uma orderby cláusula após a group cláusula. Mas para usar uma orderby cláusula, primeiro você precisa de um identificador que sirva de referência para os grupos criados pela group cláusula. Você fornece o identificador usando a palavra-chave into , da seguinte maneira:

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

Execute essa consulta e os grupos agora são classificados em ordem alfabética.

Você pode usar a let palavra-chave para introduzir um identificador para qualquer resultado de expressão na expressão de consulta. Esse identificador pode ser uma conveniência, como no exemplo a seguir. Ele também pode melhorar o desempenho armazenando os resultados de uma expressão para que ela não precise ser calculada várias vezes.

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

Para obter mais informações, consulte o artigo sobre a let cláusula.

Usar sintaxe de método em uma expressão de consulta

Conforme descrito em Sintaxe de consulta e sintaxe de método no LINQ, algumas operações de consulta só podem ser expressas usando sintaxe de método. O código a seguir calcula a pontuação total de cada Student na sequência de origem e, em seguida, chama o Average() método nos resultados dessa consulta para calcular a média de pontuação da classe.

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

Para transformar ou projetar na cláusula de seleção

É comum que uma consulta produza uma sequência cujos elementos diferem dos elementos nas sequências de origem. Exclua ou comente sua consulta anterior e loop de execução e substitua-o pelo código a seguir. A consulta retorna uma sequência de cadeias de caracteres (não Students), e este facto reflete-se no loop 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

O código anterior neste passo a passo indicou que a pontuação média da classe é de aproximadamente 334. Para produzir uma sequência de Students cuja pontuação total é maior do que a média da classe, juntamente com os seus Student ID, você pode usar um tipo anônimo na instrução 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