LINQ는 Collection을 편리하게 다루기 위한 Query 언어이다.
Data Query란 데이터에 대해 물어본다는 말로, 질문은 다음을 기본적으로 포함한다.
- From : 어떤 데이터 집합에서 찾을 것인가?
- Where : 어떤 값의 데이터를 찾을 것인가?
- Select : 어떤 항목을 추출할 것인가?
다음과 같은 Class가 있다고 가정해보자
class Profile
{
pubilc string Name {get; set;}
public int Height {get; set;}
}
//Profile 클래스를 기반한 배열
Profile[] arrProfile = {
new Profile(){Name="정우성", Height=186},
new Profile(){Name="김태희", Height=158},
new Profile(){Name="고현정", Height=172},
new Profile(){Name="이문세", Height=178},
new Profile(){Name="하동훈", Height=171},
}
// arrProfile에서 Height프로퍼티가 175 미만인 데이터만 골라 새 컬렉션으로 추출하고 싶다면
var profiles = from profile in arrProfile // arrProfile 안에 있는 각 데이터로부터
where profile.Height < 175 // Height가 175미만인 객체만 골라
orderby profile.Height // 키 오름차순으로 정렬하여
select profile; // profile 객체를 추출
모든 LINQ 쿼리식은 from으로 시작한다.
쿼리식의 대상이 될 Data Source와 데이터 원본 안에 있는 각 요소 데이터를 나타내는 Range Variable를 from에서 지정해줘야한다.
이때 from의 데이터 원본은 IEnumerable<T.> interface를 상속받아야한다
int[] numbers = {1, 2, 3, 4, 5, 6, 7};
// n : Range Variable 범위 변수
// umbers : Data Source 데이터 원본
var result = from n in numbers
where n % 2
orderby n
select n;
where은 필터 역할을 한다.
from에서 만든 범위 변수가 가져야할 조건을 입력하면 해당 조건에 부합하는 데이터만을 가져온다.
//아래와 같은 데이터가 있을때
Profile[] arrProfile = {
new Profile(){Name="정우성", Height=186},
new Profile(){Name="김태희", Height=158},
new Profile(){Name="고현정", Height=172},
new Profile(){Name="이문세", Height=178},
new Profile(){Name="하동훈", Height=171},
}
var profiles = from profile in arrProfile
where profile.Height < 175 // Height가 175미만인 객체만 골라서 추출
select profile;
orderby는 정렬을 수행한다.
기본적으로 오름차순으로 데이터를 정렬하지만, 가독성을 위해서 ascending 키워드를 명시할 수 있다.
var profiles = from profile in arrProfile
where profile.Height
orderby profile.Height ascending // 오름차순
select profile;
var profiles = from profile in arrProfile
where profile.Height
orderby profile.Height descending // 내림차순
select profile;
select는 최종 결과를 추출한다
var profiles = from profile in arrProfile
where profile.Height
orderby profile.Height
select profile;
LINQ의 질의 결과는 IEnumerable<T.>로 반환하는데, 이때 형식 매개변수 T는 select에 의해 결정된다.
예를 들어 위 쿼리문에서는 profiles는 IEnumerable<Profile.>형식이 된다.
만약 select에서 이름만 가지고 오면 profiles는 IEnumerable<string.>형식으로 컴파일 된다.
select profile.Name;
var profiles = from profile in arrProfile
where profile.Height
orderby profile.Height
select new {Name = profile.Name, InchHeight = profile.Height * 0.393};
using System;
using System.Collections.Generic;
using System.Linq;
namespace SimpleLinq
{
class Profile
{
public string Name { get; set; }
public int Height { get; set; }
}
class MainApp
{
static void Main(string[] args)
{
Profile[] arrProfile =
{
new Profile(){Name = "정우성", Height = 186},
new Profile(){Name = "김태희", Height = 158},
new Profile(){Name = "고현정", Height = 172},
new Profile(){Name = "이문세", Height = 178},
new Profile(){Name = "하하", Height = 171}
};
var profiles = from profile in arrProfile
where profile.Height < 175
orderby profile.Height
select new
{
Name = profile.Name,
InchHeight = profile.Height * 0.393
};
foreach (var item in profiles)
Console.WriteLine($"{item.Name}, {item.InchHeight}");
}
}
}
여러 개의 Data Source에 접근하려면 from을 중첩하면 된다.
class Class
{
public string Name {get; set;}
public int[] Score {get; set;} //배열
}
Class[] arrClass =
{
new Class(){Name = "연두반", Score = new int[]{99, 88, 70, 24 } },
new Class(){Name = "분홍반", Score = new int[]{60, 45, 87, 72 } },
new Class(){Name = "파랑반", Score = new int[]{95, 30, 85, 97 } },
new Class(){Name = "노랑반", Score = new int[]{90, 88, 0, 17 } },
};
// 위 배열에서 점수가 60점 미만인 학생이 소속된 학급과 그 학생의 점수를 중첩한 from을 이용해서 추출
var classes = from c in arrClass
from s in c.Score
where s < 60
orderby s
select new { c.Name, Lowest = s };
우선 c를 이용해서 arrClass의 범위 변수를 뽑고, 해당 c.Score로부터 s를 범위변수로 뽑는다.
이때 s는 개별 점수를 나타낸다.
where을 통해서 s가 60보다 낮은지 걸러내고, 그 다음은 무명 형식을 선언해서 낙제점을 맞은 학생의 학급 이름과 점수를 담아낸다.
group by는 분류 기준에 따라 데이터를 그룹화 해준다.
group by는 다음 형식으로 사용한다.
group A by B into C
A는 from에서 뽑아낸 범위 변수를, B는 분류 기준을, C는 그룹 변수를 넣는다.
이전에 사용한 예시를 분류 기준 '175미만인가 175이상인가'로 분류한다면 다음과 같다
Profile[] arrProfile =
{
new Profile(){Name = "정우성", Height = 186},
new Profile(){Name = "김태희", Height = 158},
new Profile(){Name = "고현정", Height = 172},
new Profile(){Name = "이문세", Height = 178},
new Profile(){Name = "하하", Height = 171}
};
var profiles = from profile in arrProfile
group profile by profile.Height < 175 into g
select new {GroupKey = g.key, Profiles = g};
최족 쿼리식의 결과를 모두 담는 listProfile은 다음과 같다
| Name, Height | -----> | Gorup Key | Name, Height |
|---|---|---|---|
| 정우성, 186 | (profile.Height < 175) == true | 김태희, 158 | |
| 김태희, 158 | 하하, 171 | ||
| 고현정, 172 | 고현정, 172 | ||
| 이문세, 178 | (profile.Height < 175) == false | 이문세, 178 | |
| 하하, 171 | 정우성, 186 |
join은 두 Data Source를 연결하는 연산이다.
이때, 각 데이터 원본에서 특정 필드의 값을 비교하여 일치하는 데이터끼리 연결한다.
내부 조인은 교집합과 비슷하다.
두 데이터 원본 사이에서 일치하는 데이터들만 연결한 후 반환한다.
내부 조인은 첫 번째 데이터 원본과 두 번째 데이터 원본의 특정 필드를 비교해서 일치하는 데이터를 반환한다.
이때 기준은 첫번째 원본 데이터다
| [A Data Source] Name, Height | [B Data Source] Product, Star | A.Name == B.Star 내부 조인 |
|---|---|---|
| 정우성, 186 | 비트, 정우성 | 정우성, 비트, 186 |
| 김태희, 158 | CF 다수, 김태희 | 김태희, CF 다수, 158 |
| 고현정, 172 | 아이리스, 김태희 | 김태희, 아이리스, 158 |
| 이문세, 178 | 모래시계, 고현정 | 고현정, 모래시계, 172 |
| 하하, 171 | Solo 예찬, 이문세 | 이문세, Solo 예찬, 178 |
내부 조인을 수행할 때 기준 데이터 원본에는 존재하지만, 연결할 데이터 원본에 존재하지 않는 데이터는 조인 결과에서 제외된다
내부 조인은 다음과 같이 실행된다.
from a in A
join b in B on a.XXX equals b.YYY
기준 데이터 a는 from에서 뽑아낸 범위 변수이고, 연결 대상 데이터 b는 join에서 뽑아낸 변수이다.
join의 on 키워드는 join 조건을 수반한다
이때 on의 조인 조건은 Equality만 허용된다.
예시
var listProfile =
from profile in arrProfile
join product in arrProduct on profile.Name equals product.Star
select new
{
Name = profile.Name,
Work = product.Title,
Height = profile.Height
};
외부 조인은 내부 조인과 비슷하지만 조인 결과에 기준이 되는 데이터 원본이 모두 포함된다.
| [A Data Source] Name, Height | [B Data Source] Product, Star | A.Name == B.Star 외부 조인 |
|---|---|---|
| 정우성, 186 | 비트, 정우성 | 정우성, 비트, 186 |
| 김태희, 158 | CF 다수, 김태희 | 김태희, CF 다수, 158 |
| 고현정, 172 | 아이리스, 김태희 | 김태희, 아이리스, 158 |
| 이문세, 178 | 모래시계, 고현정 | 고현정, 모래시계, 172 |
| 하하, 171 | Solo 예찬, 이문세 | 이문세, Solo 예찬, 178 |
| 하하, , 171 |
연결할 데이터 원본에 기준 데이터 원본의 데이터와 일치하는 데이터가 없다면 그 부분은 빈 값으로 결과를 채우게 된다.
외부 조인은 join 이후에 결과인 임시 컬렉션에 DefaultIfEmpty연산을 통해 비어 있는 결과에 빈 값을 채워 넣는다
예시
var listProfile =
from profile in arrProfile
join product in arrProduct on profile.Name equals product.Star into ps
from product in ps.DefaultIfEmpty(new Product(){Title="그런거 없음"})
select new
{
Name = profile.Name,
Work = product.Title,
Height = profile.Height
};
using System;
using System.Linq;
namespace Join
{
class Profile
{
public string Name { get; set; }
public int Height { get; set; }
}
class Product
{
public string Title { get; set; }
public string Star { get; set; }
}
class MainApp
{
static void Main(string[] args)
{
Profile[] arrProfile =
{
new Profile(){Name = "정우성", Height = 186},
new Profile(){Name = "김태희", Height = 158},
new Profile(){Name = "고현정", Height = 172},
new Profile(){Name = "이문세", Height = 178},
new Profile(){Name = "하하", Height = 171}
};
Product[] arrProduct =
{
new Product(){Title = "비트", Star = "정우성"},
new Product(){Title = "CF", Star = "김태희"},
new Product(){Title = "아이리스", Star = "김태희"},
new Product(){Title = "시계", Star = "고현정"},
new Product(){Title = "예찬", Star = "이문세"},
};
var listProfile =
from profile in arrProfile
join product in arrProduct on profile.Name equals product.Star
select new
{
Name = profile.Name,
Work = product.Title,
Height = profile.Height
};
Console.WriteLine("내부 조인 결과");
foreach (var item in listProfile)
Console.WriteLine($"이름 : {item.Name} 작품 : {item.Work}, 키 : {item.Height}");
listProfile = from profile in arrProfile
join product in arrProduct on profile.Name equals product.Star into ps
from product in ps.DefaultIfEmpty(new Product() { Title = "그런거 없음" })
select new
{
Name = profile.Name,
Work = product.Title,
Height = profile.Height
};
Console.WriteLine("외부 조인 결과");
foreach (var item in listProfile)
Console.WriteLine($"이름 : {item.Name} 작품 : {item.Work}, 키 : {item.Height}");
}
}
}
//Output
내부 조인 결과
이름 : 정우성 작품 : 비트, 키 : 186
이름 : 김태희 작품 : CF, 키 : 158
이름 : 김태희 작품 : 아이리스, 키 : 158
이름 : 고현정 작품 : 시계, 키 : 172
이름 : 이문세 작품 : 예찬, 키 : 178
외부 조인 결과
이름 : 정우성 작품 : 비트, 키 : 186
이름 : 김태희 작품 : CF, 키 : 158
이름 : 김태희 작품 : 아이리스, 키 : 158
이름 : 고현정 작품 : 시계, 키 : 172
이름 : 이문세 작품 : 예찬, 키 : 178
이름 : 하하 작품 : 그런거 없음, 키 : 171