티스토리 뷰



 ※ LINQ 연관글 모음

 

앞선 "LINQ 배우기를 위한 최적의 환경 LINQ to Objects"에서 다루었듯이 다양한 데이터 저장소에 대한 LINQ 사용법은 내부 오브젝트에 대한 LINQ 사용법을 익힘으로써 일정 부분 대신할 수 있으므로 리스트나 사전과 같은 C# 내부 오브젝트를 활용해서 LINQ에 대한 이해와 사용법을 차근 차근 다루어 볼까 합니다. 이번 포스팅은 사전 타입을 활용하여  LINQ 질의의 기본 원리를 다루어 볼까 합니다.

static void Main(string[] args)
        {
            Dictionary<string, string> user_names = new Dictionary<string, string>()
	            { {"hong1", "홍길동"}, {"hongsun", "홍길순"}, {"parkms", "박문수"}, {"kimsg", "김삿갓" } };
            Dictionary<string, int> user_ages = new Dictionary<string, int>() 
                { { "hong1", 23 }, { "hongsun", 32 }, { "parkms", 54 }, { "kimsg", 63 } };

            var qry =
                from name in user_names
                where name.Key == args[0]
                select name.Value;

            int i = 0;
            foreach (var item in qry)
            {
                Console.WriteLine(" {0} : {1} ", ++i, item);
            }
            Console.WriteLine("-----------------------------");

            var qry2 =
                from name in user_names
                where name.Key == args[0]
                select name;
            int j = 0;
            foreach (KeyValuePair<string, string> item in qry2)
            {
                Console.WriteLine(" {0} : {1} - {2} ", ++j, item.Key, item.Value);
            }
        }


■ 사전(Dictionary) 타입 사용

C#에서 사전(Dictionary)만큼 자주 애용하는 것이 있을까 싶을 정도로 자주 사용하는 클래스로 데이터의 개별 항목은 <키, 값> 쌍으로 이루어지는데 예제 코드의 <string, string> 또는 <string, int>처럼 개발자가 다양하게 응용할 수 있습니다. 예제에서는 변수 선언과 동시에 초기화 했지만 user_names.Add()등의 메소드를 통해서 항목을 편리하게 관리할 수 있고 특정 키를 가지고 있는 항목을 편리하게 추출할 수도 있습니다.

위의 예제는 두가지의 질의문이 있는데 첫번째 질의(qry)는 사전에서 값만을 가져오기 때문에 foreach문의 item은 string 타입의 데이터가 전달되지만 두번째 질의(qry2)는 조건에 해당하는 사전의 특정 항목을 가져오기 때문에 foreach문의 item도 단순 스트링이 아닌 사전 항목이기 전달되기 때문에 KeyValuePair<string, string>로 받고 있습니다. 


■ LINQ  기본 문법

var qry = 
    from name in user_names
    where name.Key == args[0]
    select name.Value;

위와 같은 LINQ 문장을 몇번 대하다 보니 LINQ 질의문이 눈에 익기는 했지만 하나씩 분해해 보면 처음에 오는 것은 from 구문으로 지정하는 데이터 소스입니다. 예제에서는 "in user_names" 처럼 내부 오브젝트를 데이터 소스로 지정했지만 외부 데이터베이스나 XML, Dataset등 다양한 데이터 소스를 지정할 수 있을 뿐만아니라 "from n in Enumerable.Range(1, 50)" 이나 "from n in Enumerable.Repeat(7, 10)" 처럼 임의의 값들을 생성해서 데이터 소스로 사용할 수도 있습니다.

두번째 구문은 where로 지정하는 필터링 구문입니다. from에서 지정한 데이터 소스에서 대상 범위를 걸러내기 위한 구문입니다. SQL의 NOT, AND, OR가 아니라 C#의 &&, ||와 같은 연산자와 구문을 이용해서 논리식을 기술하며 논리식이 참인 경우에 해당하는 데이터들만 대상으로 합니다. 

위의 예제 질의의 세번째 구문은 select로 지정하는 데이터 추출 구문으로 최종 질의 결과로 얻을 데이터의 형태를 지정합니다. LINQ 질의문에 마지막에 위치하는 구문입니다. 예제처럼 from에서 지정한 데이터 소스의 자료를 그대로 받을 수도 있고 변형할 수도 있으며 "select new { num = n, rst = n % 2 == 1 ? "odd" : "even" };" 처럼 새로운 값을 생성할 수도 있습니다.

var qry2 =
    from name in user_names
    where name.Key != args[0]
    group name by name.Key.Substring(0, 1) into namegrp
    orderby namegrp.Key descending
    select new { Letter = namegrp.Key, Count = namegrp.Count() };

