Home 13. LINQ 1
Post
Cancel

13. LINQ 1

LINQ(Language-Integrated Query)

개요

  • LINQ는 데이터 소스에서 데이터를 쿼리하고 변환하는 데 사용.
    • ex) 단일 쿼리로 SQL 데이터베이스에서 검색하고 XML 출력.
  • 데이터 모델 단순화
    • 관계형 데이터베이스에는
      다양한 형식의 데이터 소스에 서로 다른 언어 필요
    • LINQ는 다양한 데이터 소스 및 형식에
      일관된 모델을 제공함으로써
      이러한 상황을 단순화 했다.
    • 쿼리 식은 쉽다.
  • 대부분의 경우 컴파일러가 형식을 유추하기 때문에
    명시적 형식을 제공할 필요는 없지만
    쿼리 식의 변수는 모두 강력한 형식을 갖는다.(LINQ 쿼리 작업의 형식 관계.)
    • 보통 결과를 var로 처리하는 이유인듯
  • 쿼리는 쿼리 변수를 반복할 때까지(예: foreach 문) 실행되지 않는다.
    • 지연된 실행에 관한건 아래.
  • 컴파일 타임에 쿼리 식은 C#에 명시된 규칙에 따라
    표준 쿼리 연산자 메서드 호출으로 변환된다.
    • 쿼리 구문을 사용하여 표현할 수 있는 모든 쿼리는
      메서드 구문으로도 표현할 수 있다.
    • 원래 쓰던 query처럼 쓰는방법, method처럼 쓰는방법에 대한 설명.
      1,2로 나눠 쓸 예정
    • LINQ 쿼리를 작성하는 경우 가능하면 쿼리 구문을 사용하고
      필요한 경우 메서드 구문을 사용하는 것이 좋다.
      두 개의 다른 폼 간에 의미 체계 또는 성능상의 차이는 없다.
    • 쿼리 식이 메서드 구문으로 작성된 동급의 식보다 읽기 쉬운 경우가 많다.
    • C# 언어 사양 / 표준 쿼리 연산자 개요 참고
    • expression with method
      • 일부 linq method는 expression으로 표현할 수 없음
      • Count 또는 Max등등.
        뒤에 보면 더 많음
      • 해당하는 쿼리 식 절이 없으므로 메서드 호출로 표현해야 한다.
        단, expression 으로 구현을 못하는게 아니라
        직접 같은 결과를 도출하려면 번거로움
      • 메서드 구문을 다양한 방법으로 쿼리 구문에 조합할 수 있다.
        따라서 expression은 일반적으로 가독성이 좋고
        method에는 좀더 편리하게 만들어진것들이 더 있음
        잘 조합하는게 좋음.
    • 쿼리 식은 쿼리가 적용되는 형식에 따라
      식 트리 또는 대리자로 컴파일될 수 있다.
      • IEnumerable<T> 쿼리는 대리자로 컴파일.
      • IQueryable 및 IQueryable<T> 쿼리는 식 트리로 컴파일.
      • 자세한 내용은 식 트리를 참조.

Data Source

  • LINQ 쿼리는 .NET Framework 2.0에서 도입된 제네릭 형식을 기반으로 힌다.
  • List<T> 같은 제네릭 컬렉션 클래스의 인스턴스를 만들 때
    “T”를 목록에 포함할 개체 형식으로 대체.
    • 예를 들어 문자열 목록은 List<string>으로,
      Customer 개체 목록은 List<Customer>로 표현함.
      따라서 형식 캐스팅을 수행할 필요가 없어짐.
  • 제네릭 IEnumerable<T> 인터페이스를 암시적으로 지원하면 LINQ로 쿼리할 수 있다.
    • 쿼리가 foreach 문에서 실행되고,
      foreach는 IEnumerable 또는 IEnumerable<T>이 필요.
    • IEnumerable<T> 또는 제네릭 IQueryable<T> 같은
      파생된 인터페이스를 지원하는 형식을 쿼리 가능 형식이라고 함.
  • 이해하는 바가 맞다면
    • query는 foreach를 이용해 실행함.
    • foreach는 IEnumerable<T>을 이용함.
    • foreach를 사용할 수 있는 모든 데이터 형식은 linq를 사용가능해짐.

Query Execution

  • Deferred Execution
    • 특이한점은 쿼리 변수 자체는 쿼리 명령을 저장하는 기능만 함.
      실제 쿼리 실행은 foreach 문에서 쿼리 변수가 반복될 때까지 지연된다.

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      
      void mtd()
      {
        int[] data = { 3, 5, 4, 2, 1 };
        var q = from d in data 
                orderby d 
                select d;
              
        foreach (var item in q) 
        { 
          Console.WriteLine("{0}",item ); 
        }
            
        data[0] = 1000; //3 에서 10으로
            
        foreach(var item in q) 
        {
            Console.WriteLine("{0}",item);
        }
      
      }
      ///   1 2 3 4 5
      ///   1 2 3 4 10
      
      • 쿼리식은 명령만 저장하고 있음
      • 원본 데이터의 변경과 쿼리식은 서로 상관 없음
      • 쿼리식을 직접 검색할 때 그 시점의 데이터를 볼 수 있음
    • foreach 문은 쿼리 결과가 검색되는 위치이기도 하다.
      • 예를 들어 이전 쿼리에서 반복 변수 item은
        반환된 시퀀스에서 각 값을 한 번에 하나씩 저장한다.
    • 쿼리 변수 자체는 쿼리 결과를 저장하지 않으므로
      원하는 만큼 자주 실행할 수 있다.
      • 예를 들어 지속적으로 업데이트되는 데이터베이스가 있을 때
        애플리케이션에서 최근 데이터를 검색하는 쿼리를 작성하고
        이를 일정 간격을 두고 반복적으로 실행하여
        매번 다른 결과를 검색할 수 있다.
  • Forcing Immediate Execution
    • 소스 요소 범위에 대해 집계 함수를 수행하는 쿼리는
      먼저 해당 요소를 반복해야 한다.
      • ex) Count, Max, Average, First 등
    • 이러한 쿼리는 쿼리 자체에서 결과를 반환하려면
      foreach를 사용해야 하기 때문에 명시적 foreach 문 없이 실행된다.
    • 이러한 유형의 쿼리는 IEnumerable 컬렉션이 아니라 단일 값을 반환한다.
    • 배열에서 짝수의 개수를 반환하는 예.
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      
      var evenNumQuery =
          from num in numbers
          where (num % 2) == 0
          select num;
      int evenNumCount = evenNumQuery.Count();
      
      List<int> numQuery2 =
                (from num in numbers
                where (num % 2) == 0
                select num).ToList();
      
      // or like this:
      // numQuery3 is still an int[]
      
      var numQuery3 =
          (from num in numbers
          where (num % 2) == 0
          select num).ToArray();
      
      • 모든 쿼리를 즉시 실행하고 그 결과를 캐시하기 위해
        ToList 또는 ToArray 메서드를 호출할 수 있다.
      • foreach 루프를 쿼리 식 바로 다음에 배치하여 강제로 실행할 수 있고,
        ToList 또는 ToArray를 호출하여
        단일 컬렉션 개체에서 모든 데이터를 캐시할 수도 있다.

