LINQ

LINQ('링크'로 발음, Language Integrated Query)는 네이티브 데이터 질의 기능을 닷넷 언어에 추가하는 마이크로소프트 닷넷 프레임워크 구성 요소이며 2007년 닷넷 프레임워크 3.5의 중요 부분으로 처음 출시되었다.

LINQ는 SQL 문과 비슷하게 질의식의 추가를 통해 언어를 확장하며 배열, 열거식 클래스, XML 도큐먼트, 관계형 데이터베이스, 서드파티 데이터 소스로부터 데이터를 편리하게 추출하고 가공하기 위해 사용할 수 있다. 질의식을 임의의 계산을 읽기 쉽게 구성하기 위한 일반적인 프레임워크로 활용하는 다른 용례로는 이벤트 핸들러 구성, 모나딕 파서가 포함된다.

LINQ의 포팅판으로는 PHP(PHPLinq ), 자바스크립트(linq.js), 타입스크립트(linq.ts), 액션스크립트(ActionLinq)용으로 존재하지만
이 중 어느 것도 닷넷 파생 언어 C#, F#, VB.NET과 완전히 동일한 것은 아니다.

아키텍처

표준 쿼리 연산자 API
다음은 컬렉션으로 작업하는 애플리케이션에 기반한 연산자 설명이다. 많은 연산자가 다른 함수를 인수로 받는다. 이 함수들은 이름 있는 메서드나 익명 함수 형태로 제공될 수 있다.

