다음을 통해 공유


C의 LINQ 쿼리 소개#

쿼리는 데이터 원본에서 데이터를 검색하는 식입니다. 서로 다른 데이터 원본에는 관계형 데이터베이스용 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에서 쿼리 실행은 쿼리 자체와 다릅니다. 즉, 쿼리 변수를 만들어 데이터를 검색하지 않습니다.

전체 LINQ 쿼리 작업의 다이어그램입니다.

데이터 원본

이전 예제의 데이터 원본은 제네릭 IEnumerable<T> 인터페이스를 지원하는 배열입니다. 이 사실은 LINQ를 사용하여 쿼리할 수 있다는 것을 의미합니다. 쿼리는 foreach 문에서 실행되며, foreach에는 IEnumerable 또는 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>상속되는 인터페이스입니다.

비고

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

쿼리

쿼리는 데이터 원본 또는 원본에서 검색할 정보를 지정합니다. 필요에 따라 쿼리는 반환되기 전에 해당 정보를 정렬, 그룹화 및 모양을 지정합니다. 쿼리는 쿼리 변수에 저장되고 쿼리 식을 사용하여 초기화됩니다. C# 쿼리 구문을 사용하여 쿼리를 작성합니다.

이전 예제의 쿼리는 정수 배열의 모든 짝수 숫자를 반환합니다. 쿼리 식에는 세 개의 절이 있습니다: from, where, 그리고 select. (SQL에 익숙한 경우 절의 순서가 SQL의 순서와 반대로 바뀌는 것을 발견했습니다.) 절은 from 데이터 원본을 지정하고, where 절은 필터를 적용하고, 절은 select 반환된 요소의 형식을 지정합니다. 이 섹션에서는 모든 쿼리 절에 대해 자세히 설명합니다. 지금은 LINQ에서 쿼리 변수 자체가 아무 작업도 수행하지 않고 데이터를 반환하지 않는다는 점이 중요합니다. 나중에 쿼리가 실행될 때 결과를 생성하는 데 필요한 정보만 저장합니다. 쿼리가 생성되는 방법에 대한 자세한 내용은 표준 쿼리 연산자 개요(C#)를 참조하세요.

비고

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

실행 방식별 표준 쿼리 연산자 분류

표준 쿼리 연산자 메서드의 LINQ to Objects 구현은 즉시 실행 또는 지연이라는 두 가지 주요 방법 중 하나로 실행됩니다. 지연된 실행을 사용하는 쿼리 연산자는 스트리밍논스트림의 두 범주로 나눌 수도 있습니다.

즉시

즉시 실행은 데이터 원본을 읽고 작업이 한 번 수행됨을 의미합니다. 스칼라 결과를 반환하는 모든 표준 쿼리 연산자는 즉시 실행됩니다. 이러한 쿼리의 예는 Count, Max, AverageFirst. 쿼리 자체가 결과를 반환하기 위해 foreach을 사용해야 하므로, 이러한 메서드는 명시적 foreach 문 없이 실행됩니다. 이러한 쿼리는 컬렉션이 아닌 IEnumerable 단일 값을 반환합니다. 어떤 쿼리든지 Enumerable.ToList 또는 Enumerable.ToArray 메서드를 사용하여 즉시 실행하도록 강제할 수 있습니다. 즉시 실행은 쿼리 선언이 아니라 쿼리 결과를 재사용합니다. 결과는 한 번 검색된 다음 나중에 사용하기 위해 저장됩니다. 다음 쿼리는 원본 배열의 짝수 수를 반환합니다.

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을(를) 호출하면 단일 컬렉션 객체의 모든 데이터를 캐시합니다.

연기됨

지연된 실행은 쿼리가 선언된 코드의 지점에서 작업이 수행되지 않음을 의미합니다. 이 작업은 쿼리 변수가 열거된 경우에만 수행됩니다(예: 문을 사용하여 foreach ). 쿼리 실행 결과는 쿼리가 정의될 때가 아니라 쿼리가 실행될 때 데이터 원본의 내용에 따라 달라집니다. 쿼리 변수가 여러 번 열거되는 경우 결과가 매번 다를 수 있습니다. 거의 모든 표준 쿼리 연산자는, 반환 형식이 IEnumerable<T> 또는 IOrderedEnumerable<TElement>인 경우, 지연된 방식으로 실행됩니다. 지연 실행은 쿼리 결과가 반복될 때마다 쿼리가 데이터 원본에서 업데이트된 데이터를 가져오기 때문에 쿼리 재사용 기능을 제공합니다. 다음 코드는 지연된 실행의 예를 보여 줍니다.

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

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

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

지연된 실행을 사용하는 쿼리 연산자는 스트리밍 또는 비스트림으로 추가로 분류할 수 있습니다.

스트리밍

스트리밍 연산자는 요소를 생성하기 전에 모든 원본 데이터를 읽을 필요가 없습니다. 실행 시 스트리밍 연산자는 읽을 때 각 원본 요소에 대해 해당 작업을 수행하고 해당하는 경우 요소를 생성합니다. 스트리밍 연산자는 결과 요소를 생성할 수 있을 때까지 원본 요소를 계속 읽습니다. 즉, 둘 이상의 원본 요소를 읽어 하나의 결과 요소를 생성할 수 있습니다.

비스트리밍

비스트림 연산자는 결과 요소를 생성하기 전에 모든 원본 데이터를 읽어야 합니다. 정렬 또는 그룹화와 같은 작업은 이 범주에 속합니다. 실행 시 비스트림 쿼리 연산자는 모든 원본 데이터를 읽고, 데이터 구조에 배치하고, 작업을 수행하고, 결과 요소를 생성합니다.

분류 테이블

다음 표에서는 실행 방법에 따라 각 표준 쿼리 연산자 메서드를 분류합니다.

비고

연산자가 두 열로 표시된 경우 두 개의 입력 시퀀스가 작업에 포함되고 각 시퀀스가 다르게 평가됩니다. 이러한 경우 항상 지연된 스트리밍 방식으로 평가되는 매개 변수 목록의 첫 번째 시퀀스입니다.

표준 쿼리 연산자 반환 형식 즉시 실행 지연된 스트리밍 실행 지연된 비스트리밍 실행
Aggregate TSource
All Boolean
Any Boolean
AsEnumerable IEnumerable<T>
Average 단일 숫자 값
Cast IEnumerable<T>
Concat IEnumerable<T>
Contains Boolean
Count Int32
DefaultIfEmpty IEnumerable<T>
Distinct IEnumerable<T>
ElementAt TSource
ElementAtOrDefault TSource?
Empty IEnumerable<T>
Except IEnumerable<T>
First TSource
FirstOrDefault TSource?
GroupBy IEnumerable<T>
GroupJoin IEnumerable<T>
Intersect IEnumerable<T>
Join IEnumerable<T>
Last TSource
LastOrDefault TSource?
LongCount Int64
Max 단일 숫자 값, TSource 또는 TResult?
Min 단일 숫자 값, TSource 또는 TResult?
OfType IEnumerable<T>
OrderBy IOrderedEnumerable<TElement>
OrderByDescending IOrderedEnumerable<TElement>
Range IEnumerable<T>
Repeat IEnumerable<T>
Reverse IEnumerable<T>
Select IEnumerable<T>
SelectMany IEnumerable<T>
SequenceEqual Boolean
Single TSource
SingleOrDefault TSource?
Skip IEnumerable<T>
SkipWhile IEnumerable<T>
Sum 단일 숫자 값
Take IEnumerable<T>
TakeWhile IEnumerable<T>
ThenBy IOrderedEnumerable<TElement>
ThenByDescending IOrderedEnumerable<TElement>
ToArray TSource[] 배열
ToDictionary Dictionary<TKey,TValue>
ToList IList<T>
ToLookup ILookup<TKey,TElement>
Union IEnumerable<T>
Where IEnumerable<T>

LINQ to 객체

"LINQ to Objects"는 모든 IEnumerable 또는 IEnumerable<T> 컬렉션에서 직접 LINQ 쿼리를 사용하는 것을 의미합니다. LINQ를 사용하여 열거 가능한 컬렉션(예: List<T>, Array또는 Dictionary<TKey,TValue>.)을 쿼리할 수 있습니다. 컬렉션은 사용자 정의 또는 .NET API에서 반환된 형식일 수 있습니다. LINQ 접근 방식에서는 검색하려는 항목을 설명하는 선언적 코드를 작성합니다. LINQ to Objects는 LINQ를 사용한 프로그래밍에 대한 훌륭한 소개를 제공합니다.

LINQ 쿼리는 기존 foreach 루프보다 세 가지 주요 이점을 제공합니다.

  • 특히 여러 조건을 필터링할 때 더 간결하고 읽을 수 있습니다.
  • 최소 애플리케이션 코드를 사용하여 강력한 필터링, 순서 지정 및 그룹화 기능을 제공합니다.
  • 거의 또는 전혀 수정하지 않고 다른 데이터 원본으로 이식할 수 있습니다.

데이터에 대해 수행하려는 작업이 복잡할수록 기존 반복 기술 대신 LINQ를 사용하면 더 많은 이점을 얻을 수 있습니다.

쿼리 결과를 메모리에 저장

쿼리는 기본적으로 데이터를 검색하고 구성하는 방법에 대한 지침 집합입니다. 쿼리는 결과의 각 후속 항목이 요청되므로 지연적으로 실행됩니다. foreach을 사용하여 결과를 반복할 때, 항목은 액세스된 순서대로 반환됩니다. 루프를 실행하지 않고 쿼리를 foreach 평가하고 결과를 저장하려면 쿼리 변수에서 다음 메서드 중 하나를 호출합니다.

다음 예제와 같이 쿼리 결과를 저장할 때 반환된 컬렉션 개체를 새 변수에 할당해야 합니다.

List<int> numbers = [ 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 ];

IEnumerable<int> queryFactorsOfFour = from num in numbers
                                      where num % 4 == 0
                                      select num;

// Store the results in a new variable
// without executing a foreach loop.
var factorsofFourList = queryFactorsOfFour.ToList();

// Read and write from the newly created list to demonstrate that it holds data.
Console.WriteLine(factorsofFourList[2]);
factorsofFourList[2] = 0;
Console.WriteLine(factorsofFourList[2]);

참고하십시오