Query

  • C# 식이 유효한 모든 컨텍스트에서 사용할 수 있다.
  • 쿼리 식은 SQL 또는 XQuery와 유사한 선언적 구문으로 작성된 절 집합.
    • 다만, 서순이 좀 다름.
  • 각 절에는 하나 이상의 C# 식이 포함되며, 이러한 식은
    자체가 쿼리 식이거나 쿼리 식을 포함할 수 있다.
  • 쿼리 식은 from 절로 시작하고 select 또는 group 절로 끝나야 한다.
    • 첫 번째 from 절과 마지막 select 또는 group 절 사이에
      선택적으로 where, orderby, join, let 절과
      추가 from 절 중에서 하나 이상을 포함할 수 있다.
    • 또한 into 키워드를 사용하여, join 또는 group 절의 결과를
      동일한 쿼리 식의 추가 쿼리 절에 대한 소스로 사용할 수 있다.
  • 흔히 보던 query랑 다를게 없어 보이는 설명인데
    차이점이 있다면 query쓰는 순서, join 정도?
    • 일반적인 db qry는
      결과(select) - data(from) - 조건(where…) 이라면 LINQ는
      data(from) - 조건(where…) - 결과(select) 순서.
    1
    2
    3
    
    var result = from num in nums
                 where num < 10
                 select num
    

