다음을 통해 공유


자습서: 언어 통합 쿼리를 사용하여 C#으로 쿼리 작성(LINQ)

이 자습서에서는 데이터 원본을 만들고 여러 LINQ 쿼리를 작성합니다. 쿼리 식을 실험하고 결과의 차이점을 확인할 수 있습니다. 이 연습에서는 LINQ 쿼리 식을 작성하는 데 사용되는 C# 언어 기능을 보여 줍니다. 따라 앱을 빌드하고 쿼리를 직접 실험할 수 있습니다. 이 문서에서는 최신 .NET SDK를 설치했다고 가정합니다. 그렇지 않은 경우 .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로 사전순으로 정렬합니다. where 문 바로 뒤와 select 문 앞에 다음 orderby 절을 쿼리에 추가합니다.

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에서 명시적으로 코딩하면 빠르게 지루하게 느껴질 수 있습니다. var사용하여 동일한 쿼리 및 foreach 루프를 훨씬 더 편리하게 작성합니다. 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