LINQ에서 정의한 쿼리 연산자 집합은 표준 쿼리 연산자(SQO) API로 사용자에게 노출된다. API가 지원하는 쿼리 연산자는 다음과 같다:
Select
Select 연산자는 컬렉션에 프로젝션을 수행하여 요소의 중요한 측면을 선택한다. 사용자는 이름 있는 함수 또는 람다 식 형태로 임의의 함수를 제공하며, 이 함수는 데이터 멤버를 투영한다. 이 함수는 델리게이트로 연산자에 전달된다. 이는 Map 고차 함수를 구현한다.
Where
Where 연산자는 컬렉션의 각 객체에 대해 평가되는 조건자 규칙 집합을 정의할 수 있게 하며, 규칙과 일치하지 않는 객체는 필터링한다. 조건자는 델리게이트로 연산자에 제공된다. 이는 Filter 고차 함수를 구현한다.
SelectMany
컬렉션 요소에서 컬렉션으로의 사용자 제공 매핑에 대해 의미론적으로 두 단계가 수행된다. 첫째, 모든 요소는 해당 컬렉션에 매핑된다. 둘째, 첫 번째 단계의 결과는 한 레벨 평탄화된다. Select와 Where는 단일 컬렉션과 빈 컬렉션을 사용할 수 있는 한, SelectMany를 통해 모두 구현할 수 있다. 위에서 언급한 변환 규칙은 LINQ 공급자가 다른 두 연산자를 제공하도록 여전히 의무화한다. 이는 bind 고차 함수를 구현한다.
Sum / Min / Max / Average
이 연산자들은 선택적으로 컬렉션의 각 요소에서 특정 숫자 값을 검색하고 이를 사용하여 컬렉션의 모든 요소에 대한 합계, 최소값, 최대값 또는 평균값을 각각 찾는 함수를 받는다. 오버로드된 버전은 함수를 받지 않으며 람다로 항등 함수가 주어진 것처럼 동작한다.
Aggregate
일반화된 Sum / Min / Max. 이 연산자는 두 값을 결합하여 중간 결과 또는 최종 결과를 형성하는 방법을 지정하는 함수를 받는다. 선택적으로 시작 값을 제공할 수 있어 집계 결과의 유형을 임의로 지정할 수 있다. 또한 집계 결과를 또 다른 값으로 변환하는 최종화 함수를 제공할 수 있다. 이는 Fold 고차 함수를 구현한다.
Join / GroupJoin Join 연산자는 각 컬렉션의 객체에 대한 일치하는 키를 기반으로 두 컬렉션에 대해 내부 조인을 수행한다. 이 연산자는 두 개의 델리게이트를 받는데, 각 컬렉션에 대해 하나씩이며, 컬렉션의 각 객체에서 키를 추출하기 위해 실행한다. 또한 사용자가 일치하는 두 요소에서 결과 객체를 생성하는 데 어떤 데이터 요소를 사용해야 하는지 지정하는 또 다른 델리게이트를 받는다. GroupJoin 연산자는 그룹 조인을 수행한다. Select 연산자와 마찬가지로, 조인의 결과는 원본 객체의 두 유형의 모든 데이터 멤버 또는 그 일부를 포함하는 다른 클래스의 인스턴스이다.
Take / TakeWhile Take 연산자는 컬렉션에서 첫 n개의 객체를 선택하는 반면, 조건자를 받는 TakeWhile 연산자는 조건자와 일치하는 객체를 선택한다(일치하지 않는 첫 번째 객체에서 중지).
Skip / SkipWhile Skip 및 SkipWhile 연산자는 Take 및 TakeWhile의 보완 연산자이다. 이 연산자들은 컬렉션에서 첫 n개의 객체 또는 조건자와 일치하는 객체를 건너뛴다(SkipWhile의 경우).
OfType OfType 연산자는 특정 유형의 요소를 선택하는 데 사용된다.
Concat Concat 연산자는 두 컬렉션을 연결한다.
OrderBy / ThenBy OrderBy 연산자는 특정 키에 따라 컬렉션의 요소에 대한 기본 정렬 순서를 지정하는 데 사용된다. 기본 순서는 오름차순이며, 순서를 뒤집으려면 OrderByDescending 연산자를 사용한다. ThenBy 및 ThenByDescending은 요소의 후속 순서를 지정한다. 객체에서 키 값을 추출하는 함수는 사용자가 델리게이트로 지정한다.
Reverse Reverse 연산자는 컬렉션을 역순으로 뒤집는다.
GroupBy GroupBy 연산자는 키 값을 추출하는 함수를 받아서 각 고유 키 값에 대해 IGrouping<Key, Values> 객체 컬렉션을 반환한다. IGrouping 객체는 특정 키 값에 대한 모든 객체를 열거하는 데 사용될 수 있다.
Distinct Distinct 연산자는 컬렉션에서 중복 객체 인스턴스를 제거한다. 연산자의 오버로드는 고유성 기준을 정의하는 동등성 비교기 객체를 받는다.
Union / Intersect / Except 이 연산자들은 두 시퀀스에 대해 각각 합집합, 교집합 및 차집합 연산을 수행하는 데 사용된다. 각 연산자에는 요소 동등성 기준을 정의하는 동등성 비교기 객체를 받는 오버로드가 있다.
SequenceEqual SequenceEqual 연산자는 두 컬렉션의 모든 요소가 동일하고 같은 순서인지 여부를 결정한다.
First / FirstOrDefault / Last / LastOrDefault 이 연산자들은 조건자를 받는다. First 연산자는 조건자가 참을 반환하는 첫 번째 요소를 반환하며, 일치하는 것이 없으면 예외를 던진다. FirstOrDefault 연산자는 First 연산자와 유사하지만, 조건자와 일치하는 것이 없는 경우 요소 유형의 기본값(일반적으로 null 참조)을 반환한다. Last 연산자는 조건자와 일치하는 마지막 요소를 검색하며, 일치하는 것이 없는 경우 예외를 던진다. LastOrDefault는 일치하는 것이 없는 경우 기본 요소 값을 반환한다.
Single Single 연산자는 조건자를 받아서 조건자와 일치하는 요소를 반환한다. 조건자와 일치하는 요소가 없거나 두 개 이상이면 예외가 발생한다.
SingleOrDefault SingleOrDefault 연산자는 조건자를 받아서 조건자와 일치하는 요소를 반환한다. 조건자와 일치하는 요소가 두 개 이상이면 예외가 발생한다. 조건자와 일치하는 요소가 없으면 기본값이 반환된다.
ElementAt ElementAt 연산자는 컬렉션의 지정된 인덱스에 있는 요소를 검색한다.
Any / All Any 연산자는 컬렉션에 조건자와 일치하는 요소가 있는지 확인한다. 요소를 선택하지는 않지만, 하나 이상의 요소가 일치하면 참을 반환한다. 조건자 없이 Any를 호출하면 컬렉션이 비어 있지 않으면 참을 반환한다. All 연산자는 모든 요소가 조건자와 일치하면 참을 반환한다.
Contains Contains 연산자는 컬렉션에 주어진 요소가 포함되어 있는지 확인한다.
Count Count 연산자는 주어진 컬렉션의 요소 수를 계산한다. 조건자를 받는 오버로드는 조건자와 일치하는 요소 수를 계산한다.

