LINQ 쿼리 소개(C#)

쿼리는 데이터 소스에서 데이터를 검색하는 식입니다. 서로 다른 데이터 원본에는 관계형 데이터베이스용 SQL 및 XML용 XQuery와 같은 다양한 네이티브 쿼리 언어가 있습니다. 개발자는 지원해야 하는 데이터 원본 또는 데이터 형식의 각 형식에 대한 새 쿼리 언어를 학습해야 합니다. LINQ는 데이터 원본 및 형식의 종류에 일관된 C# 언어 모델을 제공하여 이러한 상황을 간소화합니다. LINQ 쿼리에서는 항상 C# 개체를 사용합니다. 동일한 기본 코딩 패턴을 사용하여 LINQ 공급자를 사용할 수 있는 경우 XML 문서, SQL 데이터베이스, .NET 컬렉션 및 기타 형식의 데이터를 쿼리하고 변환합니다.

쿼리 작업의 세 부분

모든 LINQ 쿼리 작업은 다음과 같은 세 가지 고유한 작업으로 구성됩니다.

  1. 데이터 소스 가져오기.
  2. 쿼리 만들기.
  3. 쿼리를 실행합니다.

다음 예제에서는 쿼리 작업의 세 부분이 소스 코드로 표현되는 방식을 보여 줍니다. 예제에서는 편의상 정수 배열을 데이터 소스로 사용하지만 다른 데이터 소스에도 동일한 개념이 적용됩니다. 이 예제는 이 문서의 나머지 부분 전체에서 참조됩니다.

// The Three Parts of a LINQ Query:
// 1. Data source.
int[] numbers = [ 0, 1, 2, 3, 4, 5, 6 ];

// 2. Query creation.
// numQuery is an IEnumerable<int>
var numQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

// 3. Query execution.
foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

다음 그림에서는 전체 쿼리 작업을 보여 줍니다. LINQ에서 쿼리 실행은 쿼리 자체와 다릅니다. 즉, 쿼리 변수를 만들어 데이터를 검색하지 않습니다.

Diagram of the complete LINQ query operation.

데이터 소스

이전 예제의 데이터 원본은 제네릭 IEnumerable<T> 인터페이스를 지원하는 배열입니다. 즉, LINQ로 쿼리할 수 있다는 의미입니다. 쿼리가 foreach 문에서 실행되고, foreachIEnumerable 또는 IEnumerable<T>이 필요합니다. IEnumerable<T> 또는 제네릭 IQueryable<T> 같은 파생된 인터페이스를 지원하는 형식을 쿼리 가능 형식이라고 합니다.

쿼리 가능 형식은 LINQ 데이터 소스로 사용하기 위해 수정하거나 특별하게 처리할 필요가 없습니다. 원본 데이터가 쿼리 가능 형식으로 메모리에 아직 없는 경우 LINQ 공급자는 이를 나타내야 합니다. 예를 들어 LINQ to XML은 XML 문서를 쿼리 가능 XElement 형식으로 로드합니다.

// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");

EntityFramework를 사용하여 C# 클래스와 데이터베이스 스키마 간에 개체 관계형 매핑을 만듭니다. 개체에 대한 쿼리를 작성하고 런타임에 EntityFramework가 데이터베이스와의 통신을 처리합니다. 다음 예에서 Customers는 데이터베이스의 특정 테이블을 나타내며, IQueryable<T> 쿼리 결과 형식은 IEnumerable<T>에서 파생됩니다.

Northwnd db = new Northwnd(@"c:\northwnd.mdf");

// Query for customers in London.
IQueryable<Customer> custQuery =
    from cust in db.Customers
    where cust.City == "London"
    select cust;

특정 형식의 데이터 소스를 만드는 방법에 대한 자세한 내용은 다양한 LINQ 공급자에 대한 설명서를 참조하세요. 그러나 기본 규칙은 간단합니다. LINQ 데이터 원본은 제네릭 IEnumerable<T> 인터페이스를 지원하는 모든 개체이거나, 거기서 상속되는 인터페이스(일반적으로 IQueryable<T>)입니다.

참고 항목

제네릭이 아닌 IEnumerable 인터페이스를 지원하는 ArrayList 같은 형식은 LINQ 데이터 소스로도 사용됩니다. 자세한 내용은 LINQ를 사용하여 ArrayList를 쿼리하는 방법(C#)을 참조하세요.

쿼리

쿼리는 데이터 소스 또는 소스에서 검색할 정보를 지정합니다. 선택적으로 쿼리는 해당 정보가 반환되기 전에 정렬, 그룹화 및 형성되는 방법도 지정합니다. 쿼리는 쿼리 변수에 저장되고 쿼리 식으로 초기화됩니다. C# 쿼리 구문을 사용하여 쿼리를 작성합니다.

이전 예제의 쿼리는 정수 배열에서 모든 짝수를 반환합니다. 쿼리 식에는 from, whereselect의 세 가지 절이 포함됩니다. (SQL에 익숙한 경우 절의 순서가 SQL의 순서와 반대임을 알고 있을 것입니다.) from 절은 데이터 소스를 지정하고 where 절은 필터를 적용하며 select 절은 반환되는 요소의 형식을 지정합니다. 이 섹션에서는 모든 쿼리 절에 대해 자세히 논의합니다. 여기에서 중요한 점은 LINQ에서 쿼리 변수 자체는 아무 작업도 수행하지 않고 데이터를 반환하지 않는다는 것입니다. 나중에 쿼리가 실행될 때 결과를 생성하는 데 필요한 정보를 저장합니다. 쿼리가 생성되는 방법에 대한 자세한 내용은 표준 쿼리 연산자 개요(C#)를 참조하세요.

참고 항목

쿼리는 메서드 구문을 사용하여 표현할 수도 있습니다. 자세한 내용은 LINQ의 쿼리 구문 및 메서드 구문을 참조하세요.

쿼리 실행

지연된 실행

쿼리 변수 자체는 쿼리 명령만 저장합니다. 실제 쿼리 실행은 foreach 문에서 쿼리 변수가 반복될 때까지 지연됩니다. 이 개념을 지연된 실행이라고 하며 다음 예제에서 보여 줍니다.

foreach (int num in numQuery)
{
    Console.Write("{0,1} ", num);
}

foreach 문은 쿼리 결과가 검색되는 위치이기도 합니다. 예를 들어 이전 쿼리에서 반복 변수 num은 반환된 시퀀스에서 각 값을 한 번에 하나씩 저장합니다.

쿼리 변수 자체는 쿼리 결과를 보유하지 않으므로 반복적으로 실행하여 업데이트된 데이터를 검색할 수 있습니다. 예를 들어 별도의 애플리케이션에서 지속적으로 업데이트되는 데이터베이스가 있을 수 있습니다. 애플리케이션에서 최신 데이터를 검색하는 하나의 쿼리를 만들 수 있으며, 업데이트된 결과를 검색하기 위해 간격을 두고 실행할 수 있습니다.

즉시 실행 강제 적용

소스 요소 범위에 대해 집계 함수를 수행하는 쿼리는 먼저 해당 요소를 반복해야 합니다. 이러한 쿼리의 예로 Count, Max, AverageFirst가 있습니다. 이러한 메서드는 쿼리 자체가 결과를 반환하기 위해 foreach(을)를 사용해야 하므로 명시적 foreach 문 없이 실행됩니다. 이러한 쿼리는 IEnumerable 컬렉션이 아닌 단일 값을 반환합니다. 다음 쿼리는 소스 배열에서 짝수의 개수를 반환합니다.

var evenNumQuery =
    from num in numbers
    where (num % 2) == 0
    select num;

int evenNumCount = evenNumQuery.Count();

모든 쿼리를 즉시 실행하고 그 결과를 캐시하기 위해 ToList 또는 ToArray 메서드를 호출할 수 있습니다.

List<int> numQuery2 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToList();

// or like this:
// numQuery3 is still an int[]

var numQuery3 =
    (from num in numbers
        where (num % 2) == 0
        select num).ToArray();

또한 foreach 루프를 쿼리 식 바로 다음에 배치하여 강제로 실행할 수 있습니다. 그러나 ToList 또는 ToArray를 호출하여 단일 컬렉션 개체에서 모든 데이터를 캐시할 수도 있습니다.

참고 항목