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>로 표현함.
따라서 형식 캐스팅을 수행할 필요가 없어짐.
- 예를 들어 문자열 목록은 List<string>으로,
- 제네릭 IEnumerable<T> 인터페이스를 암시적으로 지원하면 LINQ로 쿼리할 수 있다.
- 쿼리가 foreach 문에서 실행되고,
foreach는 IEnumerable 또는 IEnumerable<T>이 필요. - IEnumerable<T> 또는 제네릭 IQueryable<T> 같은
파생된 인터페이스를 지원하는 형식을 쿼리 가능 형식이라고 함.
- 쿼리가 foreach 문에서 실행되고,
- 이해하는 바가 맞다면
- 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은
반환된 시퀀스에서 각 값을 한 번에 하나씩 저장한다.
- 예를 들어 이전 쿼리에서 반복 변수 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 절의 결과를
동일한 쿼리 식의 추가 쿼리 절에 대한 소스로 사용할 수 있다.
- 첫 번째 from 절과 마지막 select 또는 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
- 일반적인 db qry는
from
- 쿼리 시작점.
- from이 나타내는 내용은
- 쿼리 또는 하위쿼리가 실행된 데이터소스
- 소스 시퀀스의 각 요소를 나타내는 지역 범위 변수
- 컴파일러는 데이터 소스가 IEnumerable<T>을 구현할 경우
범위 변수의 형식을 유추한다.- 아래의 경우 numbers는 데이터 소스이고 num은 범위 변수.
- var 키워드가 사용되어도 두 변수는 모두 강력한 형식.
- 아래의 예제에서 num은 int 형식으로 유추된다.
- 범위 변수가 강력한 형식이므로 범위 변수에서 메서드를 호출하거나
다른 작업에서 범위 변수를 사용 가능.- 예를 들어 select num을 작성하는 대신에
select num.ToString() 사용 가능. - 또는 select num + 10을 작성하여
식에서 14, 11, 13, 12, 10 시퀀스를 반환 가능
- 예를 들어 select num을 작성하는 대신에
- 소스가 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이지만 강력한 형식.
- 위의 경우 studentQuery1 이며
- 가장 단순한 경우에서는 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 절로 끝내야 한다.
- group 쿼리에 의해 생성된 IGrouping<TKey,TElement> 개체는
- 그룹화
- string
- group key로 위에서는 char를 사용했지만
문자열 키를 student.Last등으로 지정할 수 있다.
- group key로 위에서는 char를 사용했지만
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를 사용.
- 추가 쿼리 논리가 그룹에 적용되지 않는 경우
- string
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;
- 쿼리 식의 orderby 절에서 정렬 순서를 내림차순으로 지정.
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); }
- 내부 동등 조인의 예.
- 조건은 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); }
- 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); } }
- 보통의 경우 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); } }
- 데이터 검색을 좀 더 바꿔본다.
- 사실 위 설명 다시보면 왼쪽기준 오른쪽에 있으면 값이 들어오고
없으면 빈 배열 들어온다 했다.
이걸 다 출력하면 left join처럼 된다.
- 근데 이럴꺼면 굳이 Group Join을 쓸 이유가 있나?
- equals
- join 절은 동등 조인을 수행.
즉, 일치 항목만을 기준으로 두 키가 같은지 비교할 수 있다.
“보다 큼”이나 “같지 않음”과 같은 다른 유형의 비교는 지원되지 않는다. - 동등 조인인지 확인하기 위해 “==” 연산자 대신 “equals” 키워드를 사용.
- equals 키워드는 join 절에서만 사용할 수 있으며 == 연산자와 다르다.
- equals를 사용할 경우 왼쪽 키는 외부 소스 시퀀스를 사용하고
오른쪽 키는 내부 소스를 사용. - 외부 소스는 equals의 왼쪽 범위에만 있고 내부 소스 시퀀스는 오른쪽 범위에만 있음.
- equals를 사용할 경우 왼쪽 키는 외부 소스 시퀀스를 사용하고
- join 절은 동등 조인을 수행.
- 그 외
- 비동등조인, 개체 컬렉션 및 관계형 테이블에서 조인, 복합키 등
많은건 따로 참고.
- 비동등조인, 개체 컬렉션 및 관계형 테이블에서 조인, 복합키 등
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를 호출해야 한다.
- 여기에서는 2가지 용도로 사용하는데
Comments powered by Disqus.