표준 쿼리 연산자 API는 또한 컬렉션을 다른 유형으로 변환하는 특정 연산자를 지정한다:
 AsEnumerable: 컬렉션을 IEnumerable<T>로 정적으로 형변환한다.
 AsQueryable: 컬렉션을 IQueryable<T>로 정적으로 형변환한다.
 ToArray: 컬렉션에서 배열 T[]를 생성한다.
 ToList: 컬렉션에서 List<T>를 생성한다.
 ToDictionary: 컬렉션에서 키 K로 인덱싱된 Dictionary<K, T>를 생성한다. 사용자 제공 프로젝션 함수가 각 요소에서 키를 추출한다.
 ToLookup: 컬렉션에서 키 K로 인덱싱된 Lookup<K, T>를 생성한다. 사용자 제공 프로젝션 함수가 각 요소에서 키를 추출한다.
 Cast: 비제네릭 IEnumerable 컬렉션을 각 요소를 타입 T로 캐스팅하여 IEnumerable<T> 중 하나로 변환한다. 또는 각 요소를 타입 T에서 타입 R로 캐스팅하여 제네릭 IEnumerable<T>를 다른 제네릭 IEnumerable<R>로 변환한다. 어떤 요소라도 지정된 타입으로 캐스팅할 수 없으면 예외를 던진다.
 OfType: 비제네릭 IEnumerable 컬렉션을 IEnumerable<T> 중 하나로 변환한다. 또는 각 요소를 타입 T에서 타입 R로 캐스팅을 시도하여 제네릭 IEnumerable<T>를 다른 제네릭 IEnumerable<R>로 변환한다. 두 경우 모두, 대상 타입으로 성공적으로 캐스팅된 요소의 하위 집합만 포함된다. 예외는 발생하지 않는다.

언어 확장
LINQ는 주로 .NET Framework 3.5용 라이브러리로 구현되지만, 쿼리를 일급 언어 구성체로 만들고 쿼리 작성에 신택틱 슈거를 제공하는 선택적 언어 확장도 정의한다. 이러한 언어 확장은 처음에 C# 3.0, VB 9.0, F# 및 옥시즌에 구현되었으며, Nemerle와 같은 다른 언어들도 예비 지원을 발표했다. 언어 확장에는 다음이 포함된다:
쿼리 구문: 언어는 자체적으로 인식할 쿼리 구문을 자유롭게 선택할 수 있다. 이러한 언어 키워드는 컴파일러에 의해 적절한 LINQ 메서드 호출로 번역되어야 한다.
암시적 형식 변수: 이 개선 사항은 변수를 타입을 지정하지 않고 선언할 수 있도록 한다. C# 3.0 및 옥시즌 언어는 var 키워드로 이를 선언한다. VB9.0에서는 타입 선언 없는 Dim 키워드가 동일한 기능을 수행한다. 이러한 객체는 여전히 강하게 타입이 지정된다. 이러한 객체에 대해 컴파일러는 자료형 추론을 통해 변수의 타입을 추론하며, 이는 중간 변수의 타입을 선언하지 않고도 쿼리 결과를 지정하고 정의할 수 있도록 한다.
 익명 형식: 익명 형식은 데이터 멤버 선언만 포함하는 클래스를 컴파일러가 추론할 수 있도록 한다. 이는 Select 및 Join 연산자에서 유용하며, 이들의 결과 타입은 원본 객체의 타입과 다를 수 있다. 컴파일러는 타입 추론을 사용하여 클래스에 포함된 필드를 결정하고 이러한 필드에 대한 접근자 및 뮤테이터를 생성한다.
 객체 초기화자: 객체 초기화자는 Select 및 Join 연산자에 필요한 대로 단일 스코프에서 객체를 생성하고 초기화할 수 있도록 한다.
 람다 식: 람다 식은 간결한 구문으로 조건자 및 기타 프로젝션 함수를 인라인으로 작성할 수 있게 하며, 완전한 렉시컬 클로저를 지원한다. 이들은 쿼리 공급자에 따라 델리게이트 또는 표현식 트리로 매개변수에 캡처된다.

