쿼리 식 기본 사항

이 문서에서는 C#의 쿼리 식과 관련된 기본 개념을 소개합니다.

쿼리란 무엇이며 쿼리의 기능은 무엇인가요?

쿼리는 지정된 데이터 소스(또는 소스)에서 검색할 데이터 및 반환된 데이터에 필요한 모양과 구성을 설명하는 지침 집합입니다. 쿼리와 쿼리에서 생성되는 결과는 다릅니다.

일반적으로 소스 데이터는 논리적으로 같은 종류의 요소 시퀀스로서 구성됩니다. 예를 들어 SQL 데이터베이스 테이블에는 행 시퀀스가 포함됩니다. XML 파일에는 XML 요소의 "시퀀스"가 있습니다(XML 요소는 트리 구조에서 계층적으로 구성되지만). 메모리 내 컬렉션은 개체의 시퀀스를 포함합니다.

애플리케이션의 관점에서 원본 데이터의 특정 형식과 구조는 중요하지 않습니다. 애플리케이션에는 소스 데이터가 항상 IEnumerable<T> 또는 IQueryable<T> 컬렉션으로 표시됩니다. 예를 들어 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}";
    
  • 소스 데이터에 대한 다음과 같은 singleton 값을 검색합니다.

    • 특정 조건과 일치하는 요소의 수.

    • 최대값 또는 최소값을 가지고 있는 요소.

    • 조건과 일치하는 첫 번째 요소 또는 지정된 요소 집합에서 특정 값의 합계. 예를 들어 다음 쿼리는 scores 정수 배열에서 80보다 큰 점수를 반환합니다.

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

이전 예에서는 highScoresQuery에 의해 반환된 요소 수를 확인하려면 Count가 결과를 반복해야 하기 때문에 Count에 대한 호출에서 쿼리가 실행됩니다.

쿼리 식이란 무엇입니까?

쿼리 식은 쿼리 구문으로 표현되는 쿼리입니다. 쿼리 식은 고급 언어 구문으로서, 이는 다른 식과 같으며 C# 식이 유효한 모든 컨텍스트에서 사용할 수 있습니다. 쿼리 식은 SQL 또는 XQuery와 유사한 선언적 구문으로 작성된 절 집합으로 구성됩니다. 각 절에는 하나 이상의 C# 식이 포함되며, 이러한 식 자체는 쿼리 식이거나 쿼리 식을 포함할 수 있습니다.

쿼리 식은 from 절로 시작하고 select 또는 group 절로 끝나야 합니다. 첫 번째 from 절과 마지막 select 또는 group 절 사이에는 where, orderby, join, let 및 심지어 다른 from 절과 같은 선택적 절 중 하나 이상을 포함할 수 있습니다. 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 루프에서 반복될 수 있습니다. 자체적 수정 또는 데이터 원본이 모두 수정되지 않은 한 동일한 결과를 생성합니다.

쿼리 변수는 쿼리 구문 또는 메서드 구문으로 표현되는 쿼리 또는 둘의 조합을 저장할 수 있습니다. 다음 예제에서는 queryMajorCitiesqueryMajorCities2 모두 쿼리 변수입니다.

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 > 100000
    select city;

// Execute the query to produce the results
foreach (City city in queryMajorCities)
{
    Console.WriteLine(city);
}

// Output:
// City { Population = 120000 }
// City { Population = 112000 }
// City { Population = 150340 }

// Method-based syntax
IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);

반면에 다음 두 예제에서는 각각 쿼리를 사용하여 초기화되더라도 쿼리 변수가 아닌 변수를 보여 줍니다. 이들은 결과를 저장하기 때문에 쿼리 변수가 아닙니다.

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

쿼리 변수의 명시적 형식 및 암시적 형식

이 문서는 일반적으로 쿼리 변수와 select 절 간의 형식 관계를 표시하기 위해 쿼리 변수의 명시적 형식을 제공합니다. 그러나 var 키워드를 사용하여 컴파일 시간에 쿼리 변수(또는 다른 지역 변수)의 형식을 추론하도록 컴파일러에 지시할 수도 있습니다. 예를 들어 이 문서의 앞에 표시된 쿼리 예제는 암시적 입력을 사용하여 표현할 수도 있습니다.

var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

앞의 예제에서 var의 사용은 선택 사항입니다. queryCities(은)는 암시적으로 또는 명시적으로 입력되었는지 여부에 관계없이 IEnumerable<City>입니다.

쿼리 식 시작

쿼리 식은 from 절로 시작해야 합니다. 쿼리 식은 범위 변수와 함께 데이터 소스를 지정합니다. 범위 변수는 소스 시퀀스가 트래버스할 때 소스 시퀀스의 각 연속 요소를 나타냅니다. 범위 변수는 데이터 소스의 요소 형식을 기반으로 강력하게 형식이 지정됩니다. 다음 예제에서 countriesCountry 개체의 배열이므로 범위 변수의 형식도 Country로 지정됩니다. 범위 변수의 형식은 강력하게 지정되므로 사용 가능한 형식 멤버에 액세스하기 위해 점 연산자를 사용할 수 있습니다.

