티스토리 뷰

프로그래밍

LINQ 특정 항목 추출하기

야라바 2016. 11. 8. 14:03


 ※ LINQ 연관글 모음

 

데이터베이스 질의의 효용성은 특정한 키값을 가지고 있는 레코드를 찾아오는 것보다 특정한 조건을 갖는 정보들을 질의문 하나로 추출하는 과정에 있지 않나 싶습니다. where 구문을 통해 필터링한 이후에("LINQ 필터링 다루기" 참조) 그 결과에서 특정 항목들을 추출하는 것을 다룹니다.

■ 페이징(Paging)

List<Userinfo> userinfo = new List<Userinfo>() {
    new Userinfo { UserID = "hong1", UserName = "홍길동", UserType = "vip", Age = 50, Sex = true},
    new Userinfo { UserID = "parkms", UserName = "박문수", UserType = "vip", Age = 54, Sex = true},
    new Userinfo { UserID = "hongsun", UserName = "홍길순", UserType = "vvip", Age = 43, Sex = false},
    new Userinfo { UserID = "kimsg", UserName = "김삿갓", UserType = "general", Age = 35, Sex = true},
    new Userinfo { UserID = "hwang1", UserName = "황금복", UserType = "general", Age = 29, Sex = false},
    new Userinfo { UserID = "hwangso", UserName = "황소", UserType = "vip", Age = 63, Sex = true},
    new Userinfo { UserID = "kimsan", UserName = "김산", UserType = "general", Age = 23, Sex = true},
    new Userinfo { UserID = "seohee", UserName = "서희", UserType = "vvip", Age = 22, Sex = false}};


Dictionary<string, int> user_fee = new Dictionary<string, int>() { { "vip", 1000 }, { "vvip", 0 }, { "general", 3000 } };

var qry = from ui in userinfo
          from uf in user_fee
          select new { ui, uf };

int i = 0;
foreach (var item in qry)
{
    Console.WriteLine(" {0} : {1} {2} {3} ", ++i, item.ui.UserID, item.ui.UserName, item.uf.Value);
}

위의 예제는 두 오브젝트를 크로스 조인(Cross Join)한 결과입니다. 필터나 정렬 구문이 포함될 수도 있겠지만 구문의 포함여부와 관계없이 결과의 양이 많은 경우, 대부분의 응용 프로그램에서는 한번에 출력하는 양을 조절하는 페이징 기능을 적용합니다. 특히, 게시판 처럼 데이터 양이 많지만 표시 양을 제한 시켜야 하는 웹 응용의 경우에는 페이징 기능은 시스템 성능과도 직결되는 요소입니다. 페이징 기능은 DBMS 종류별로 그 사용법이 다르다는 특성도 있습니다. LINQ에서는 Skip 및 Take 구문을 이용해서 페이징을 구현할 수 있습니다.

var qry = from ui in userinfo
          from uf in user_fee
          select new { ui, uf };

int i = 0;
foreach (var item in qry.Skip(10).Take(5))
{
    Console.WriteLine(" {0} : {1} {2} {3} ", ++i, item.ui.UserID, item.ui.UserName, item.uf.Value);
}

MySQL의 경우에는 "LIMIT 10,5처럼" 결과에서 "시작 Offset, 개수"로 일정 부분을 가져올 수 있는데 LINQ에서는 "시작 Offset"과 유사하지만 Skip(무시할 개수) Take(가져올 개수) 구문으로 지정할 수 있습니다. 페이지번호와 페이지당 개수를 입력으로 한다면 Skip((페이지번호-1)*페이지당 개수).Take(페이지당 개수)로 간편하게 페이징을 구현할 수 있습니다.


■ 시작, 끝, 특정 부분에서 추출하기

var qry = from uf in user_fee
          join ui in userinfo on uf.Key equals ui.UserType
          orderby ui.UserID
          select new { uf, ui };

int i = 0;
foreach (var itm in qry)
{
    Console.WriteLine(" {0} : {1} {2} {3} ", ++i, itm.uf.Key, itm.ui.UserName, itm.uf.Value);
}