예를 들어, SomeProperty가 10 미만인 컬렉션의 모든 객체를 선택하는 쿼리에서,

IEnumerable<MyObject> SomeCollection = /* 여기에 뭔가 */

IEnumerable<MyObject> results = from c in SomeCollection
                                where c.SomeProperty < 10
                                select new {c.SomeProperty, c.OtherProperty};

foreach (MyObject result in results)
{
    Console.WriteLine(result);
}

변수 result, c 및 results의 타입은 최종적으로 사용되는 메서드의 서명에 따라 컴파일러가 모두 추론한다. 메서드 선택의 기초는 쿼리 표현식이 없는 변환 결과에서 형성된다.

IEnumerble<MyObject> results =
     SomeCollection
        .Where(c => c.SomeProperty < 10)
        .Select(c => new {c.SomeProperty, c.OtherProperty});

results.ForEach(x => {Console.WriteLine(x.ToString());})

LINQ 공급자
C#3.0 사양은 쿼리 표현식 패턴과 LINQ 표현식에서 LINQ 표현식이 없는 C# 3.0의 하위 집합으로의 변환 규칙을 정의한다. 이렇게 정의된 변환은 실제로 타입이 지정되지 않으며, 람다 표현식이 델리게이트 또는 표현식 트리로 해석될 수 있다는 점과 더불어, 인터페이스의 일부를 LINQ 표현식 절로 노출하려는 라이브러리에 상당한 유연성을 제공한다. 예를 들어, LINQ to Objects는 IEnumerable<T> 및 델리게이트와 함께 작동하는 반면, LINQ to SQL은 표현식 트리를 사용한다.

표현식 트리는 LINQ 확장성 메커니즘의 핵심이며, 이를 통해 LINQ는 여러 데이터 소스에 맞게 조정될 수 있다. 표현식 트리는 LINQ 공급자에게 전달되며, 이들은 LINQ 쿼리를 데이터 소스에서 사용하도록 조정하는 데이터 소스별 구현이다. LINQ 공급자는 원하는 경우 쿼리에 포함된 표현식 트리를 분석하여 쿼리 실행에 필요한 필수 요소를 생성한다. 이는 SQL 조각이거나 추가로 조작 가능한 데이터로서 완전히 다른 코드 표현일 수 있다.
LINQ는 인메모리 객체 컬렉션, 마이크로소프트 SQL 서버 데이터베이스, ADO.NET 데이터셋 및 XML 문서용 LINQ 공급자와 함께 제공된다. 이러한 다양한 공급자는 LINQ의 다양한 특징을 정의한다.

LINQ to Objects
LINQ to Objects 공급자는 LINQ의 로컬 쿼리 실행 엔진을 사용하여 인메모리 컬렉션에 사용된다. 이 공급자가 생성하는 코드는 Sequence 패턴에 정의된 표준 쿼리 연산자 구현을 참조하며, IEnumerable<T> 컬렉션을 로컬에서 쿼리할 수 있도록 한다. LINQ to Objects의 현재 구현은 IEnumerable의 런타임 타입이 지원하는 경우 빠른 멤버십 테스트, 카운트 및 인덱스 조회 작업을 허용하기 위해 인터페이스 구현 검사를 수행한다.

LINQ to XML (이전 명칭: XLINQ)
LINQ to XML 공급자는 XML 문서를 XElement 객체 컬렉션으로 변환하며, 이 객체들은 표준 쿼리 연산자 구현의 일부로 제공되는 로컬 실행 엔진을 사용하여 쿼리된다.

