[이것이 C#이다] 15. LINQ

ssu_hyun·2022년 5월 4일
1

C#

목록 보기
19/22
post-thumbnail

Key point

  • LINQ
    • from
    • where
    • orderby
    • select
    • groupby
    • join



15.1 LINQ (Language INtegrated Query)

  • SQL을 본떠 C# 프로그래밍 언어에 통합한 데이터 질의 기능
    • 데이터 질의 : 데이터 집합에서 원하는 데이터를 찾는 작업
  • 기본적인 질문 내용
    • From
    • Where
    • Select

15.2 LINQ 기본

from

  • 데이터 원본으로부터 범위 변수 뽑아내기
  • from절의 매개변수, 즉 데이터 원본은 IEnumerable<T>의 파생 형식(배열, 컬렉션 객체)이어야 한다.
  • 범위 변수는 쿼리 변수(Query Variable)라고도 함

예제

using System;
using System.Linq;

namespace From
{
    class MainApp
    {
        static void Main(string[] args)
        {
            int[] numbers = { 9, 2, 6, 4, 5, 3, 7, 8, 1, 10 };

            var result = from     n in numbers  // from 범위변수 in 데이터원본
                         where    n % 2 == 0
                         orderby  n
                         select   n;

            foreach (int n in result)
                Console.WriteLine($"짝수 : {n}");
        }
    }
}

where

  • 범위 변수의 조건 필터

예제

// 원본 데이터
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}
                        };

// where
var profiles = from    profile in arrProfile
			   where   profile.Height < 175  // Height 175 미만 필터링
               select  profile;

orderby

  • 데이터 정렬

예제

// orderby
var profiles = from    profile in arrProfile
			   where   profile.Height < 175  
               orderby profile.Height  // Height를 기준으로 정렬 (기본 : 오름차순)
               select  profile;

// ascending 키워드
var profiles = from    profile in arrProfile
			   where   profile.Height < 175
               orderby profile.Height ascending
               select  profile;
               
// descending 키워드
var profiles = from    profile in arrProfile
			   where   profile.Height < 175
               orderby profile.Height descending
               select  profile;

select

  • 최종 결과 추출
  • LINQ 질의 결과는 IEnumerable<T>로 반환되는데,
    여기서 매개변수 Tselect문에 의해 결정된다.

예제

var profiles = from    profile in arrProfile
			   where   profile.Height < 175
               orderby profile.Height 
               select  profile;  // IEnumerable<Profile> : Height가 175 미만인 Profile 객체


var profiles = from    profile in arrProfile
			   where   profile.Height < 175
               orderby profile.Height 
               select  profile.Name;  // IEnumerable<string> : Height가 175 미만인 Name 프로퍼티
               
               
var profiles = from    profile in arrProfile
			   where   profile.Height < 175
               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
                           };

            Console.WriteLine(profiles);
            Console.WriteLine();

            foreach (var profile in profiles)
                Console.WriteLine($"{profile.Name}, {profile.InchHeight}");
        }
    }
}


15.3 group by

  • 특정 기준으로 데이터 분류/그룹핑
  • 선언 형식
   group A by B into C

예제

using System;
using System.Linq;

namespace GroupBy
{
    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 listProfile = from profile in arrProfile
                                orderby profile.Height
                                group profile by profile.Height < 175 into g  // 분류/그룹핑
                                select new { GroupKey = g.Key, Profiles = g };

            foreach (var Group in listProfile)  // Group은 IGrouping<T> 형식
            {
                Console.WriteLine($"- 175cm 미만? : {Group.GroupKey}");  // Group.GroupKey -> True/False

                foreach (var profile in Group.Profiles)
                {
                    Console.WriteLine($">>> {profile.Name}, {profile.Height}");
                }                
            }   
        }
    }
}



15.5 join

  • 데이터 원본의 특정 필드 값 비교하여 일치하는 데이터끼리 연결

내부 조인 (Inner Join)

  • 교집합
    • 두 데이터 원본을 비교해 일치하는 데이터 반환
  • 기준 : 첫번째 원본 데이터
  • 선언 형식
   from a in A
   join b in B on a.XXXX equals b.YYYY
   // on절의 join 조건은 동등만 허용 (작거나 큰 경우 허용X)

