LINQ – 이것이 C# 이다.
쿼리식 연산자의 종류
– from
– where
– orderby
– select
– group by
– join
– 여러 개의 데이터 원본에 질의
– L I N Q –
LINQ(Language INtegrated Query) 는 컬렉션을 편리하게 다루기 위한 목적으로 만들어진 질의(Query) 언어입니다.
질의란? 무엇인가에 대하여 물어본다는 뜻인데 데이터 질의란 데이터에 대해 물어본다는 말입니다.
기본적인 질문은 아래와 같습니다.
from : 어떤 데이터를 집합에서 찾을 것인가?
where : 어떤 값의 데이터를 찾을 것인가?
orderby : 어떤 기준으로 정렬할 것인가?
select : 어떤 항목을 추출할 것인가?
class Profile { public string Name { get; set; } public int Height { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, }; } }
위의 예시에서 키가 170 이상인 데이터만 찾아서 새 컬렉션으로 추가한다고 한다면?
using System; using System.Collections.Generic; namespace ConsoleApp2 { class Profile { public string Name { get; set; } public int Height { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, // ... }; List<Profile> profiles = new List<Profile>(); foreach (Profile data in arrProfiles ) { if (data.Height >= 170) { profiles.Add(data); } } foreach (Profile data in profiles) { Console.WriteLine("{0} , {1}", data.Name , data.Height); } } } }
코드에 잘못된 부분은 없으나 LINQ를 모르는 프로그래머의 코드 라는 평을 듣습니다.
다음예제는 LINQ를 사용한 예제입니다.
using System; using System.Linq; namespace ConsoleApp2 { class Profile { public string Name { get; set; } public int Height { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, new Profile() { Name = "홍길동" , Height = 193 }, new Profile() { Name = "임꺽정" , Height = 170 } }; // LINQ var profiles = from profile in arrProfiles where profile.Height >= 170 orderby profile.Height ascending select profile; foreach (Profile data in profiles) { Console.WriteLine("{0} , {1}", data.Name, data.Height); } } } }
– from –
모든 쿼리식(Query Expression)은 반드시 from절로 시작합니다.
쿼리식의 대상이 될 데이터 원본(Data Source)과 데이터 원본 안에 들어 있는 각 요소 데이터를 나타내는
범위 변수(Range Variable) (= 쿼리 변수(Query Variable)) 를 from 절에서 지정합니다.
데이터 원본은 아무 형식이나 사용할 수 없고, IEnumerable<T> 인터페이스를 상속하는 형식만 가능합니다.
https://learn.microsoft.com/ko-kr/dotnet/api/system.collections.generic.ienumerable-1?view=net-6.0
배열 또는 컬렉션 객체들은 이를 상속하므로 모드 from절의 데이터 원본으로 사용할 수 있습니다.
범위변수는 foreach 문의 반복변수와 비슷합니다.
단. 반복변수는 데이터 원본으로부터 데이터를 담아내지만, 범위 변수는 실제로 데이터를 담지않습니다.
그래서 쿼리식 외부에서 선언된 변수에 범위 변수의 데이터를 복사해 넣는다든가 하는 일은 할 수 없습니다.
범위변수는 오로지 LINQ 질의 안에서만 통용됩니다.
using System; using System.Linq; namespace ConsoleApp2 { class MainApp { static void Main(string[] args) { int[] intArr = { 10, 41, 7, 67, 3, 13, 15, 25, 76, 89, 44, 33, 22, 1 }; var evenNumber = from data in intArr where data % 2 == 0 orderby data descending select data; foreach (int data1 in evenNumber) { Console.Write(" 짝수 : {0} ", data1); } } } }
– where –
where 은 필터 역할(Filter)을 하는 연산자 입니다.
from 절이 데이터의 원본으로부터 뽑아낸 범위 변수가 가져야 하는 조건을 where 연산자에 인수로 입력하면
LINQ는 해당조건에 부합하는 데이터만을 걸러냅니다.
– orderby –
orderby는 정렬을 수행하는 연산자 입니다.
위의 예제
orderby profile.Height ascending // 오름차순 정렬
orderby data descending // 내림차순 정렬
와 같이 사용합니다.
– select –
select 절은 최종 결과를 추출하는 쿼리식의 마침표 같은 존재입니다.
from 절에서 데이터 원본으로부터 범위 변수를 뽑아내고
where 절에서 이 범위 변수의 조건을 검사한 후
orderby 절에서 결과를 정리하고
select 문을 통하여 최종 결과를 추출해내는 것 입니다.
var profiles = from profile in arrProfiles where profile.Height >= 170 orderby profile.Height select profile;
LINQ 질의 결과는 IEnumerable<T>로 반환되는데, 이떄 형식 매개변수T는 Select 문에 의해 결정됩니다.
위의 질의 결과는 IEnumerable<profile> 형식입니다.
만약 select 문에서 profile 객체 전체가 아닌 Name 프로퍼티만 추출하면 IEnumerable<string> 형식으로 컴파일 됩니다.
var profiles = from profile in arrProfiles where profile.Height >= 170 orderby profile.Height ascending select profile.Name; foreach (string data in profiles) { Console.WriteLine("{0} ", data); }
또한 select 문의 무명 형식을 이용하여 새로운 형식을 즉석에서 만들 수 있습니다.
using System; using System.Linq; namespace ConsoleApp2 { class Profile { public string Name { get; set; } public int Height { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, new Profile() { Name = "홍길동" , Height = 193 }, new Profile() { Name = "임꺽정" , Height = 170 } }; // LINQ var profiles = from profile in arrProfiles where profile.Height >= 170 orderby profile.Height select new { Name = profile.Name, InchHeight = profile.Height * 0.393 }; foreach (var data in profiles) { Console.WriteLine("{0} ", data); } Console.WriteLine(profiles.GetType()); } } }
– group by –
group by 문을 통하여 분류 기준에 따라 데이터를 그룹화 할 수 있습니다.
group A by B into c
A 에서는 from 절에서 뽑아낸 범위 변수를, B에는 분류 기준을, C에는 그룹 변수를 위치시키면 됩니다.
Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, new Profile() { Name = "홍길동" , Height = 193 }, new Profile() { Name = "임꺽정" , Height = 170 } };
위의 예제를 175 미만 / 이상 으로 분류합니다.
var listprofiles = from profile in arrProfiles group profile by profile.Height < 175 into g select new { GroupKey = g.Key, profiles = g };
그룹변수 g에는 height 값이 175미만인 객체는 컬레션과 175 이상인 객체의 컬렉션이 입력되고,
select 문이 추출하는 새로운 무명형식은 컬렉션의 컬렉션이 됩니다.
그리고 이 무명형식의 Profiles 필드는 바로 이 그룹 변수 g를 담게됩니다.
using System; using System.Linq; namespace ConsoleApp2 { class Profile { public string Name { get; set; } public int Height { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, new Profile() { Name = "홍길동" , Height = 193 }, new Profile() { Name = "임꺽정" , Height = 170 } }; // LINQ var listprofiles = from profile in arrProfiles group profile by profile.Height < 175 into g select new { GroupKey = g.Key, profiles = g }; foreach (var Group in listprofiles) { Console.WriteLine(" - 175cm 미만? : {0}", Group.GroupKey); foreach (var data in Group.profiles) { Console.WriteLine("{0} : {1}", data.Name, data.Height); } } } } }
– join –
– 내부 조인
내부조인(Inner) 은 교집합과 비슷합니다.
두 데이터 원본 사이에서 일치하는 데이터들만 연결한 후 반환합니다. 기준은 원본 데이터 입니다.
내부조인을 수행 할떄 기준 데이터 원본에는 존재하지만 연결할 데이터 원본에는 존재하지 않는 데이터는 조인 결과에 포함되지 않습니다.
반대의 경우도 마찬가지 입니다.
using System; using System.Linq; namespace ConsoleApp2 { class Profile { public string Name { get; set; } public int Height { get; set; } public int Number { get; set; } } class Profile2 { public string Star { get; set; } public int Number { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, new Profile() { Name = "홍길동" , Height = 193 }, new Profile() { Name = "임꺽정" , Height = 170 } }; Profile2[] arr2Profiles = { new Profile2() { Star = "정우성" , Number = 111 }, new Profile2() { Star = "김태희" , Number = 222 }, new Profile2() { Star = "이문세" , Number = 333 }, new Profile2() { Star = "하동훈" , Number = 444 }, // new Profile2() { Star = "홍길동" , Number = 555 }, new Profile2() { Star = "임꺽정" , Number = 666 } }; // LINQ var listJoin = from data1 in arrProfiles join data2 in arr2Profiles on data1.Name equals data2.Star select new { Name = data1.Name, Number = data2.Number, Height = data1.Height }; foreach (var data3 in listJoin) { Console.WriteLine("Name(star) : {0}, Number : {1}, Height : {2}", data3.Name, data3.Number, data3.Height); // 홍길동 포함 X } } } }
– 외부 조인
} class Profile2 { public string Star { get; set; } public int Number { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, new Profile() { Name = "홍길동" , Height = 193 }, new Profile() { Name = "임꺽정" , Height = 170 } }; Profile2[] arr2Profiles = { new Profile2() { Star = "정우성" , Number = 111 }, new Profile2() { Star = "김태희" , Number = 222 }, new Profile2() { Star = "이문세" , Number = 333 }, new Profile2() { Star = "하동훈" , Number = 444 }, new Profile2() { Star = "홍길동" }, new Profile2() { Star = "임꺽정" , Number = 666 } }; // LINQ var listJoin = from data1 in arrProfiles join data2 in arr2Profiles on data1.Name equals data2.Star into ps from data2 in ps.DefaultIfEmpty(new Profile2() { Number = 0 } ) select new { Name = data1.Name, Number = data2.Number, Height = data1.Height }; foreach (var data3 in listJoin) { Console.WriteLine("Name(star) : {0}, Number : {1}, Height : {2}", data3.Name, data3.Number, data3.Height); } } } }
– 여러 개의 데이터 원본에 질의 –
LINQ 쿼리식은 데이터 원본에 접근하기 위해 from 절을 사용합니다.
여러 개의 데이터 원본에 접근하려면 이 from 문을 중첩해서 사용하면 됩니다.
using System; using System.Linq; namespace ConsoleApp2 { class Class { public string Name { get; set; } public int[] Score { get; set; } } class MainApp { static void Main(string[] args) { Class[] arrClass = { new Class() { Name = "연두반", Score = new int[]{ 70,24 } }, new Class() { Name = "분홍반", Score = new int[]{ 60,45,87,72 } }, new Class() { Name = "파랑반", Score = new int[]{ 92,30,85,94 } }, new Class() { Name = "노랑반", Score = new int[]{ 90,88,0,10 } } }; var Classes = from c in arrClass from s in c.Score where s < 60 orderby s select new { c.Name, Lowest = s }; foreach (var data in Classes) { Console.WriteLine(" 낙제 : {0} ", data); } } } }
위의 링크는 C#에서 지원하는 11가지의 쿼리식 문법입니다.
53개의 표준 LINQ 연산자
https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=ljc8808&logNo=220662771778 <- 출처
메소드 이름 | 설명 | C# 쿼리식 문법 | |
정렬 | OrderBy | 오름차순으로 값을 정렬 | orderby |
OrderBy Descending | 내림차순으로 값을 정렬 | orderby … descending | |
ThenBy | 오름차순으로 2차 정렬 수행 | orderby …, … | |
ThenBy Descending | 내림차순으로 2차 정렬 수행 | orderby …, … descending | |
Reverse | 컬렉션 요소의 순서를 거꾸로 뒤집는다. | ||
집합 | Distinct | 중복 값을 제거한다. | |
Except | 두 컬렉션 사이의 차집합을 반환한다. 임의의 한 컬렉션(a,b,c,e)에 존재하는데 다른 컬렉션(a,d,f)에는 존재하지 않는 요소들(b,e)을 반환한다. | ||
Intersect | 두 컬렉션 사이의 교집합을 반환한다. 양쪽 컬렉션 양쪽에 존재하는 요소들만 반환한다. | ||
Union | 두 컬렉션 사이의 합집합을 반환한다. 한쪽 컬렉션이 a,b,c,d요소를 갖고 있고, 다른 컬렉션이 a,b,c,d,e 요소를 갖고 있다면 이 두 컬렉션 사이의 합집합은 a,b,c,d,e 이다. | ||
필터링 | OfType | 메소드의 형식 매개 변수로 형식 변환이 가능한 값들만 추출한다. | |
Where | 필터링할 조건을 평가하는 함수를 통과하는 값들만 추출한다. | where | |
수량 연산 | All | 모든 요소가 임의의 조건을 모두 만족시키는지를 평가한다. 결과는 true 이거나 false 둘 중 하나이다. | |
Any | 모든 요소 중 단 하나의 요소라도 임의의 조건을 만족시키는지 평가한다. 결과는 true이거나 false 둘 중 하나이다. | ||
Contains | 명시적 요소가 포함되어 있는지 평가한다. 결과는 true이거나 false 둘 중 하나이다. | ||
데이터 추출 | Select | 값을 추출하여 시퀀스를 만든다. | select |
SelectMany | 여러 개의 데이터 원본으로부터 값을 추출하여 하나의 시퀀스를 만든다. 여러개의 from 절을 사용한다. | ||
데이터 분할 | Skip | 시퀀스에서 지정한 위치까지 요소들을 건너뛴다. | |
SkipWhile | 입력된 조건 함수를 만족시키는 요소들을 건너뛴다. | ||
Take | 시퀀스에서 지정한 요소까지 요소들을 취한다. | ||
TakeWhile | 입력된 조건 함수를 만족시키는 요소들을 취한다. | ||
데이터 결합 | Join | 공통 특성을 가진 서로 다른 두 개의 데이터 소스의 객체를 연결한다. 공통 특성을 키(Key)로 삼아, 키가 일치하는 두 객체의 쌍을 추출한다. | Join … in … on … equals … |
GroupJoin | 기본적으로 Join 연산자와 같은 일을 하되, 조인 결과를 그룹으로 만들어 넣는다. | Join … in … on … equals … into … | |
데이터 그룹화 | GroupBy | 공통된 특성을 공유하는 요소들을 각 그룹으로 묶는다. 각 그룹은 IGrouping<TKey, TElement>객체로 표현된다. | group … by 또는 group … by … into |
ToLookup | 키(Key) 선택 함수를 이용하여 골라낸 요소들을 Lookup<TKey, TElement> 형식의 객체에 삽입한다. ( 이 형식은 하나의 키에 여러 개의 객체를 대응시킬 때 사용하는 컬렉션이다 ) | ||
생성 | DefaultIfEmpty | 빈 컬렉션을 기본값이 할당된 싱글턴 컬렉션으로 바꾼다. | |
Empty | 비어 있는 컬렉션을 반환한다. | ||
Range | 일정 범위의 숫자 시퀀스를 담고 있는 컬렉션을 생성한다. | ||
Repeat | 같은 값이 반복되는 컬렉션을 생성한다. | ||
동등 여부 평가 | SequenceEqual | 두 시퀀스가 서로 일치하는지를 평가한다. | |
요소 접근 | ElementAt | 컬렉션으로부터 임의의 인덱스에 존재하는 요소를 반환한다. | |
ElementAt OrDefault | 컬렉션으로부터 임의의 인덱스에 존재하는 요소를 반환하되, 인덱스가 컬렉션의 범위를 벗어날 때 기본값을 반환한다. | ||
First | 컬렉션의 첫 번째 요소를 반환한다. 조건식이 매개 변수로 입력되는 경우 이 조건을 만족시키는 첫 번째 요소를 반환한다. | ||
FirstOrDefault | First 연산자와 같은 기능을 하되, 반환할 값이 없는 경우 기본값을 반환한다. | ||
Last | 컬렉션의 마지막 요소를 반환한다. 조건식이 매개 변수로 입력되는 경우 이 조건을 만족시키는 마지막 요소를 반환한다. | ||
LastOrDefault | Last 연산자와 같은 기능을 하되, 반환할 값이 없는 경우 기본값을 반환한다. | ||
Single | 컬렉션의 유일한 요소를 반환한다. 조건식이 매개 변수로 입력되는 경우 이 조건을 만족시키는 유일한 요소를 반환한다. | ||
SingleOrDefault | Single 연산자와 같은 기능을 하되, 반환할 값이 없거나 유일한 값이 아닌 경우 주어진 기본값을 반환한다. | ||
형식 변환 | AsEnumerable | 매개 변수를 IEnumerable<T>로 형식 변환하여 반환한다. | |
AsQueryable | (일반화) IEnumerable 객체를 (일반화)IQueryable 형식으로 반환한다. | ||
Cast | 컬렉션의 요소들을 특정 형식으로 변환한다. | 범위 변수를 선언할 때 명시적으로 형식을 지정하면 된다. | |
OfType | 특정 형식으로 형식 변환할 수 있는 값만을 걸러낸다. | ||
ToArray | 컬렉션을 배열로 변환한다. 이 메소드는 강제로 쿼리를 실행한다. | ||
TODictinoary | 키 선택 함수에 근거해서 컬렉션의 요소를 Dictionary<TKey, TValue>에 삽입한다. 이 메소드는 강제로 쿼리를 실행한다. | ||
ToList | 컬렉션을 List<T> 형식으로 변환한다. 이 메소드는 강제로 쿼리를 실행한다. | ||
ToLookup | 키 선택 함수에 근거해서 컬렉션의 요소를 Lookup<TKey, TElement>에 삽입한다. 이 메소드는 강제로 쿼리를 실행한다. | ||
연결 | Concat | 두 시퀀스를 하나의 시퀀스로 연결한다. | |
집계 | Aggregate | 컬렉션의 각 값에 대해 사용자가 정의한 집계 연산을 수행한다. | |
Average | 컬렉션의 각 값에 대한 평균을 계산한다 | ||
Count | 컬렉션에서 조건에 부합하는 요소의 개수를 센다 | ||
LongCount | Count와 동일한 기능을 하지만, 매우 큰 컬렉션을 대상으로 한다는 점이 다르다. | ||
Max | 컬렉션에서 가장 큰 값을 반환한다 | ||
Min | 컬렉션에서 가장 작은 값을 반환한다 | ||
Sum | 컬렉션 내의 값의 합을 계산한다. |
쿼리식과 메소드를 함께 사용한다면 모두 사용 가능합니다.
using System; using System.Linq; namespace ConsoleApp2 { class Profile { public string Name { get; set; } public int Height { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, new Profile() { Name = "홍길동" , Height = 193 }, new Profile() { Name = "임꺽정" , Height = 170 } }; // LINQ double average = (from profile in arrProfiles where profile.Height < 180 select profile).Average(profile => profile.Height); Console.WriteLine("키 180 미만 평균 : {0}", average); } } }
using System; using System.Linq; namespace ConsoleApp2 { class Profile { public string Name { get; set; } public int Height { get; set; } public int Number { get; set; } } class MainApp { static void Main(string[] args) { Profile[] arrProfiles = { new Profile() { Name = "정우성" , Height = 186 }, new Profile() { Name = "김태희" , Height = 158 }, new Profile() { Name = "이문세" , Height = 171 }, new Profile() { Name = "하동훈" , Height = 163 }, new Profile() { Name = "홍길동" , Height = 193 }, new Profile() { Name = "임꺽정" , Height = 170 } }; // LINQ var profiles = arrProfiles.Where(profile => profile.Height < 175) .OrderBy(profile => profile.Height) .Select(profile => new { Name = profile.Name, InchHeight = profile.Height * 0.393 }); foreach (var data in profiles) { Console.WriteLine("{0} {1}",data.Name , data.InchHeight); } } } }