LINQ – 이것이 C# 이다.

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

https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/concepts/linq/query-expression-syntax-for-standard-query-operators

위의 링크는 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);
            }
        }
    }
}

댓글 달기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다

위로 스크롤