IEnumerable<Country> countryAreaQuery =
    from country in countries
    where country.Area > 500000 //sq km
    select country;

쿼리가 세미콜론 또는 continuation 절로 종료될 때까지 범위 변수는 범위 내에 있습니다.

쿼리 식에는 여러 from 절이 포함될 수 있습니다. 소스 시퀀스의 각 요소가 컬렉션이거나 컬렉션을 포함하는 경우 더 많은 from 절을 사용합니다. 예를 들어 Country 개체의 컬렉션이 있으며, 각각에 Cities라는 이름의 City 개체 컬렉션이 포함되어 있다고 가정해 보겠습니다. 각 Country에서 City 개체를 쿼리하려면 다음과 같이 두 개의 from 절을 사용합니다.

IEnumerable<City> cityQuery =
    from country in countries
    from city in country.Cities
    where city.Population > 10000
    select city;

자세한 내용은 from 절을 참조하세요.

쿼리 식 종료

쿼리 식은 group 절 또는 select 절로 끝나야 합니다.

group 절

지정한 키로 구성되는 그룹의 시퀀스를 생성하려면 group 절을 사용합니다. 키의 데이터 형식은 무엇이든 가능합니다. 예를 들어, 다음 쿼리는 하나 이상의 Country 개체를 포함하며 키가 char 형식이고 값은 국가 이름의 첫 번째 문자인 그룹의 시퀀스를 만듭니다.

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

그룹화에 대한 자세한 내용은 group 절을 참조하세요.

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 키워드를 사용하여 쿼리를 저장하는 임시 식별자를 만들 수 있습니다. 그룹화 또는 선택 작업 후에 쿼리에 대해 추가 쿼리 작업을 수행해야 하는 경우 into 절을 사용합니다. 다음 예제에서 countries(은)는 1,000만 범위의 인구에 따라 그룹화됩니다. 이러한 그룹을 만든 후에는 더 많은 절이 일부 그룹을 필터링한 다음 그룹을 오름차순으로 정렬합니다. 이러한 추가 작업을 수행하려면 countryGroup(으)로 표현되는 연속 작업이 필요합니다.

// percentileQuery is an IEnumerable<IGrouping<int, Country>>
var percentileQuery =
    from country in countries
    let percentile = (int)country.Population / 10_000_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)은 선택 사항입니다. 선택적 절은 쿼리 본문에서 0번 또는 여러 번 사용될 수 있습니다.

where 절

하나 이상의 조건자 식을 기반으로 소스 데이터에서 요소를 필터링하려면 where 절을 사용합니다. 다음 예제의 where 절에는 조건자 하나와 조건 두 개가 있습니다.

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

자세한 내용은 where 절을 참조하세요.

orderby 절

결과를 오름차순 또는 내림차순으로 정렬하려면 orderby 절을 사용합니다. 2차 정렬 순서를 지정할 수도 있습니다. 다음 예제에서는 Area 속성을 사용하여 country 개체에 대해 1차 정렬을 수행합니다. 그런 다음 Population 속성을 사용하여 2차 정렬을 수행합니다.

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

ascending 키워드는 선택 사항입니다. 지정된 순서가 없는 경우 기본 정렬 순서입니다. 자세한 내용은 orderby 절을 참조하세요.

join 절

각 요소의 지정된 키 간 동일성 비교에 따라 하나의 데이터 소스의 요소를 다른 데이터 소스의 요소와 연결하거나 결합하려면 join 절을 사용합니다. LINQ에서는 요소의 형식이 서로 다른 개체의 시퀀스에 대해 조인 작업이 수행됩니다. 두 시퀀스를 조인한 후에는 select 또는 group 문을 사용하여 출력 시퀀스에 저장할 요소를 지정해야 합니다. 연결된 각 요소 집합의 속성을 출력 시퀀스의 새 형식으로 결합하는 데에도 무명 형식을 사용할 수 있습니다. 다음 예제는 Category 속성이 categories 문자열 배열의 범주 중 하나와 일치하는 prod 개체를 연결합니다. Category(이)가 categories의 문자열과 일치하지 않는 제품은 필터링됩니다. select 문은 속성을 catprod에서 가져오는 새 형식을 프로젝션합니다.

var categoryQuery =
    from cat in categories
    join prod in products on cat equals prod.Category
    select new
    {
        Category = cat,
        Name = prod.Name
    };

into 키워드를 사용하여 join 작업의 결과를 임시 변수에 저장함으로써 그룹 조인을 수행할 수 있습니다. 자세한 내용은 join 절을 참조하세요.

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

자세한 내용은 그룹화 작업에서 하위 쿼리를 수행하는 방법을 참조하세요.

참고 항목