from

  • 쿼리 시작점.
  • from이 나타내는 내용은
    • 쿼리 또는 하위쿼리가 실행된 데이터소스
    • 소스 시퀀스의 각 요소를 나타내는 지역 범위 변수
  • 컴파일러는 데이터 소스가 IEnumerable<T>을 구현할 경우
    범위 변수의 형식을 유추한다.
    • 아래의 경우 numbers는 데이터 소스이고 num은 범위 변수.
    • var 키워드가 사용되어도 두 변수는 모두 강력한 형식.
    • 아래의 예제에서 num은 int 형식으로 유추된다.
    • 범위 변수가 강력한 형식이므로 범위 변수에서 메서드를 호출하거나
      다른 작업에서 범위 변수를 사용 가능.
      • 예를 들어 select num을 작성하는 대신에
        select num.ToString() 사용 가능.
      • 또는 select num + 10을 작성하여
        식에서 14, 11, 13, 12, 10 시퀀스를 반환 가능
  • 소스가 ArrayList와 같이 제네릭이 아닌 IEnumerable 형식인 경우에만
    형식을 명시적으로 지정하면 됨. (해당내용은 ArrayList를 쿼리하는 방법 참조.)
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
      class LowNums
      {
      static void Main()
      {
          // A simple data source.
          int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
    
          // Create the query.
          // lowNums is an IEnumerable<int>
          var lowNums = from num in numbers
              where num < 5
              select num;
    
          // Execute the query.
          foreach (int i in lowNums)
          {
              Console.Write(i + " ");
          }
      }
      }
      // Output: 4 1 3 2 0
    
  • 하위에 다른 from 가능
  • 경우에 따라 소스 시퀀스의 각 요소 자체가 시퀀스이거나 시퀀스를 포함할 수 있다.
    • 예를 들어, 시퀀스의 각 학생 개체에 테스트 점수 목록이 포함된
      IEnumerable<Student>가 데이터 소스일 수 있음.
  • 각 Student 요소 내의 내부 목록에 액세스하려면 복합 from 절을 사용.
    • 이 기술은 중첩된 foreach 문을 사용하는 것과 같다.
  • where 또는 orderby 절을 둘 중 하나의 from 절에 추가하여 필터링.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    
    class CompoundFrom
    {
        // The element type of the data source.
        public class Student
        {
            public string LastName { get; set; }
            public List<int> Scores { get; set; }
        }
    
        static void Main()
        {
    
            // Use a collection initializer to create the data source. Note that
            // each element in the list contains an inner sequence of scores.
            List<Student> students = new List<Student>
            {
              new Student {LastName="Omelchenko", Scores= new List<int> {97, 72, 81, 60}},
              new Student {LastName="O'Donnell", Scores= new List<int> {75, 84, 91, 39}},
              new Student {LastName="Mortensen", Scores= new List<int> {88, 94, 65, 85}},
              new Student {LastName="Garcia", Scores= new List<int> {97, 89, 85, 82}},
              new Student {LastName="Beebe", Scores= new List<int> {35, 72, 91, 70}}
            };
    
            // Use a compound from to access the inner sequence within each element.
            // Note the similarity to a nested foreach statement.
            var scoreQuery =  from student in students
                              from score in student.Scores
                                where score > 90
                                select new { Last = student.LastName, score };
    
            // Execute the queries.
            Console.WriteLine("scoreQuery:");
            // Rest the mouse pointer on scoreQuery in the following line to
            // see its type. The type is IEnumerable<'a>, where 'a is an
            // anonymous type defined as new {string Last, int score}. That is,
            // each instance of this anonymous type has two members, a string
            // (Last) and an int (score).
            foreach (var student in scoreQuery)
            {
                Console.WriteLine("{0} Score: {1}", student.Last, student.score);
            }
    
            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
    /*
    scoreQuery:
    Omelchenko Score: 97
    O'Donnell Score: 91
    Mortensen Score: 94
    Garcia Score: 97
    Beebe Score: 91
    */
    

where

  • 데이터 소스의 어떤 요소가 쿼리 식에서 반환될지를 지정 : 필터링.
    • 각 소스 요소(범위 변수로 참조됨)에
      부울 조건(predicate)을 적용하고 참인 요소를 반환.
  • 단일 쿼리 식에는 여러 where 절을 포함할 수 있으며
    단일 절에는 여러 조건부 하위 식을 포함 가능.
  • 첫 번째 또는 마지막 절이 될 수 없다는 점을 제외하고,
    쿼리 식의 거의 모든 곳에 배치할 수 있다.
    • 소스 요소를 그룹화 전에 필터링할지,
      그룹화 후에 필터링할지에 따라
      where 절은 group 절 앞 또는 뒤에 나타날 수 있다.
  • 지정된 조건자가 데이터 소스의 요소에 대해
    유효하지 않은 경우 컴파일 오류 발생.
    이는 LINQ에서 제공하는 강력한 형식 검사의 이점.
    컴파일 시간에 where 키워드는
    Where 표준 쿼리 연산자 메서드에 대한 호출로 변환된다.
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    
      class LINQ_where
      {
      public Action<string> action;
    
      public void test()
      {
          // Data source.
          int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
    
          // Create the query with two where clause.
          var queryLowNums3 =
              from num in numbers
              where num > 0 && num < 10
              where IsEven(num)
              select num;
    
          // Execute the query
          foreach (var s in queryLowNums3)
          {
              action(s.ToString());
          }
      }
    
      bool IsEven(int i)
      {
          return i % 2 == 0;
      }
      }
      // Output:
      // 4 8 6 2
    
  • where num > 0 && num < 10는
    where num > 0 where num < 10 로 변환 가능.
  • 결국, bool로 판단할 수 있는
    식또는 method가 들어가는건 모두 가능하며
    적당히 && || 등으로 묶어서 표현 가능.

select

  • 쿼리 식의 끝 1.
  • 쿼리를 실행할 때 생성되는 값의 형식을 지정.
    또는 쿼리 결과에서 실제 뽑아올 데이터를 지정.
  • 결과는 모든 이전 절의 평가와
    select 절 자체의 모든 계산을 기반으로 한다.
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
    200
    201
    202
    203
    204
    205
    206
    207
    208
    209
    210
    211
    212
    213
    214
    
      class LINQ_select
      {
      public Action<string> action;
      // Define some classes
      public class Student
      {
          public string First { get; set; }
          public string Last { get; set; }
          public int ID { get; set; }
          public List<int> Scores;
          public ContactInfo GetContactInfo(LINQ_select app, int id)
          {
              ContactInfo cInfo =
                  (from ci in app.contactList
                    where ci.ID == id
                    select ci)
                  .FirstOrDefault();
    
              return cInfo;
          }
    
          public override string ToString()
          {
              return First + " " + Last + ":" + ID;
          }
      }
    
      public class ContactInfo
      {
          public int ID { get; set; }
          public string Email { get; set; }
          public string Phone { get; set; }
          public override string ToString() { return Email + "," + Phone; }
      }
    
      public class ScoreInfo
      {
          public double Average { get; set; }
          public int ID { get; set; }
      }
    
      // The primary data source
      List<Student> students = new List<Student>()
      {
          new Student 
          {
              First="Svetlana", 
              Last="Omelchenko", 
              ID=111, 
              Scores= new List<int>() {97, 92, 81, 60}
          },
          new Student 
          {
              First="Claire", 
              Last="O'Donnell", 
              ID=112, 
              Scores= new List<int>() {75, 84, 91, 39}
          },
          new Student 
          {
              First="Sven",
              Last="Mortensen", 
              ID=113, 
              Scores= new List<int>() {88, 94, 65, 91}
          },
          new Student 
          {
              First="Cesar", 
              Last="Garcia", 
              ID=114, 
              Scores= new List<int>() {97, 89, 85, 82}
          },
      };
    
      // Separate data source for contact info.
      List<ContactInfo> contactList = new List<ContactInfo>()
      {
          new ContactInfo {ID=111, Email="SvetlanO@Contoso.com", Phone="206-555-0108"},
          new ContactInfo {ID=112, Email="ClaireO@Contoso.com", Phone="206-555-0298"},
          new ContactInfo {ID=113, Email="SvenMort@Contoso.com", Phone="206-555-1130"},
          new ContactInfo {ID=114, Email="CesarGar@Contoso.com", Phone="206-555-0521"}
      };
    
      public void test()
      {
          LINQ_select app = new LINQ_select();
    
          // Produce a filtered sequence of unmodified Students.
          var studentQuery1 =
              from student in app.students
              where student.ID > 111
              select student;
    
          action("Query1: select range_variable");
          foreach (var s in studentQuery1)
          {
              action(s.ToString());
          }
    
          // Produce a filtered sequence of elements that contain
          // only one property of each Student.
          IEnumerable<String> studentQuery2 =
              from student in app.students
              where student.ID > 111
              select student.Last;
    
          action("\r\n studentQuery2: select range_variable.Property");
          foreach (string s in studentQuery2)
          {
              action(s);
          }
    
          // Produce a filtered sequence of objects created by
          // a method call on each Student.
          IEnumerable<ContactInfo> studentQuery3 =
              from student in app.students
              where student.ID > 111
              select student.GetContactInfo(app, student.ID);
    
          action("\r\n studentQuery3: select range_variable.Method");
          foreach (ContactInfo ci in studentQuery3)
          {
              action(ci.ToString());
          }
    
          // Produce a filtered sequence of ints from
          // the internal array inside each Student.
          IEnumerable<int> studentQuery4 =
              from student in app.students
              where student.ID > 111
              select student.Scores[0];
    
          action("\r\n studentQuery4: select range_variable[index]");
          foreach (int i in studentQuery4)
          {
              action(string.Format("First score = {0}", i));
          }
    
          // Produce a filtered sequence of doubles
          // that are the result of an expression.
          IEnumerable<double> studentQuery5 =
              from student in app.students
              where student.ID > 111
              select student.Scores[0] * 1.1;
    
          action("\r\n studentQuery5: select expression");
          foreach (double d in studentQuery5)
          {
              action(string.Format("Adjusted first score = {0}", d));
          }
    
          // Produce a filtered sequence of doubles that are
          // the result of a method call.
          IEnumerable<double> studentQuery6 =
              from student in app.students
              where student.ID > 111
              select student.Scores.Average();
    
          action("\r\n studentQuery6: select expression2");
          foreach (double d in studentQuery6)
          {
              action(string.Format("Average = {0}", d));
          }
    
          // Produce a filtered sequence of anonymous types
          // that contain only two properties from each Student.
          var studentQuery7 =
              from student in app.students
              where student.ID > 111
              select new { student.First, student.Last };
    
          action("\r\n studentQuery7: select new anonymous type");
          foreach (var item in studentQuery7)
          {
              action(string.Format("{0}, {1}", item.Last, item.First));
          }
    
          // Produce a filtered sequence of named objects that contain
          // a method return value and a property from each Student.
          // Use named types if you need to pass the query variable
          // across a method boundary.
          IEnumerable<ScoreInfo> studentQuery8 =
              from student in app.students
              where student.ID > 111
              select new ScoreInfo
              {
                  Average = student.Scores.Average(),
                  ID = student.ID
              };
    
          action("\r\n studentQuery8: select new named type");
          foreach (ScoreInfo si in studentQuery8)
          {
              action(string.Format("ID = {0}, Average = {1}", si.ID, si.Average));
          }
    
          // Produce a filtered sequence of students who appear on a contact list
          // and whose average is greater than 85.
          IEnumerable<ContactInfo> studentQuery9 =
              from student in app.students
              where student.Scores.Average() > 85
              join ci in app.contactList on student.ID equals ci.ID
              select ci;
    
          action("\r\n studentQuery9: select result of join clause");
          foreach (ContactInfo ci in studentQuery9)
          {
              action(string.Format("ID = {0}, Email = {1}", ci.ID, ci.Email));
          }
    
          // Keep the console window open in debug mode
          action("Press any key to exit.");
      }
      }
    
  • 위는 select로 할 수 있는 모든 짓거리들.
  • select 절에서 생성된 시퀀스의 형식에 따라
    쿼리 변수의 형식이 결정.
    • 위의 경우 studentQuery1 이며
      이전 설명에도 나와있듯이 var이지만 강력한 형식.
  • 가장 단순한 경우에서는 select 절이 범위 변수를 지정합니다.
    • 이것도 studentQuery1의 경우.
    • 이렇게 하면 반환된 시퀀스에
      데이터 소스와 동일한 형식의 요소가 포함된다.
    • 관련 내용은 LINQ 쿼리 작업의 형식 관계 참조.
  • select 절은 소스 데이터를
    새 형식으로 변환(또는 프로젝션)하기 위한 메커니즘도 제공.
    • studentQuery7에서와 같이 이 전과 다른 새로운 형식으로 생성 가능.
    • studentQuery8에 표시된 대로 반환된 시퀀스의 요소에
      소스 요소의 속성 하위 집합만 포함하려는 경우도 가능.
    • 반환된 시퀀스를 최대한 작게 유지하면
      메모리 요구 사항을 줄이고 쿼리 실행 속도를 높일 수 있다.
    • select 절에서 무명 형식을 만들고
      개체 이니셜라이저를 사용하여
      소스 요소의 적절한 속성으로 초기화하면 된다.
    • 관련 내용은 LINQ를 통한 데이터 변환(C#),
      개체 및 컬렉션 이니셜라이저를 참조.

group

  • 쿼리 식의 끝 2
  • group 절은 그룹의 키 값과 일치하는
    0개 이상의 항목이 포함된
    IGrouping<TKey,TElement> 개체 시퀀스를 반환.
    • 예를 들어 각 문자열의 첫 번째 문자에 따라
      문자열 시퀀스를 그룹화할 수 있다.
    • 이 경우 첫 번째 문자는 키로, char 형식이며
      각 IGrouping<TKey,TElement> 개체의 Key 속성에 저장.
  • 컴파일러는 키의 형식을 유추한다.
    • 그룹 키는 문자열, 기본 제공 숫자 형식,
      사용자 정의 명명된 형식, 무명 형식 등 모든 형식가능.
    • 위 경우 IGrouping<char,string> 인듯.
  • by를 사용해 반환된 항목의 그룹화 방법을 지정.
  • 다음은 group의 대략적인 예시

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    
    // Group students by the first letter of their last name
    // Query variable is an IEnumerable<IGrouping<char, Student>>
    var studentQuery2 =
        from student in students
        group student by student.Last[0] into g
        orderby g.Key
        select g;
    
      // Iterate group items with a nested foreach. This IGrouping encapsulates
    // a sequence of Student objects, and a Key of type char.
    // For convenience, var can also be used in the foreach statement.
    foreach (IGrouping<char, Student> studentGroup in studentQuery2)
    {
        Console.WriteLine(studentGroup.Key);
        // Explicit type for student could also be used here.
        foreach (var student in studentGroup)
        {
            Console.WriteLine("   {0}, {1}", student.Last, student.First);
        }
    }
    
    • group 쿼리에 의해 생성된 IGrouping<TKey,TElement> 개체는
      기본적으로 목록의 목록이기 때문에
      중첩된 foreach로 액세스해야 한다.
    • 외부 루프는 그룹 키를 반복하고,
      내부 루프는 그룹 자체에 있는 각 항목을 반복.
    • 그룹에 키가 있지만 요소는 없을 수도 있다.
    • 각 그룹에서 추가 쿼리 작업을 수행하려는 경우
      into 상황별 키워드를 사용하여 임시 식별자를 지정.
    • into를 사용하는 경우 쿼리를 계속 진행하되
      궁극적으로 select 문이나 다른 group 절로 끝내야 한다.
  • 그룹화
    • string
      • group key로 위에서는 char를 사용했지만
        문자열 키를 student.Last등으로 지정할 수 있다.
    • bool

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      
      class GroupSample1
      {
          // The element type of the data source.
          public class Student
          {
              public string First { get; set; }
              public string Last { get; set; }
              public int ID { get; set; }
              public List<int> Scores;
          }
      
          public static List<Student> GetStudents()
          {
              // Use a collection initializer to create the data source. Note that each element
              //  in the list contains an inner sequence of scores.
              List<Student> students = new List<Student>
              {
                  new Student 
                  {
                      First="Svetlana", 
                      Last="Omelchenko", 
                      ID=111, 
                      Scores= new List<int> {97, 72, 81, 60}
                  },
                  new Student 
                  {
                      First="Claire", 
                      Last="O'Donnell", 
                      ID=112, 
                      Scores= new List<int> {75, 84, 91, 39}
                  },
                  new Student 
                  {
                      First="Sven", 
                      Last="Mortensen", 
                      ID=113, 
                      Scores= new List<int> {99, 89, 91, 95}
                  },
                  new Student 
                  {
                      First="Cesar", 
                      Last="Garcia", 
                      ID=114, 
                      Scores= new List<int> {72, 81, 65, 84}
                  },
                  new Student 
                  {
                      First="Debra", 
                      Last="Garcia", 
                      ID=115, 
                      Scores= new List<int> {97, 89, 85, 82}
                  }
              };
      
              return students;
          }
      
          static void Main()
          {
            // Obtain the data source.
            List<Student> students = GetStudents();
      
            // Group by true or false.
            // Query variable is an IEnumerable<IGrouping<bool, Student>>
            var booleanGroupQuery =
                from student in students
                group student by student.Scores.Average() >= 80; //pass or fail!
      
            // Execute the query and access items in each group
            foreach (var studentGroup in booleanGroupQuery)
            {
              Console.WriteLine(studentGroup.Key == true ? "High averages" : "Low averages");
              foreach (var student in studentGroup)
              {
                  Console.WriteLine("   {0}, {1}:{2}", 
                          tudent.Last, student.First, student.Scores.Average());
              }
            }
      
            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
          }
      }
      /* Output:
        Low averages
        Omelchenko, Svetlana:77.5
        O'Donnell, Claire:72.25
        Garcia, Cesar:75.5
        High averages
        Mortensen, Sven:93.5
        Garcia, Debra:88.25
      */
      
      • 결과적으로 bool 키 값에 의해 두 그룹으로 나눠짐.
      • 값은 group 절의 하위 식에서 생성.
    • 숫자 범위

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      
      class GroupSample2
      {
          // The element type of the data source.
          public class Student
          {
              public string First { get; set; }
              public string Last { get; set; }
              public int ID { get; set; }
              public List<int> Scores;
          }
      
          public static List<Student> GetStudents()
          {
              // Use a collection initializer to create the data source. Note that each element
              //  in the list contains an inner sequence of scores.
              List<Student> students = new List<Student>
              {
                  new Student 
                  {
                      First="Svetlana", 
                      Last="Omelchenko", 
                      ID=111, 
                      Scores= new List<int> {97, 72, 81, 60}
                  },
                  new Student 
                  {
                      First="Claire", 
                      Last="O'Donnell", 
                      ID=112, 
                      Scores= new List<int> {75, 84, 91, 39}
                  },
                  new Student 
                  {
                      First="Sven", 
                      Last="Mortensen", 
                      ID=113, 
                      Scores= new List<int> {99, 89, 91, 95}
                  },
                  new Student 
                  {
                      First="Cesar", 
                      Last="Garcia", 
                      ID=114, 
                      Scores= new List<int> {72, 81, 65, 84}
                  },
                  new Student 
                  {
                      First="Debra", 
                      Last="Garcia", 
                      ID=115, 
                      Scores= new List<int> {97, 89, 85, 82}
                  }
              };
      
              return students;
          }
      
          // This method groups students into percentile ranges based on their
          // grade average. The Average method returns a double, so to produce a whole
          // number it is necessary to cast to int before dividing by 10.
          static void Main()
          {
              // Obtain the data source.
              List<Student> students = GetStudents();
      
              // Write the query.
              var studentQuery =
                  from student in students
                  let avg = (int)student.Scores.Average()
                  group student by (avg / 10) into g
                  orderby g.Key
                  select g;
      
              // Execute the query.
              foreach (var studentGroup in studentQuery)
              {
                  int temp = studentGroup.Key * 10;
                  Console.WriteLine("Students with an average between {0} and {1}", 
                          temp, temp + 10);
                  foreach (var student in studentGroup)
                  {
                      Console.WriteLine("   {0}, {1}:{2}", 
                              student.Last, student.First, student.Scores.Average());
                  }
              }
      
              // Keep the console window open in debug mode.
              Console.WriteLine("Press any key to exit.");
              Console.ReadKey();
          }
      }
      /* Output:
          Students with an average between 70 and 80
            Omelchenko, Svetlana:77.5
            O'Donnell, Claire:72.25
            Garcia, Cesar:75.5
          Students with an average between 80 and 90
            Garcia, Debra:88.25
          Students with an average between 90 and 100
            Mortensen, Sven:93.5
      */
      
      • 식을 사용해 백분위수 범위를 나타내는 숫자 그룹 키를 만듦.
      • 메서드 호출 결과를 저장할 위치로
        let을 사용하여 group 절에서 메서드를 두 번 호출할 필요가 없다.
      • 쿼리 식에 메서드를 안전하게 사용하는 방법에 대한 자세한 내용은 쿼리 식의 예외 처리 참조.
    • 복합 키

      1
      
      group person by new {name = person.surname, city = person.city};
      
      • 둘 이상의 키에 따라 요소를 그룹화하려는 경우 복합 키 사용.
      • 무명 형식이나 명명된 형식을 통해 키 요소로 만듦.
      • 위에서는 Person 클래스가 surname 및 city라는 멤버로 선언되었다고 가정했을떄
        group 절은 성과 도시가 동일한 각 개인 집합에 대해 별도 그룹이 생성되도록 한다.
      • 쿼리 변수를 다른 메서드에 전달해야 하는 경우 명명된 형식을 사용한다.
        키에 대해 자동 구현 속성을 사용하여 특수 클래스를 만든 다음
        Equals 및 GetHashCode 메서드를 재정의.
        구조체를 사용할 수도 있으며, 이 경우 이러한 메서드를 엄격하게 재정의하지 않아도 된다.
      • 관련 내용은 자동으로 구현된 속성을 사용하여 간단한 클래스를 구현하는 방법,
        디렉터리 트리의 중복 파일을 쿼리하는 방법을 참조.
    • 비연속 그룹화

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      
      class GroupExample1
      {
          static void Main()
          {
              // Create a data source.
              string[] words = {  "blueberry",  "chimpanzee", "abacus", 
                                  "banana",     "apple",      "cheese" };
      
              // Create the query.
              var wordGroups =
                  from w in words
                  group w by w[0];
      
              // Execute the query.
              foreach (var wordGroup in wordGroups)
              {
                  Console.WriteLine("Words that start with the letter '{0}':", 
                          wordGroup.Key);
                  foreach (var word in wordGroup)
                  {
                      Console.WriteLine(word);
                  }
              }
      
              // Keep the console window open in debug mode
              Console.WriteLine("Press any key to exit.");
              Console.ReadKey();
          }
      }
      /* Output:
            Words that start with the letter 'b':
              blueberry
              banana
            Words that start with the letter 'c':
              chimpanzee
              cheese
            Words that start with the letter 'a':
              abacus
              apple
          */
      
      • 추가 쿼리 논리가 그룹에 적용되지 않는 경우
        소스 데이터를 그룹으로 정렬하기 위해 사용.
      • 문자열 배열에 있는 요소는 첫 문자에 따라 그룹화.
      • 쿼리 결과는 char 형식의 공개 키 속성이 포함 된
        IGrouping<TKey, TElement> 형식과
        그룹화의 각 항목이 포함 된 IEnumerable<T> 컬렉션.
      • group 절의 결과는 시퀀스의 시퀀스이기 때문에
        반환된 각 그룹 내의 개별 요소에 액세스하려면
        그룹 키를 반복하는 루프 안에 중첩된 foreach를 사용.

into

  • group, join, select 절의 결과를 새 식별자에 저장하기 위한 임시 식별자.
  • 이 식별자 자체는 추가 쿼리 명령의 생성기일 수 있다.
  • group 또는 select 절에 사용할 경우 새 식별자의 사용을 ‘연속’ 이라고함.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class IntoSample1
{
    static void Main()
    {

        // Create a data source.
        string[] words = { "apples", "blueberries", "oranges", "bananas", "apricots"};

        // Create the query.
        var wordGroups1 =
            from w in words
            group w by w[0] into fruitGroup
            where fruitGroup.Count() >= 2
            select new { FirstLetter = fruitGroup.Key, Words = fruitGroup.Count() };

        // Execute the query. Note that we only iterate over the groups,
        // not the items in each group
        foreach (var item in wordGroups1)
        {
            Console.WriteLine(" {0} has {1} elements.", item.FirstLetter, item.Words);
        }

        // Keep the console window open in debug mode
        Console.WriteLine("Press any key to exit.");
        Console.ReadKey();
    }
}
/* Output:
  a has 2 elements.
  b has 2 elements.
*/
  • into를 사용하여 임시 식별자 fruitGroup을 활성화하는 방법.
  • 이 식별자는 IGrouping의 유추된 형식을 갖는다.
  • 식별자를 사용해 각 그룹에서 Count 메서드를 호출하고
    둘 이상의 단어를 포함하는 그룹만 선택하고있다.

orderby

  • 반환된 시퀀스 또는 하위 시퀀스(그룹)를 오름차순이나 내림차순으로 정렬.
  • 하나 이상의 보조 정렬 작업을 수행하기 위해 여러 키를 지정할 수 있다.
  • 정렬은 요소 형식에 대한 기본 비교자에 의해 수행됩니다. 기본은 오름차순입니다.
  • 사용자 지정 비교자를 지정할 수도 있다. 단, 메서드 기반 구문을 통해서만 가능.
    • 자세한 내용은 데이터 정렬을 참조.
  • ```c# class OrderbySample2 { // The element type of the data source. public class Student { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } }

    public static List GetStudents() { // Use a collection initializer to create the data source. Note that each element // in the list contains an inner sequence of scores. List students = new List { new Student {First="Svetlana", Last="Omelchenko", ID=111}, new Student {First="Claire", Last="O'Donnell", ID=112}, new Student {First="Sven", Last="Mortensen", ID=113}, new Student {First="Cesar", Last="Garcia", ID=114}, new Student {First="Debra", Last="Garcia", ID=115} };

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    
      return students;   }   static void Main(string[] args)   {
      // Create the data source.
      List<Student> students = GetStudents();
    
      // Create the query.
      IEnumerable<Student> sortedStudents =
          from student in students
          orderby student.Last ascending, student.First ascending
          select student;
    
      // Execute the query.
      Console.WriteLine("sortedStudents:");
      foreach (Student student in sortedStudents)
          Console.WriteLine(student.Last + " " + student.First);
    
      // Now create groups and sort the groups. The query first sorts the names
      // of all students so that they will be in alphabetical order after they are
      // grouped. The second orderby sorts the group keys in alpha order.
      var sortedGroups =
          from student in students
          orderby student.Last, student.First
          group student by student.Last[0] into newGroup
          orderby newGroup.Key
          select newGroup;
    
      // Execute the query.
      Console.WriteLine(Environment.NewLine + "sortedGroups:");
      foreach (var studentGroup in sortedGroups)
      {
          Console.WriteLine(studentGroup.Key);
          foreach (var student in studentGroup)
          {
              Console.WriteLine("   {0}, {1}", student.Last, student.First);
          }
      }
    
      // Keep the console window open in debug mode
      Console.WriteLine("Press any key to exit.");
      Console.ReadKey();   }   }   /* Output:   sortedStudents:   Garcia Cesar   Garcia Debra   Mortensen Sven   O'Donnell Claire   Omelchenko Svetlana
    

sortedGroups: G Garcia, Cesar Garcia, Debra M Mortensen, Sven O O’Donnell, Claire Omelchenko, Svetlana */

1
2
3
4
5
6
7
8
9
- **ascending**
  - 쿼리 식의 orderby 절에서 정렬 순서를 오름차순으로 지정.  
  - ascending은 기본 정렬 순서이므로 지정할 필요가 없다.
  ```c#
  IEnumerable<string> sortAscendingQuery =
    from vegetable in vegetables
    orderby vegetable ascending
    select vegetable;
  • descending
    • 쿼리 식의 orderby 절에서 정렬 순서를 내림차순으로 지정.
      1
      2
      3
      4
      
      IEnumerable<string> sortDescendingQuery =
      from vegetable in vegetables
      orderby vegetable descending
      select vegetable;
      

join

  • 개체 모델에서 직접적인 관계가 없는 서로 다른 소스 시퀀스의 요소를 연결.
  • 각 소스의 요소가 같은지 비교할 수 있는 일부 값을 공유할 수 있어야함.
    • 예를들어 식품 유통업체에 특정 제품의 공급업체 목록과 구매자 목록이 있을 때
      해당 제품의 공급업체 및 구매자 목록을 만드는 데 사용할 수 있다.
  • join 절은 두 개의 소스 시퀀스를 입력으로 사용.
    각 시퀀스의 요소는 다른 시퀀스의 속성과 비교할 수 있는 속성이거나 해당 속성을 포함해야 함.
  • join 절은 특수한 equals 키워드를 사용하여 지정된 키가 같은지 비교한다.
    • join 절로 수행된 모든 조인은 동등 조인이다.
    • join 절의 출력 형태는 수행하는 조인의 특정 유형에 따른다.
  • 일반적인 조인 유형
    • 내부 조인
    • 그룹 조인
    • 왼쪽 우선 외부 조인
  • on 을 사용해 조인 조건을 지정.

  • Inner join

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    var innerJoinQuery = 
        from cate in categories.AsEnumerable()
        join prod in products.AsEnumerable() 
            on cate.Field<string>("ID") equals prod.Field<string>("ID")
        select new
        {
            CateID = cate.Field<string>("ID"),
            CateName = cate.Field<string>("Name"),
            ProdID = prod.Field<string>("ID"),
            ProdName = prod.Field<string>("Name"),
        };
    foreach (var item in innerJoinQuery)
    {
        DataRow dr = result.NewRow();
        dr["CateID"] = item.CateID;
        dr["CateName"] = item.CateName;
        dr["ProdID"] = item.ProdID;
        dr["ProdName"] = item.ProdName;
    
        result.Rows.Add(dr);
    }
    
    • image
    • 내부 동등 조인의 예.
    • 조건은 on뒤에 쓰는데 이떄는 equals만 허용한다함
    • ID를 비교해 같은 ID에 대한 정보 출력.
    • categories의 요소에 일치하는 products가 없는 경우 해당 범주는 결과에 나타나지 않음.
  • Left outer join

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    
    var leftOuterJoinQuery =
        from category in data.categories.AsEnumerable()
        join prod in data.products.AsEnumerable() 
            on category.Field<string>("ID") equals prod.Field<string>("ID") 
            into prodGroup
        from item in prodGroup.DefaultIfEmpty()
        select new
        {
            CateID = category.Field<string>("ID"),
            CateName = category.Field<string>("Name"),
            ProdID = item == null ? string.Empty : item.Field<string>("id"),
            PtodName = item == null ? string.Empty : item.Field<string>("Name") 
        };
      
    foreach (var item in leftOuterJoinQuery)
    {
        DataRow dr = result.NewRow();
        dr["CateID"] = item.CateID;
        dr["CateName"] = item.CateName;
        dr["ProdID"] = item.ProdID;
        dr["ProdName"] = item.PtodName;
    
        result.Rows.Add(dr);
    }
    
    • image
    • Left outer join에서는 오른쪽 시퀀스에 일치하는 요소가 없는 경우에도
      왼쪽 소스 시퀀스의 모든 요소가 반환됩니다.
    • LINQ에서 Left outer join을 수행하려면
      그룹 조인과 함께 DefaultIfEmpty 메서드를 사용하여
      왼쪽 요소에 일치하는 요소가 없을 경우
      생성할 기본 오른쪽 요소를 지정합니다.
    • null을 모든 참조 형식의 기본값으로 사용하거나
      사용자 정의 기본 형식을 지정할 수 있습니다.
  • Group join
    • into 식을 포함한 join .
    • 좀 특이한데 결과가 단순히 inner join처럼 될수도있고
      아니면 left join처럼 될 수도 있음
    • 왼쪽 기준 오른쪽에 있으면 값이 들어오고
      없으면 빈 배열 들어옴.
    • 결과에 그룹화된 결과가 들어온다는점을 제외하면
      개본적으로 inner join과 같음
    • 그룹을 풀어 foreach를 줄여줄 수 있음

    • like inner join
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      
      var groupJoinQuery = 
          from cate in data.categories.AsEnumerable()
          join prod in data.products.AsEnumerable() 
              on cate.Field<string>("ID") equals prod.Field<string>("ID") 
              into prodgroup
          select new
          {
              CateID = cate.Field<string>("ID"),
              CateName = cate.Field<string>("Name"),
              ProdGroup = prodgroup
          };
      foreach (var item in groupJoinQuery)
      {
          foreach (var pg in item.ProdGroup)
          {
              DataRow dr = result.NewRow();
              dr["CateID"] = item.CateID;
              dr["CateName"] = item.CateName;
              dr["ProdID"] = pg.Field<string>("ID");
              dr["ProdName"] = pg.Field<string>("Name");
              result.Rows.Add(dr);
      
          }
      
      }
      
      • image
      • 보통의 경우 inner join처럼 출력.
      • 위 설명에 그룹화된 결과를 제외하면
        기본적으로 inner join과 같다는게 이거.
    • like left join

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      
      var groupJoinQuery2 =
          from cate in data.categories.AsEnumerable()
          join prod in data.products.AsEnumerable()
              on cate.Field<string>("ID") equals prod.Field<string>("ID")
              into prodgroup
          select new
          {
              CateID = cate.Field<string>("ID"),
              CateName = cate.Field<string>("Name"),
              ProdGroup = prodgroup
          };
      
      foreach (var item in groupJoinQuery2)
      {
          if (item.ProdGroup.ToList().Count > 0)
          {
              foreach (var pg in item.ProdGroup)
              {
                  DataRow dr = result.NewRow();
                  dr["CateID"] = item.CateID;
                  dr["CateName"] = item.CateName;
                  dr["ProdID"] = pg.Field<string>("ID");
                  dr["ProdName"] = pg.Field<string>("Name");
                  result.Rows.Add(dr);
      
              }
          }
          else
          {
              DataRow dr = result.NewRow();
              dr["CateID"] = item.CateID;
              dr["CateName"] = item.CateName;
              result.Rows.Add(dr);
          }
      }
      
      • image
      • 데이터 검색을 좀 더 바꿔본다.
      • 사실 위 설명 다시보면 왼쪽기준 오른쪽에 있으면 값이 들어오고
        없으면 빈 배열 들어온다 했다.
        이걸 다 출력하면 left join처럼 된다.
    • 근데 이럴꺼면 굳이 Group Join을 쓸 이유가 있나?
  • equals
    • join 절은 동등 조인을 수행.
      즉, 일치 항목만을 기준으로 두 키가 같은지 비교할 수 있다.
      “보다 큼”이나 “같지 않음”과 같은 다른 유형의 비교는 지원되지 않는다.
    • 동등 조인인지 확인하기 위해 “==” 연산자 대신 “equals” 키워드를 사용.
    • equals 키워드는 join 절에서만 사용할 수 있으며 == 연산자와 다르다.
      • equals를 사용할 경우 왼쪽 키는 외부 소스 시퀀스를 사용하고
        오른쪽 키는 내부 소스를 사용.
      • 외부 소스는 equals의 왼쪽 범위에만 있고 내부 소스 시퀀스는 오른쪽 범위에만 있음.
  • 그 외
    • 비동등조인, 개체 컬렉션 및 관계형 테이블에서 조인, 복합키 등
      많은건 따로 참고.

let

  • 쿼리 식에서 하위 식의 결과를
    후속 절에서 사용하기 위해 사용.
  • 새 범위 변수를 만들고 제공한 식의 결과로 초기화.
  • 값으로 초기화되면 범위 변수를 사용하여 다른 값을 저장할 수 없음.
    그러나 범위 변수가 쿼리 가능 형식을 포함할 경우 쿼리할 수 있음.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    
    class LetSample1
    {
        static void Main()
        {
            string[] strings =
            {
                "A penny saved is a penny earned.",
                "The early bird catches the worm.",
                "The pen is mightier than the sword."
            };
    
            // Split the sentence into an array of words
            // and select those whose first letter is a vowel.
            var earlyBirdQuery =
                from sentence in strings
                let words = sentence.Split(' ')
                from word in words
                let w = word.ToLower()
                where w[0] == 'a' || w[0] == 'e'
                    || w[0] == 'i' || w[0] == 'o'
                    || w[0] == 'u'
                select word;
    
            // Execute the query.
            foreach (var v in earlyBirdQuery)
            {
                Console.WriteLine("\"{0}\" starts with a vowel", v);
            }
    
            // Keep the console window open in debug mode.
            Console.WriteLine("Press any key to exit.");
            Console.ReadKey();
        }
    }
    /* Output:
        "A" starts with a vowel
        "is" starts with a vowel
        "a" starts with a vowel
        "earned." starts with a vowel
        "early" starts with a vowel
        "is" starts with a vowel
    */
    
    • 여기에서는 2가지 용도로 사용하는데
      • 그 자체를 쿼리할 수 있는 열거 가능한 형식을 생성.
      • 쿼리가 범위 변수 word에서 ToLower를 한 번만 호출할 수 있도록 함.
        let을 사용하지 않을 경우 where 절의 각 조건자에서 ToLower를 호출해야 한다.

참고

This post is licensed under CC BY 4.0 by the author.

12. async/await_3 Excention 2

14. LINQ 2

Comments powered by Disqus.