쿼리 식 기본 사항

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

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

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

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

애플리케이션의 관점에서 소스 데이터의 특정 형식과 구조는 중요하지 않습니다. 애플리케이션에는 소스 데이터가 항상 IEnumerable<T> 또는 IQueryable<T> 컬렉션으로 표시됩니다. 예를 들어 LINQ to XML 원본 데이터는 .로 표시됩니다IEnumerableXElement<>.

이 소스 시퀀스에서 쿼리는 다음 세 가지 중 하나를 수행할 수 있습니다.

  • 개별 요소를 수정하지 않고 요소의 하위 집합을 검색하여 새 시퀀스를 생성합니다. 그런 다음 쿼리는 다음 예제와 같이 여러 가지 방법으로 반환된 시퀀스를 정렬 또는 그룹화할 수 있습니다(scoresint[]로 가정).

    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보다 큰 점수를 반환합니다.

      int highScoreCount = (
          from score in scores
          where score > 80
          select score
      ).Count();
      

      이전 예제에서는 Count 메서드를 호출하기 전에 쿼리 식 주변에 괄호를 사용했습니다. 구체적인 결과를 저장하기 위한 새 변수를 사용하여 식을 표현할 수도 있습니다. 이 기술은 결과를 저장하는 쿼리와 별도로 쿼리를 저장하는 변수를 유지하기 때문에 더 읽기 쉽습니다.

      IEnumerable<int> highScoresQuery3 =
          from score in scores
          where score > 80
          select score;
      
      int scoreCount = highScoresQuery3.Count();
      

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

쿼리 식이란 무엇입니까?

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

쿼리 식은 from 절로 시작하고 select 또는 group 절로 끝나야 합니다. 첫 번째 from 절과 마지막 select 또는 group 절 사이에는 다음과 같은 선택적 절 중 하나 이상을 포함할 수 있습니다. fromselect, group, let 및 추가 을 포함할 수 있습니다. into 키워드를 사용하여 동일한 쿼리 식에서 추가 쿼리 절의 원본으로 사용하거나 group 절의 결과를 사용하도록 설정할 수도 있습니다.

쿼리 변수

LINQ에서 쿼리 변수는 쿼리의 결과 대신 쿼리를 저장하는 변수입니다. 좀 더 구체적으로, 쿼리 변수는 항상 foreach 문이나 IEnumerator.MoveNext 메서드에 대한 직접 호출에서 반복될 때 요소 시퀀스를 생성하는 Enumerable 형식입니다.

다음 코드 예제는 하나의 데이터 소스, 하나의 필터링 절, 하나의 순서 지정 절, 변환 없는 원본 요소로 간단한 쿼리 식을 보여 줍니다. 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 (int testScore in scoreQuery)
{
    Console.WriteLine(testScore);
}

// Output: 93 90 82 82

이전 예제 scoreQuery 에서는 scoreQuery라고도 합니다. 쿼리 변수는 foreach 루프에서 생성되는 실제 결과 데이터를 저장하지 않습니다. 그리고 foreach 문이 실행될 때, 쿼리 변수 scoreQuery를 통해 쿼리 결과가 반환되지 않습니다. 오히려 반복 변수 testScore를 통해 반환됩니다. scoreQuery 변수는 두 번째 foreach 루프에서 반복될 수 있습니다. 변수 또는 데이터 소스를 수정하지 않는 한 동일한 결과가 생성됩니다.

쿼리 변수는 쿼리 구문이나 메서드 구문 또는 이 두 가지 조합으로 표현된 쿼리를 저장할 수 있습니다. 다음 예제에서는 queryMajorCitiesqueryMajorCities2 모두 쿼리 변수입니다.

//Query syntax
IEnumerable<City> queryMajorCities =
    from city in cities
    where city.Population > 100000
    select city;

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

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

int highestScore = (
    from score in scores
    select score
).Max();

// or split the expression
IEnumerable<int> scoreQuery =
    from score in scores
    select score;

int highScore = scoreQuery.Max();
// the following returns the same result
highScore = scores.Max();
List<City> 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;

List<City> largeCitiesList2 = largeCitiesQuery.ToList();

쿼리를 표현하는 다양한 방법에 대한 자세한 내용은 LINQ의 쿼리 구문 및 메서드 구문을 참조하세요.

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

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

// Use of var is optional here and in all queries.
// queryCities is an IEnumerable<City> just as
// when it is explicitly typed.
var queryCities =
    from city in cities
    where city.Population > 100000
    select city;

자세한 내용은 암시적으로 형식화된 지역 변수LINQ 쿼리 작업의 형식 관계를 참조하세요.

쿼리 식 시작

쿼리 식은 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 원래 요소에 있는 필드의 하위 집합만 포함하는 무명 형식의 시퀀스를 select 합니다. 새 개체는 개체 이니셜라이저를 사용하여 초기화됩니다.

// Here var is required because the query
// produces an anonymous type.
var queryNameAndPop =
    from country in countries
    select new
    {
        Name = country.Name,
        Pop = country.Population
    };

절을 사용하여 원본 데이터를 변환하는 select 모든 방법에 대한 자세한 내용은 select을 참조하세요.

into를 사용한 연속

select 또는 group 절에서 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 < 200000 && city.Population > 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 개체를 연결합니다. Categorycategories의 어떠한 문자열과도 일치하지 않는 제품은 필터링됩니다. select 문은 catprod에서 속성을 가져온 새 형식을 프로젝션합니다.

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

작업 결과를 joinjoin 를 사용하여 임시 변수에 저장하여 그룹 조인을 수행할 수도 있습니다. 자세한 내용은 join 절을 참조하세요.

let 절

식의 결과(예: 메서드 호출)를 새 범위 변수에 저장하려면 let 절을 사용합니다. 다음 예제에서 범위 변수 firstNameSplit에서 반환하는 문자열 배열의 첫 번째 요소를 저장합니다.

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

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

참조