외부 조인 (Outer Join)

  • 기준 데이터 + 교집합
    • 기준이 되는 데이터 원본의 모든 것을 조인 결과에 포함
  • 연결할 데이터 원본에 기준 데이터 원본의 데이터와 일치하는 데이터가 없다면 그 부분은 빈 값으로 결과를 채움
  • 과정
    1. join절을 이용해 조인을 수행 후 그 결과를 임시 컬렉션에 저장
    2. DefaultIfEmpty 연산을 통해 임시 컬렉션의 비어 있는 조인 결과에 빈 값 삽입
    3. DefaultIfEmpty 연산을 거친 임시 컬렉션에서 다시 from절을 통해 범위 변수 추출
    4. 범위 변수와 기준 데이터 원본에서 뽑아낸 범위 변수를 이용해 최종 결과 추출

예제

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)
        {
        	// 배우 프로필(A)  | Name, Height 필드로 구성  (기준)
            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}                
            };
			
            // 작품 정보(B) | Product, Star 필드로 구성
            Product[] arrProduct = 
            {
                new Product(){Title="비트",        Star="정우성"},
                new Product(){Title="CF 다수",     Star="김태희"},
                new Product(){Title="아이리스",    Star="김태희"},
                new Product(){Title="모래시계",    Star="고현정"},
                new Product(){Title="Solo 예찬",   Star="이문세"}
            };
			
            // 내부 조인 | 겹치는 것 모두 반환
            var listProfile = 
                from profile in arrProfile  // A 요소 추출
                join product in arrProduct on profile.Name equals product.Star
                // [A의 필드 Name]과 [B의 필드 Star]가 동일한 A&B 요소 join
                select new // 
                { 
                    Name = profile.Name, 
                    Work = product.Title,
                    Height = profile.Height
                };

            Console.WriteLine("--- 내부 조인 결과 ---");
            foreach (var profile in listProfile)
            {
                Console.WriteLine("이름:{0}, 작품:{1}, 키:{2}cm", 
                    profile.Name, profile.Work, profile.Height);
            }
			
            
            // 외부 조인 | 기준 데이터와 겹치는 것 모두 반환
            listProfile = 
                from profile in arrProfile  // A 요소 추출 
                join product in arrProduct on profile.Name equals product.Star into ps
                // 모든 A의 요소와 [A의 필드 Name]&[B의 필드 Star]가 동일한 B 요소 join => join 결과 임시 컬렉션(ps)에 저장
                from sub_product in ps.DefaultIfEmpty(new Product(){Title="그런거 없음"})
                // 임시 컬렉션에 DefaultIfEmpty 연산 적용, Title : 빈 곳에 채울 내용
                select new
                {
                    Name = profile.Name,
                    Work = sub_product.Title,
                    Height = profile.Height
                };

            Console.WriteLine();
            Console.WriteLine("--- 외부 조인 결과 ---");
            foreach (var profile in listProfile)
            {
                Console.WriteLine("이름:{0}, 작품:{1}, 키:{2}cm",
                    profile.Name, profile.Work, profile.Height);
            }
        }
    }
}



15.6 LINQ의 비밀과 LINQ 표준 연산자

  • .NET 언어 중 C#과 VB에서만 사용 가능
    • LINQ 쿼리식을 CLR이 이해하는 코드로 번역하도록 컴파일러 업그레이드
    // 쿼리식
    var profiles = from     profile in arrProfile
                   where    profile.Height < 175
                   orderby  profile.Height).
                   select   new {Name = profile.Name, InchHeight = profile.Height * 0.393};
                   
    // C# 컴파일러의 번역
    var profiles = arrProfile  // 각 메소드에 입력되는 람다식의 매개변수로 변경
                                .Where(profile => profile.Height < 175) // where -> Where() 메소드
                                .OrderBy(profile => profile.Height)  // orderby -> OrderBy() 메소드
                                .Select(profile =>  // select -> Select()
                                       new
                                       {
                                           Name = profile.Name,
                                           InchHeight = profile.Height * 0.393
                                       });

예제