LINQ to SQL (이전 명칭: DLINQ)
LINQ to SQL 공급자는 SQL 서버 컴팩트 데이터베이스를 포함한 마이크로소프트 SQL 서버 데이터베이스를 쿼리하는 데 LINQ를 사용할 수 있도록 한다. SQL Server 데이터는 원격 서버에 있을 수 있고, SQL Server는 자체 쿼리 엔진을 가지고 있기 때문에, LINQ to SQL은 LINQ의 쿼리 엔진을 사용하지 않는다. 대신 LINQ 쿼리를 SQL 쿼리로 변환하여 처리하기 위해 SQL Server로 보낸다. 그러나 SQL Server는 데이터를 관계형 데이터로 저장하고 LINQ는 객체에 캡슐화된 데이터와 함께 작동하므로, 두 표현은 서로 매핑되어야 한다. 이러한 이유로 LINQ to SQL은 매핑 프레임워크도 정의한다. 매핑은 데이터베이스의 테이블에 해당하는 클래스를 정의하고, 테이블의 모든 또는 일부 열을 데이터 멤버로 포함하여 수행된다. 기본 키와 같은 다른 관계형 모델 속성과의 대응 관계는 LINQ to SQL이 정의한 특성을 사용하여 지정된다. 예를 들어,

[Table(Name="Customers")]
public class Customer
{
     [Column(IsPrimaryKey = true)]
     public int CustID;

     [Column]
     public string CustName;
}

이 클래스 정의는 Customers라는 이름의 테이블에 매핑되며, 두 데이터 멤버는 두 열에 해당한다. LINQ to SQL을 사용하기 전에 클래스를 정의해야 한다. 비주얼 스튜디오 2008에는 객체 및 관계형 도메인의 데이터 스키마 간 매핑을 생성하는 데 사용할 수 있는 매핑 디자이너가 포함되어 있다. 이 디자이너는 데이터베이스 스키마에서 해당 클래스를 자동으로 생성할 수 있을 뿐만 아니라, 테이블의 일부 테이블 또는 열만 사용하여 다른 뷰를 생성하기 위한 수동 편집도 허용한다.

매핑은 서버에 대한 연결 문자열을 받고 데이터베이스 테이블이 매핑될 타입인 T를 가진 Table<T>를 생성하는 데 사용될 수 있는 DataContext에 의해 구현된다. Table<T>는 테이블의 데이터를 캡슐화하고 IQueryable<T> 인터페이스를 구현하여, LINQ to SQL 공급자가 처리하는 표현식 트리가 생성된다. 이는 쿼리를 T-SQL로 변환하고 데이터베이스 서버에서 결과 집합을 검색한다. 처리는 데이터베이스 서버에서 발생하므로, 조건자를 나타내는 람다 표현식의 일부로 정의되지 않은 로컬 메서드는 사용할 수 없다. 그러나 서버의 저장 프로시저를 사용할 수 있다. 결과 집합에 대한 모든 변경 사항은 추적되며 데이터베이스 서버로 다시 제출될 수 있다.

LINQ to DataSets
위의 LINQ to SQL 공급자는 마이크로소프트 SQL 서버 데이터베이스에서만 작동하므로, 모든 일반 데이터베이스를 지원하기 위해 LINQ에는 LINQ to DataSets도 포함되어 있다. 이는 ADO.NET을 사용하여 데이터베이스와의 통신을 처리한다. 데이터가 ADO.NET 데이터셋에 들어가면, LINQ to DataSets는 이 데이터셋에 대해 쿼리를 실행한다.

 PLINQ 
닷넷 프레임워크 버전 4에는 PLINQ(Parallel LINQ)가 포함되어 있으며 이는 LINQ 쿼리들을 위한 병렬 실행 엔진이다. ParallelQuery<T> 클래스를 정의한다. IEnumerable<T> 인터페이스 구현체는 닷넷 프레임워크의 System.Linq 이름공간의 ParallelEnumerable 클래스에 의해 정의된 AsParallel<T>(this IEnumerable<T>) 확장 메소드를 호출함으로써 PLIQ 엔진의 이점을 활용할 수 있다. PLIQ 엔진은 다중 스레드로 동시에 쿼리의 일부를 실행할 수 있어서 더 빠른 결과를 도출해 낸다.

 같이 보기 
 객체 관계 매핑 (ORM)
 리스트 캄프리헨션
 느긋한 계산법

 각주 

 외부 링크 
 Official Microsoft LINQ Project
 LINQ to Objects for the .NET developer 
 Future of LINQ to SQL
 How does it work in C#? - Part 3 (C# LINQ in detail)

분류:질의 언어
분류:닷넷 용어