var item = qry.First();
Console.WriteLine("> First row => {0} {1} {2} ", item.ui.UserID, item.ui.UserName, item.uf.Value);

item = qry.Last();
Console.WriteLine("> Last tow => {0} {1} {2} ", item.ui.UserID, item.ui.UserName, item.uf.Value);

item = qry.First(row => row.uf.Value > 1000);
Console.WriteLine("> First(>1000) row => {0} {1} {2} ", item.ui.UserID, item.ui.UserName, item.uf.Value);

item = qry.LastOrDefault(row => row.ui.Age > 70);
if (item == null)
    Console.WriteLine("> Last or Default(Age > 70) => No result!");
else
    Console.WriteLine("> Last or Default(Age > 70) => {0} {1} {2} ", item.ui.UserID, item.ui.UserName, item.uf.Value);

item = qry.ElementAt(5);
Console.WriteLine("> Index 5 Row => {0} {1} {2} ", item.ui.UserID, item.ui.UserName, item.uf.Value);

위의 예제 코드는 두 오브젝트를 이너조인(Inner join)으로 결합한 결과를 UserID기준으로 정렬하여 추출한 다음에 First()메소드를 이용해서 첫번째 결과를 Last()를 마지막 결과를 추출하는 예제를 보인것입니다. First() 또는 Last()에 람다 문법을 이용해서 조건을 기술할 수 있습니다. 질의 결과가 없거나 First() 또는 Last() 조건에 해당하는 것이 없을 경우에 기본값(Default)을 리턴하도록 FirstOrDefault()나 LastOrDefault() 메소드를 사용할 수 있습니다. 위의 예제에서는 결과값의 형태가 집합 오브젝트라서 기본값이 null이지만 결과값이 정수인 경우등에는 기본값인 0을 리턴합니다. FirstOrDefault()나 LastOrDefault() 대신 First(), Last()를 사용하고 결과가 없으면 예외(Exception)가 발생하므로 주의해야 합니다. ElementAt(인덱스)나 ElementAtOrDefault(인덱스)는 0부터 시작하는 인덱스로 특정 위치의 값을 추출할 때 사용합니다.


■ 조건을 지정해서 특정 결과를 제외/포함시키기

int i = 0;
foreach (var itm in qry.SkipWhile(row => row.ui.Sex == false).Take(5))
{
    Console.WriteLine(" {0} : {1} {2} {3} ", ++i, itm.uf.Key, itm.ui.UserName, itm.uf.Value);
}

i = 0;
foreach (var itm in qry.Skip(2).TakeWhile((row, idx) => idx<10 && row.ui.Sex == false))
{
    Console.WriteLine(" {0} : {1} {2} {3} ", ++i, itm.uf.Key, itm.ui.UserName, itm.uf.Value);
}

앞서 페이징에 사용했던 Skip(), Take() 메소드가 정수 인수를 받아서 특정 개수의 결과를 건너뛰고 특정 개수의 결과만을 추출했다면 SkipWhile()과 TakeWhile()은 람다 구문으로 조건을 기술해서 해당 조건을 만족하는 동안만 해당 결과를 건너뛰고, 조건이 만족하는 동안만 데이터를 추출합니다. 다시 말해서 조건을 만족하지 않는 결과를 만나면 SkipWhile()과 TakeWhile() 메소드는 동작을 멈춤니다. 위의 예제에서 "SkipWhile(row => row.ui.Sex == false)"는 첫 결과부터 조건에 맞지 않기 때문에 바로 "Take(5)"로 진행되었습니다. 두번째 예제는 일단 Skip(2)로 두개의 결과를 건너뛴 다음에 첫 결과는 조건을 만족했지만 그 다음 결과가 조건을 만족하지 않았기 때문에 "TakeWhile((row, idx) => idx<10 && row.ui.Sex == false)"는 바로 중단 되었습니다. 두번째 예제에서 인지할 것은 앞서 다루었던 ElementAt(인덱스) 처럼 SkipWhile()과 TakeWhile()도 인덱스로 결과를 조정할 수 있다는 것입니다.


댓글
댓글쓰기 폼