using System;
using System.Linq;
/* Where(), OrderBy(), Select() 메소드는
   System.Linq 네임스페이스에 정의되어 있는 IEnumerable<T>의 확장 메소드 */

namespace SimpleLinq2
{
    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 = arrProfile.
                              Where(profile => profile.Height < 175).
                              OrderBy(profile => profile.Height).
                              Select(profile =>
                                     new
                                     {
                                         Name = profile.Name,
                                         InchHeight = profile.Height * 0.393
                                     });

            foreach (var profile in profiles)
                Console.WriteLine($"{profile.Name}, {profile.InchHeight}");
        }
    }
}
  • C# 지원 LINQ 쿼리 식(11) < LINQ 표준 연산자 수 (53)

    • LINQ 쿼리 식과 메소드 함께 사용

      // Average() 메소드 호출 코드 1
      var profiles = from profile in arrProfile
      			   where profile.Height < 180
                     select profile;
      
      double Average = profiles.Average(profile => profile.Height);
      Console.WriteLine(Average); 
      
      // Average() 메소드 호출 코드 2
      double Average = (from profile in arrProfile
      			     where profile.Height < 180
                       select profile).Average(profile => profile.Height);
                     
      Console.WriteLine(Average);

예제

/* 키 175 기준으로 연예인 프로필 그룹을 나누고
   각 그룹의 키가 가장 큰 연예인, 가장 작은 연예인 뽑기 */
   
using System;
using System.Linq;

namespace MinMaxAvg
{
    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 heightStat = from profile in arrProfile
                           group profile by profile.Height < 175 into  g
                           select new
                           {
                               Group   = g.Key==true?"175미만":"175이상",
                               Count   = g.Count(),
                               Max     = g.Max(profile => profile.Height),
                               Min     = g.Min(profile => profile.Height),
                               Average = g.Average(profile => profile.Height)
                           };

            foreach (var stat in heightStat)
            {
                Console.Write("{0} - Count:{1}, Max:{2}, ",
                    stat.Group, stat.Count, stat.Max);
                Console.WriteLine("Min:{0}, Average:{1}",
                    stat.Min, stat.Average);
            }
        }
    }
}


연습문제

  1. 다음과 같은 배열이 있다고 할 때, Cost는 50 이상, MaxSpeed는 150 이상인 레코드만 조회하는 LINQ를 작성하세요.
class Car
{
    public int Cost { get; set; }
    public int MaxSpeed { get; set; }
}


class Program
{
    static void Main(string[] args)
    {
        Car[] cars =
        {
            new Car(){Cost = 56, MaxSpeed = 120},
            new Car(){Cost = 70, MaxSpeed = 150},
            new Car(){Cost = 45, MaxSpeed = 180},
            new Car(){Cost = 32, MaxSpeed = 200},
            new Car(){Cost = 82, MaxSpeed = 280},
         };
        
        // LINQ
        var selected = from car in cars
                       where car.Cost >= 50 && car.MaxSpeed >= 150
                       select car;
        
        foreach (var s in selected)
            Console.WriteLine($"Cost = {s.Cost}, MaxSpeed = {s.MaxSpeed}");
    }
}


  1. 다음 코드에서 cars.Where(c=> c.Cost <60).OrderBy(c=> c.Cost)와 동일한 결과를 반환하는 LINQ를 작성하세요.
class Car
{
    public int Cost { get; set; }
    public int MaxSpeed { get; set; }
}


class Program
{
    static void Main(string[] args)
    {
        Car[] cars =
        {
            new Car(){Cost = 56, MaxSpeed = 120},
            new Car(){Cost = 70, MaxSpeed = 150},
            new Car(){Cost = 45, MaxSpeed = 180},
            new Car(){Cost = 32, MaxSpeed = 200},
            new Car(){Cost = 82, MaxSpeed = 280},
        };
		
        
        // var selected = cars.Where(c => c.Cost < 60).OrderBy(c => c.Cost);
        
        // LINQ
        var selected = from car in cars
                       where car.Cost < 60
                       orderby car.Cost
                       select car;

        foreach (var s in selected)
            Console.WriteLine($"Cost = {s.Cost}, MaxSpeed = {s.MaxSpeed}");
    }
}

0개의 댓글