위에서 언급한 from, where, select외에도 SQL에서 지원하는 것과 같은 orderby 로 결과를 정렬할 수 있으며 group ... by ... into 구문으로 질의 결과를 그룹화 할 수 있습니다. 물론 질의를 그룹화하는 경우에는 where 구문의 위치에 따라 그룹화 사전/사후 필터링이 적용될 수 있으므로 주의해야 합니다. 

var qry2 =
    from name in user_names
    join ages in user_ages on name.Key equals ages.Key
    orderby name.Key ascending
    select new { Id = name.Key, Name = name.Value, Age = ages.Value };
int j = 0;
foreach (var item in qry2)
{
    Console.WriteLine(" {0} : {1} - {2} - {3}", ++j, item.Id, item.Name, item.Age);
}

관계형 데이터베이스에 여러 테이블을 조인할 때 사용하는 조인 구문도 from 구문 다음에 join ... in ... on 방식으로 기술할 수 있습니다. 조인 구문을 사용함으로써 분리되어 있는 두가지 사전으로 마치 하나처럼 보이도록 하는 뷰를 생성할 수 있었습니다. 조인 구문의 "equals"를 통해서 SQL의 이너조인(inner join) 결과를 얻을 수 있었다면 OUTER JOIN은 join 구문 뒤에 into를 붙여서 join 결과를 받을 변수를 정의하고 바로 아래에 join 결과에 대한 sub-from절을 다음과 같이 기술하면 됩니다. "join ages in user_ages on name.Key equals ages.Key into agesjoin  from ages in agesjoin.DefaultIfEmpty()"


■ 지연 실행과 즉시 실행

위의 에제 코드에서 "var qry = ..."이나 "var qry2 = ..."로 선언한 질의는 질의 선언 시점에는 실행되지 않고 아래쪽의 foreach 문장에서 실행합니다. 결과적으로 질의 수행에 필요한 문장만을 제작했을 뿐이지 실제 수행은 foreach처럼 그 결과를 받아보는 순간에 질의를 수행합니다. 이런 질의 방식을 지연 실행(Deferred Execution)이라 합니다. 질의문 선언과 실행이 분리되어 있으므로 질의문을 여러번 재실행할 수 있습니다. 데이터 소스에 대한 빈번한 변경이 있다면 질의를 수행하는 foreach문마다 서로 다른 결과를 받아볼 수도 있는 것입니다.

var qry = (from name in user_names
         where name.Key == args[0]
         select name.Value).ToList();

반면에 위의 예제 처럼 LINQ 질의에 대해서 ToList(), First(), FirstOrDefault(), Last(), LastOrDefault()등의 메소드를 실행하면 질의의 선언과 동시에 실행이 이루어집니다. "qry" 변수에는 질의문이 아니라 수행 결과인 리스트 오브젝트가 들어 있기 때문에 "qry" 변수를 통한 질의 재실행은 수행 할 수 없습니다. 이런 질의 형태를 즉시 실행(Immediate Execution)이라 합니다. 용도에 따라 적절하게 사용하면 좋을 것 같습니다.


■ 질의(Query) 문법과 메소드(Method) 문법

앞서 다룬 LINQ 질의 문법들은 빌드 과정에서 닷넷이 인식할 수 있는 메소드 호출 형태로 바뀝니다.  결과적으로 모든 LINQ 질의문은 메소드 호출 형태로 바꾸어서 기술할 수 있습니다. 그리고 어떤 기능들은 메소드 형태로만 지원하기도 합니다. 

var qry = from name in user_names
            where name.Key == args[0]
            select name.Value;

var qry3 = user_names.Where(name => name.Key == args[0]).Select(name => name.Value);

위의 qry는 질의 문법으로 선언한 질의문이라면 qry3는 질의 문법을 동일한 메소드 문법으로 기술한 것입니다. 메소드 호출 형태로 문장을 기술했지만 즉시 실행되는 것은 아닙니다.  메소드 문법으로 기술한 질의문의 수행 시점은 질의 문법의 수행 시점과 동일합니다. 

"=>"는 람다(Lambda) 연산자로 람다 표현식은 빌드 시점에 => 연산자 좌측의 내용을 입력으로 적절한 질의문을 생성하는데 사용합니다. 위의 코드에서는 user_names라는 데이터 소스를 name에 적용해서 질의문을 생성합니다. 오로지 메소드 문법을 사용할 필요는 없으므로 질의 문법과 함께 필요에 따라 적절하게 사용하면 될것 같습니다.


댓글
댓글쓰기 폼