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);
}
}
}
}



