본문 바로가기

아티클/팁/.NET / Windows

LINQ : .NET Language-Integrated Query - 1

이 포스트의 내용은 지난 2007.10.6 ASP 뉴스그룹 오프라인 세미나에서 발표했던 내용을 간추린 것입니다.
어디까지나 LINQ의 개요를 살펴보기 위한 내용이므로, 여기에 나온 내용이 LINQ의 전부라고 생각하시면 곤란합니다.

포스트가 조금 길어져서, 둘로 나누어 올립니다.

링크 : LINQ : .NET Language-Integrated Query - 2


- 작성자 : 이수겸, 올랩컨설팅 컨설턴트, keniallee_at_gmail.com


1. LINQ의 개요

- LINQ의 등장

닷넷 프레임워크 프로그래밍 환경에 존재하는 모든 종류의 데이터 원본(Data Sources)에 접근할 수 있는, 범용의 조회 기능을 구현하기 위한 프로젝트로 시작되었다. 기존의 SQL을 이용한 관계형 데이터베이스 조회를 생각해보면, 컴퓨터 언어(Visual Basic, C++, C#, Java, 무엇이든간에)를 이미 알고 있음에도 불구하고 데이터베이스의 데이터를 가져와야 할 경우 SQL 문법을 알아야만 데이터를 가져올 수 있었다. 세상의 모든 것(이라기보다는 컴퓨터 환경 내에 존재하는 모든 것이라고 해야 정확하겠지만)을 객체로 나타내고 프로그래밍 언어로 주물럭거려야만 직성이 풀리는 객체지향적 사고로 이러한 현실을 바라본다면, 이는 도저히 용납 못할 일이다. 그래서 마이크로소프트는 자사의 닷넷 프레임워크를 좀 더 강력하고 쉽게 만드는 것과 동시에, MS 기반의 기술을 사용하는 개발자들을 더더욱 바보로 만들어 자신들의 개발 환경에 묶어놓기 위해서 - LINQ라는 프로젝트를 발족시켰다. 이 프로젝트에는 각각 ASP.NET, C#의 설계자로 알려져있는 Don Box, Anders Hejlsberg가 LINQ의 설계에 참여하고 있다.

하지만 우리와는 별 상관없는 이야기이다.


- LINQ의 구성


MSDN Mag에서 무단 캡처질을 해 왔음을 고백합니다.

LINQ는 위와 같은 구성 요소를 갖고 있다. 이런 형태의 구성도를 많이 봐 온 사람이라면 알겠지만, 이 LINQ라는 것은 단순히 라이브러리가 아니다. 언어의 문법적 요소도 포함하고 있으며, 이는 닷넷 프레임워크 3.5의 새로운 언어 기능과도 관련이 있다. 하지만 닷넷 프레임워크 3.5에 대해서는 이 포스팅에서 그리 자세히 다루지 않을 예정이므로, 정 궁금한 사람은 Visual Studio 2008에 대한 자료를 찾아서 공부하는 것이 좋겠다.

그럼 LINQ를 한마디로 정의하자면 뭐라고 해야 할까? 케냘은 이렇게 정의하겠다 : XML, 데이터베이스, 닷넷 개체 등의 닷넷 프레임워크 환경의 데이터 원본을 다루기 위한 객체지향적 관점을 제공하는 프레임워크

LINQ는 일종의 언어 확장(Language-Extension)이면서, XML(LINQ to XML), 데이터베이스(LINQ to SQL, LINQ to Dataset 및 LINQ to Entities를 포함하는 LINQ 지원 ADO.NET), 개체(LINQ to Objects) 등의 타입을 다룰 수 있다. 또한, 앞서 말했듯이 닷넷 프레임워크 3.5에 통합되어 있다. 현재 시점에서 LINQ를 직접 사용해 보려면 Visual Studio 2008을 설치해 보는 것이 가장 빠를 것이다.


2. LINQ의 실제

- 표준 쿼리 연산자(Standard Query Operators)

LINQ는 표준 쿼리 연산자라는 것을 사용해서 데이터 원본을 다룰 수 있도록 해 준다. 기존의 DataSet등을 떠올리는 사람도 있겠지만, 이는 그것과는 완전히 개념부터가 다르다. 다음의 예제를 보자 :

static void Main(string[] args)
        {
            string[] names = { "Burke", "Connor", "Frank",
                       "Everett", "Albert", "George",
                       "Harris", "David" };

            IEnumerable<string> query = from s in names
                                        where s.Length == 5
                                        orderby s
                                        select s.ToUpper();

            foreach (string item in query)
                Console.WriteLine(item);

        }

'아니 이게 대체 뭐야...'라고 생각할 사람들이 많을 것 같다. 그도 그럴 것이, 이는 LINQ를 지원하는(그리고 닷넷 프레임워크 3.5에 통합된) 새로운 문법의 일부이다. from ~ in의 구문은 SQL의 그것과 약간 다르고, 나머지는 거의 동일하다고 생각하면 된다. names 배열에 저장된 데이터를 '조회'해서 string 개체를 담을 수 있는 IEnumerable 열거자 형식에 담는 것으로, 관계형 DB의 테이블에서 레코드 목록을 조회해 오는 것과 비슷해 보인다.

s에 대한 형식이 미리 지정되어 있지 않아서 조금 당황스럽겠지만, 이것도 닷넷 프레임워크 3.5에 포함된 언어 문법의 일부이다. names 배열에서 가져온 각각의 s에 대해서, where 연산을 수행해서 문자열의 길이가 5(s.Length==5)인 개체를 걸러내고, orderby 연산을 통해 s를 순서대로 정렬하여, 최종적으로 s를 대문자로 변환한(s.ToUpper()) 결과를 얻는다. 이 코드의 결과는 다음과 같다 :

BURKE
DAVID
FRANK

어떤가? 아직 감이 안 오시는가? 코드를 열 번 더 읽어보자.

* 위와 같은 형태의 수식은 쿼리 식(Query Expression)이라고 부른다.



- 람다 식(Lambda Expressions)

위와 같은 표준 쿼리 연산자를 사용해서 개체를 다룰 수 있다. 하지만 기존의 쿼리문을 생각해보자. where 절에 수많은 조건이 붙고, 쿼리 식이 한없이 길어지기 시작하면, 이게 굳이 SQL 위에 가상의 클래스를 얹은 것 이상의 무슨 의미가 있겠는가? 그래서, LINQ에는 기존의 C# 2.0에서 DataSet의 각종 필터 기능을 수행하는 함수를 객체 형태로 포장하는 기법(여기서 익명 메서드, Delegate등을 떠올릴 수 있다면 C# 중급자라고 자신해도 좋다)을 사용할 수 있게 새로운 구문이 추가되었다. 이것이 람다 식이다. 앞서 등장한 예를 람다 식으로 기술하면 다음과 같다 :

static void Main(string[] args)
{
    string[] names = { "Burke", "Connor", "Frank",
               "Everett", "Albert", "George",
               "Harris", "David" };

    // Lambda Expression
    IEnumerable<string> query = names
                    .Where(s => s.Length == 5)
                    .OrderBy(s => s)
                    .Select(s => s.ToUpper());

    foreach (string item in query)
        Console.WriteLine(item);

}

위 코드는 아까와는 또 다른 차이점을 보여주고 있는데, 표준 쿼리 연산자 대신 .Where()와 같은 메서드를 사용하고 있다는 것이다. 눈치가 빠른 사람은 이미 알아챘겠지만, 표준 쿼리 연산자는 실제로는 저러한 메서드에 대한 맵핑이다. .Where(), .Orderby(), .Select()와 같은 메서드가 기존의 데이터 원본에 구현되어 있는 것이다. 이런 메서드를 확장 메서드(Extension Method)라고 부르며, 사용자가 직접 구현해서 표준 쿼리 연산자를 정의할 수도 있다.

그러면 대체 위 코드는 어떻게 해석할 수 있을까? .Where()의 경우를 들자면, 's.Length==5'라는 조건(Boolean)을 인자로 주고, s를 리턴해 받는 것이라고 보면 대충 맞을 것이다. 그 아래의 Orderby(), Select()도 마찬가지일 것이다. 하지만 이렇게 기술해 놓은 형태로는 Delegate같은 것은 전혀 보이지 않는다. 이런 것은 실제로 다음과 같이 구현된다 :

static void Main(string[] args)
{
    string[] names = { "Burke", "Connor", "Frank",
               "Everett", "Albert", "George",
               "Harris", "David" };

    Func<string, bool> filter = s => s.Length == 5;
    Func<string, string> extract = s => s;
    Func<string, string> project = s => s.ToUpper();

    IEnumerable<string> query = names.Where(filter)
                                     .OrderBy(extract)
                                     .Select(project);

    foreach (string item in query)
        Console.WriteLine(item);
}

Delegate를 이미 이해하고 있는 개발자라면, 굳이 더 자세한 설명이 없어도 될 것이다.



* 참고자료

Basic Instincts : 람다 식
http://msdn.microsoft.com/msdnmag/issues/07/09/BasicInstincts/Default.aspx?loc=ko

bkchung's WebLog : LINQ의 정체(정의)는?
http://blogs.msdn.com/bkchung/archive/2005/10/16/481569.aspx

LINQ: .NET Language-Integrated Query
http://msdn2.microsoft.com/ko-kr/library/bb308959.aspx

MSDN Magazine > October 2007 - Parallel LINQ ; 다중 코어 프로세서에서 쿼리 실행
http://msdn.microsoft.com/msdnmag/issues/07/10/PLINQ/Default.aspx?loc=ko

MSDN Magazine > June 2007 - C# 3.0 ; LINQ의 발전과 C# 설계에 미치는 영향
http://msdn.microsoft.com/msdnmag/issues/07/06/CSharp30/default.aspx?loc=ko