https://msdn.microsoft.com/ko-kr/library/bb397933.aspx
https://msdn.microsoft.com/ko-kr/library/bb397676.aspx
요약
from A in B // 데이터 소스
group A by B into C // 그룹화 (A를 키값 B로 그룹화하고 C 식별자로 추가 작업 가능)
let // 임의 식 식별자 만듦
where // 필터링
orderby // 정렬
select new { new_name = ... } // 쿼리 저장
from A in B
group new { } by A // 완전한 소스가 아닌 부분 소스로 익명 형식
또는
group A by new { } into B // 여러 개의 키값 그리고 B 식별자로 추가 작업
select B;
int highScoreCount = // 메서드 구문
(from score in scores
where score > 80
select score)
.Count();
group 뒤에 select 면 key를 기준 반복
group 뒤에 from A in B 데이터 기준 반복
var query = from person in people
join pet in pets on person equals pet.Owner // person과 pet.Owner이 같은 것만 조인
select new { OwnerName = person.FirstName, PetName = pet.Name };
LINQ 쿼리 소개 (C#)
쿼리 작업의 세 가지 부분
1. 데이터 소스를 가져옵니다.
2. 쿼리를 만듭니다.
3. 쿼리를 실행합니다.
예제
class IntroToLINQ { static void Main() { // The Three Parts of a LINQ Query: // 1. Data source. int[] numbers = new int[7] { 0, 1, 2, 3, 4, 5, 6 }; // 2. Query creation. // numQuery is an IEnumerable<int> var numQuery = from num in numbers where (num % 2) == 0 select num; // 3. Query execution. foreach (int num in numQuery) { Console.Write("{0,1} ", num); } } }
XML 불러오기
1. LINQ to XML은 XML 문서를 쿼리 가능한 XElement 형식에 불러옵니다.
// Create a data source from an XML document.
// using System.Xml.Linq;
XElement contacts = XElement.Load(@"c:\myContactList.xml");
LINQ TO SQL 예제
Northwnd db = new Northwnd(@"c:\northwnd.mdf"); // Query for customers in London. IQueryable<Customer> custQuery = from cust in db.Customers where cust.City == "London" select cust;
쿼리
1. 쿼리 변수는 쿼리 명령을 저장합니다.
1. from 절은 데이터 소스를 지정합니다.
2. where 절은 필터를 적용합니다.
3. select 절은 반환된 요소의 형식을 지정합니다.
쿼리 실행
지연된 실행
1. 쿼리 변수 자체는 쿼리 명령을 저장할 뿐입니다.
1. 쿼리의 실제 실행은 foreach 문에서 쿼리 변수를 반복할 때까지 지연됩니다.
2. 이 개념을 지연된 실행(deferred execution)이라고 합니다.
즉시 실행
1. Count, Max, Average, First
즉시 실행 예제
var evenNumQuery = from num in numbers where (num % 2) == 0 select num; int evenNumCount = evenNumQuery.Count();
쿼리 즉시 실행 저장
1. 쿼리의 즉시 실행을 저장하려면 ToList<TSource> 또는 To<TSource>메서드를 호출합니다.
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();
LINQ 및 제네릭 형식
1. List<T>와 같은 제네릭 클래스의 인스턴스를 만들려면 "T"를 저장할 형식으로 바꿔 적습니다.
예) List<string>, List<Customer>
3. IEnumerable<T>는 foreach 문을 사용해서 제네릭 컬렉션 클래스를 열거할 수 있게 해주는 인터페이스입니다.
LINQ 쿼리의 IEnumerable<T> 변수
1. LINQ 쿼리 변수는 IEnumerable<T> 형식 또는 IQueryable<T>의 파생 형식입니다.
예제
IEnumerable<Customer> customerQuery = from cust in customers where cust.City == "London" select cust; foreach (Customer customer in customerQuery) { Console.WriteLine(customer.LastName + ", " + customer.FirstName); }
컴파일러에서 제네릭 형식 선언 처리
1. var 키워드는 from 절에 지정된 데이터 소스를 검사하여 쿼리 변수의 형식을 추론하도록 컴파일러에 지시합니다.
예제
var customerQuery2 = from cust in customers where cust.City == "London" select cust; foreach(var customer in customerQuery2) { Console.WriteLine(customer.LastName + ", " + customer.FirstName); }
기본 LINQ 쿼리 작업
데이터 소스 가져오기
1. LINQ 쿼리에서 첫 번째 단계는 데이터 소스를 지정하는 것입니다.
2. LINQ 쿼리에서 데이터 소스(customers)와 범위 변수(cust)를 제공하기 위해서 from절이 처음에 옵니다.
예제
//queryAllCustomers is an IEnumerable<Customer> var queryAllCustomers = from cust in customers select cust;
3. 범위 변수는 쿼리 식에서 실제 반복이 일어나지 않는 것을 제외하면 foreach 루프에서 반복 변수와 유사합니다.
4. 쿼리가 실행될 때 범위 변수는 customers의 각 연속 요소에 대한 참조 역할을 합니다.
5. 추가 범위 변수가 let 절로 제공될 수 있습니다.
필터링
1. 필터는 쿼리에서 true인 요소만 반환하게 합니다.
2. 필터는 where 절을 사용합니다.
예제
var queryLondonCustomers = from cust in customers where cust.City == "London" select cust;
3. 익숙한 C# 논리 AND 및 OR 연산자를 사용하여 where 절에서 필요한 만큼의 필터 식을 적용할 수 있습니다.
예제
where cust.City=="London" && cust.Name == "Devon"
예제
where cust.City == "London" || cust.City == "Paris"
정렬
1. orderby 절은 반환된 시퀀스의 요소를 정렬시킵니다.
예제
var queryLondonCustomers3 = from cust in customers where cust.City == "London" orderby cust.Name ascending select cust;
2. 역순으로 정렬하려면 descending 절을 사용합니다.
그룹화
1. group 절은 지정한 키에 따라 결과를 그룹화할 수 있습니다.
예제
// queryCustomersByCity is an IEnumerable<IGrouping<string, Customer>> var queryCustomersByCity = from cust in customers group cust by cust.City; // customerGroup is an IGrouping<string, Customer> foreach (var customerGroup in queryCustomersByCity) { Console.WriteLine(customerGroup.Key); foreach (Customer customer in customerGroup) { Console.WriteLine(" {0}", customer.Name); } }
2. 쿼리를 group 절로 끝내면 결과에서 목록 형식을 사용합니다.
3. 목록의 각 요소는 Key 멤버와 해당 Key로 그룹화된 요소의 목록을 가지는 객체입니다.
4. 그룹을 반복하려면 중첩된 foreach 루프를 사용해야 합니다.
5. 바깥쪽 루프는 각 그룹을 반복하고, 안쪽 루프는 각 그룹의 멤버를 반복합니다.
6. 그룹 작업의 결과를 참조하려면 into 키워드를 사용해서 더 깊숙이 쿼리하도록 식별자를 만들어야 합니다.
7. 다음 쿼리는 세 명 이상의 고객을 포함하는 그룹만 반환합니다.
예제
// custQuery is an IEnumerable<IGrouping<string, Customer>> var custQuery = from cust in customers group cust by cust.City into custGroup where custGroup.Count() > 2 orderby custGroup.Key select custGroup;
조인
1. 조인 작업은 데이터 소스에서 명시적으로 모델링되지 않은 시퀀스 간 연결을 만듭니다.
예제
var innerJoinQuery = from cust in customers join dist in distributors on cust.City equals dist.City select new { CustomerName = cust.Name, DistributorName = dist.Name };
선택
1. select 절은 쿼리 결과를 생성하고 각 반환된 요소의 모양이나 형식을 지정합니다.
2. select 절이 소스 요소의 복사본 이외의 다른 내용을 생성하는 경우 해당 작업을 프로젝션이라고 합니다.
LINQ를 통한 데이터 변환
1. 여러 개의 입력 시퀀스를 새 형식의 단일 출력 시퀀스로 병합합니다.
2. 요소가 소스 시퀀스에 있는 각 요소의 한 속성만으로 또는 여러 속성으로 구성된 출력 시퀀스를 만듭니다.
3. 요소가 소스 데이터에서 수행된 작업 결과로 구성된 출력 시퀀스를 만듭니다.
4. 다른 형식으로 출력 시퀀스를 만듭니다. 예를 들어 SQL 행 또는 텍스트 파일에서 XML로 데이터를 변환할 수 있습니다.
여러 입력을 하나의 출력 시퀀스로 결합
class Student { public string First { get; set; } public string Last {get; set;} public int ID { get; set; } public string Street { get; set; } public string City { get; set; } public List<int> Scores; } class Teacher { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public string City { get; set; } }
class DataTransformations
{ static void Main() { // Create the first data source. List<Student> students = new List<Student>() { new Student {First="Svetlana", Last="Omelchenko", ID=111, Street="123 Main Street", City="Seattle", Scores= new List<int> {97, 92, 81, 60}}, new Student {First="Claire", Last="O’Donnell", ID=112, Street="124 Main Street", City="Redmond", Scores= new List<int> {75, 84, 91, 39}}, new Student {First="Sven", Last="Mortensen", ID=113, Street="125 Main Street", City="Lake City", Scores= new List<int> {88, 94, 65, 91}}, }; // Create the second data source. List<Teacher> teachers = new List<Teacher>() { new Teacher {First="Ann", Last="Beebe", ID=945, City = "Seattle"}, new Teacher {First="Alex", Last="Robinson", ID=956, City = "Redmond"}, new Teacher {First="Michiyo", Last="Sato", ID=972, City = "Tacoma"} }; // Create the query. var peopleInSeattle = (from student in students where student.City == "Seattle" select student.Last) .Concat(from teacher in teachers where teacher.City == "Seattle" select teacher.Last); Console.WriteLine("The following students and teachers live in Seattle:"); // Execute the query. foreach (var person in peopleInSeattle) { Console.WriteLine(person); } Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } /* Output: The following students and teachers live in Seattle: Omelchenko Beebe */
각 소스 요소의 하위 집합 선택
1. 소스 요소의 한 멤버만 선택하려면 점 작업을 사용합니다.
var query = from cust in Customers
select cust.City;
2. 소스 요소에서 둘 이상의 속성을 포함하는 요소를 만들려면 명명된 객체나 익명 형식으로 객체 이니셜라이저를 사용할 수 있습니다.
var query = from cust in Customer
select new {Name = cust.Name, City = cust.City};
메모리 내부 객체를 XML로 변환
1. using System.Xml.Linq;
class XMLTransform { static void Main() { // Create the data source by using a collection initializer. // The Student class was defined previously in this topic. 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}}, }; // Create the query. var studentsToXML = new XElement("Root", from student in students let x = String.Format("{0},{1},{2},{3}", student.Scores[0], student.Scores[1], student.Scores[2], student.Scores[3]) select new XElement("student", new XElement("First", student.First), new XElement("Last", student.Last), new XElement("Scores", x) ) // end "student" ); // end "Root" // Execute the query. Console.WriteLine(studentsToXML); // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } }
소스 요소에서 작업 수행
class FormatQuery { static void Main() { // Data source. double[] radii = { 1, 2, 3 }; // Query. IEnumerable<string> query = from rad in radii select String.Format("Area = {0}", (rad * rad) * 3.14); // Query execution. foreach (string s in query) Console.WriteLine(s); // Keep the console open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } /* Output: Area = 3.14 Area = 12.56 Area = 28.26 */
LINQ 쿼리 작업에서 형식 관계
소스 데이터를 변환하는 쿼리
1. 데이터 소스의 형식 인수는 범위 변수의 형식을 결정합니다.
2. 선택된 개체의 형식은 쿼리 변수의 형식을 결정합니다. 여기서 name은 문자열입니다. 따라서 쿼리 변수는 IEnumerable<string>입니다.
3. 쿼리 변수는 foreach 문에서 반복됩니다. 쿼리 변수가 문자열의 시퀀스이므로 반복 변수도 문자열입니다.
소스 데이터를 변환하는 쿼리
1. 데이터 소스의 형식 인수는 범위 변수의 형식을 결정합니다.
2. select 문은 전체 Customer 개체 대신 Name 속성을 반환합니다. Name은 문자열이므로 custNameQuery의 형식 인수는 Customer가 아니라 string입니다.
3. custNameQuery는 문자열의 시퀀스이므로 foreach 루프의 반복 변수도 string이어야 합니다.
1. 데이터 소스의 형식 인수는 항상 쿼리의 범위 변수 형식입니다.
2. select 문은 익명 형식을 생성하므로 var을 사용하여 쿼리 변수를 암시적으로 형식화해야 합니다.
3. 쿼리 변수의 형식은 암시적이므로 foreach 루프의 반복 변수도 암시적이어야 합니다.
컴파일러에서 형식 정보를 유추하도록 지정
LINQ에서의 쿼리 문법 및 메서드 문법
표준 쿼리 연산자 확장 메서드
class QueryVMethodSyntax { static void Main() { int[] numbers = { 5, 10, 8, 3, 6, 12}; //Query syntax: IEnumerable<int> numQuery1 = from num in numbers where num % 2 == 0 orderby num select num; //Method syntax: IEnumerable<int> numQuery2 = numbers.Where(num => num % 2 == 0).OrderBy(n => n); foreach (int i in numQuery1) { Console.Write(i + " "); } Console.WriteLine(System.Environment.NewLine); foreach (int i in numQuery2) { Console.Write(i + " "); } // Keep the console open in debug mode. Console.WriteLine(System.Environment.NewLine); Console.WriteLine("Press any key to exit"); Console.ReadKey(); } } /* Output: 6 8 10 12 6 8 10 12 */
LINQ를 지원하는 C# 기능
1. 다음 단원에서는 C# 3.0에 도입된 새로운 언어 구문을 소개합니다.
2. 이러한 새 기능은 모두 LINQ 쿼리와 함께 사용되지만 LINQ로 제한되지 않으며 유용한 모든 컨텍스트에서 사용할 수 있습니다.
쿼리 식
1. 컴파일 타임에 쿼리 구문이 표준 쿼리 연산자 확장 메서드의 LINQ 공급자 구현에 대한 메서드 호출로 변환됩니다.
2. 다음 쿼리 식은 문자열 배열을 가지며, 문자열의 첫 문자에 따라 그룹화하고 정렬합니다.
var query = from str in stringArray group str by str[0] into stringGroup orderby stringGroup.Key select stringGroup;
암시적으로 형식이 지정된 변수(var)
1. 변수를 선언하고 초기화할 때 명시적으로 형식을 지정하지 않고, var 한정어는 컴파일러에게 형식을 추론해서 할당하도록 지시합니다.
var number = 5; var name = "Virginia"; var query = from str in stringArray where str[0] == 'm' select str;
개체 및 이니셜라이저
1. 객체 및 컬렉션 이니셜라이저를 사용하면 객체의 생성자를 명시적으로 호출하지 않고 객체를 초기화할 수 있습니다.
2. 이니셜라이저는 일반적으로 소스 데이터를 새로운 데이터 형식으로 변환할 때 쿼리 식에서 사용됩니다.
3. public Name 및 Phone 속성을 가진 Customer라는 클래스가 있다고 가정할 경우 다음 코드와 같이 개체 이니셜라이저를 사용할 수 있습니다.
Customer cust = new Customer { Name = "Mike", Phone = "555-1212" };
익명 형식
1. 익명 형식은 컴파일러에서 생성되며 컴파일러에서만 형식 이름을 사용할 수 있습니다.
2. 익명 형식은 별도의 명명된 형식을 정의하지 않고도 쿼리 결과의 속성 그룹을 일시적으로 그룹화하는 편리한 방법을 제공합니다.
3. 익명 형식은 다음과 같이 새 식과 개체 이니셜라이저를 사용하여 초기화됩니다.
select new {name = cust.Name, phone = cust.Phone};
확장 메서드
1. 확장 메서드는 형식의 인스턴스 메서드인 것처럼 호출할 수 있도록 형식과 연결될 수 있는 static 메서드입니다.
2. 이 기능을 사용하면 실제로 기존 형식을 수정하지 않고도 기존 형식에 새 메서드를 "추가"할 수 있습니다.
3. 표준 쿼리 연산자는 IEnumerable<T>을 구현하는 모든 형식에 LINQ 쿼리 기능을 제공하는 확장 메서드 집합입니다.
람다 식
1. 람다 식은 => 연산자를 사용하여 입력 매개 변수를 함수 본문과 분리하는 인라인 함수이며 컴파일할 때 대리자나 식 트리로 변환될 수 있습니다.
2. LINQ 프로그래밍에서는 표준 쿼리 연산자에 대한 직접 메서드 호출을 수행할 때 람다 식이 사용됩니다.
자동으로 구현된 속성
1. 자동으로 구현된 속성을 사용하면 속성 선언이 보다 간단해집니다.
2. 다음 예제와 같이 속성을 선언하면 컴파일러에서 속성의 getter 및 setter를 통해서만 액세스할 수 있는 전용 익명 지원 필드를 만듭니다.
public string Name {get; set;}
객체 및 컬렉션 이니셜라이저
https://msdn.microsoft.com/ko-kr/library/bb384062.aspx
1. 개체 이니셜라이저를 사용하면 할당문 줄이 있는 생성자를 호출하지 않고도 생성 시 개체의 모든 액세스 가능한 필드나 속성에 값을 할당할 수 있습니다.
2. 개체 이니셜라이저 구문을 사용하면 생성자의 인수를 지정하거나 인수(및 괄호 구문)를 생략할 수 있습니다.
ex) Cat cat = new Cat { }; // 인수와 괄호 생략됨
3. 다음 예제는 명명된 형식인 Cat의 개체 이니셜라이저를 사용하는 방법과 기본 생성자를 호출하는 방법을 보여 줍니다.
4. Cat 클래스의 자동 구현된 속성 사용을 확인합니다.
5. 자세한 내용은 자동으로 구현된 속성(C# 프로그래밍 가이드)을 참조하세요.
class Cat { // Auto-implemented properties. public int Age { get; set; } public string Name { get; set; } }
Cat cat = new Cat { Age = 10, Name = "Fluffy" };
익명 형식의 객체 이니셜라이저
1. 개체 이니셜라이저는 모든 컨텍스트에서 사용할 수 있지만 특히 LINQ 쿼리 식에 유용합니다.
2. 쿼리 식은 익명 형식을 자주 사용하고, 익명 형식은 다음 선언에 나와 있는 것처럼 객체 이니셜라이저 사용으로만 초기화될 수 있습니다.
var pet = new { Age = 10, Name = "Fluffy" };
3. 익명 형식을 사용하면 select 절로 원래 시퀀스를 값과 모양이 다른 객체로 변환할 수 있습니다.
var productInfos = from p in products select new { p.ProductName, p.UnitPrice };
foreach(var p in productInfos){...}
4. 익명 형식을 만들 때 필드 이름을 바꿀 수도 있습니다.
5. 다음 예제에서는 UnitPrice 필드의 이름을 Price로 바꿉니다.
select new {p.ProductName, Price = p.UnitPrice};
nullable 형식의 개체 이니셜라이저
1. nullable 구조체에 개체 이니셜라이저를 사용하는 것은 컴파일 타임 오류입니다.
컬렉션 이니셜라이저
1. 요소 이니셜라이저는 단순한 값, 식 또는 개체 이니셜라이저일 수 있습니다.
2. 컬렉션 이니셜라이저를 사용하면 소스 코드에서 클래스의 Add 메서드를 여러번 호출할 필요가 없습니다.
3. 컴파일러가 호출을 추가합니다.
List<int> digits = new List<int> { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; List<int> digits2 = new List<int> { 0 + 1, 12 % 3, MakeInt() };
4. 개별 개체 이니셜라이저는 중괄호로 묶이고 쉼표로 구분합니다.
List<Cat> cats = new List<Cat> { new Cat(){ Name = "Sylvester", Age=8 }, new Cat(){ Name = "Whiskers", Age=2 }, new Cat(){ Name = "Sasha", Age=14 } };
5. 컬렉션의 Add 메서드가 허용한다면 컬렉션 이니셜라이저에서 null을 요소로 지정할 수 있습니다.
List<Cat> moreCats = new List<Cat> { new Cat(){ Name = "Furrytail", Age=5 }, new Cat(){ Name = "Peaches", Age=4 }, null };
6. 컬렉션이 인덱싱을 지원한다면 인덱스 요소를 지정할 수 있습니다.
var numbers = new Dictionary<int, string> { [7] = "seven", [9] = "nine", [13] = "thirteen" };
예제
// The following code consolidates examples from the topic. class ObjInitializers { class Cat { // Auto-implemented properties. public int Age { get; set; } public string Name { get; set; } } static void Main() { Cat cat = new Cat { Age = 10, Name = "Fluffy" }; List<Cat> cats = new List<Cat> { new Cat(){ Name = "Sylvester", Age=8 }, new Cat(){ Name = "Whiskers", Age=2 }, new Cat(){ Name = "Sasha", Age=14 } }; List<Cat> moreCats = new List<Cat> { new Cat(){ Name = "Furrytail", Age=5 }, new Cat(){ Name = "Peaches", Age=4 }, null }; // Display results. System.Console.WriteLine(cat.Name); foreach (Cat c in cats) System.Console.WriteLine(c.Name); foreach (Cat c in moreCats) if (c != null) System.Console.WriteLine(c.Name); else System.Console.WriteLine("List element has null value."); } // Output: //Fluffy //Sylvester //Whiskers //Sasha //Furrytail //Peaches //List element has null value. }
익명 형식
https://msdn.microsoft.com/ko-kr/library/bb397696.aspx
1. 익명 형식은 처음에 명시적으로 형식을 정의할 필요 없이 읽기 전용 속성 집합을 단일 객체로 캡슐화하는 편리한 방식을 제공합니다.
2. 형식 이름은 컴파일러가 생성하고 소스 코드 수준에서는 이용할 수 없습니다.
3. 각 속성의 형식은 컴파일러에 의해 추론됩니다.
4. new 연산자를 객체 이니셜라이저와 함께 사용하여 익명 형식을 만듭니다.
5. 다음 예제에서는 Amount 및 Message라는 이름의 두 속성으로 초기화된 익명 형식을 보여줍니다.
var v = new { Amount = 108, Message = "Hello" }; // Rest the mouse pointer over v.Amount and v.Message in the following // statement to verify that their inferred types are int and string. Console.WriteLine(v.Amount + v.Message);
6. 익명 형식은 하나 이상의 public 읽기 전용 속성을 포함합니다.
7. 메서드 또는 이벤트와 같은 다른 종류의 클래스 멤버는 유효하지 않습니다.
8. 속성을 초기화하는 데 사용되는 식은 null, 익명 함수, 포인터 형식일 수 없습니다.
9. 익명 형식의 선언은 new 키워드로 시작합니다.
10. 선언은 Products에서 두가지의 속성만 사용하는 새로운 형식을 초기화합니다.
var productQuery = from prod in products select new { prod.Color, prod.Price }; foreach (var v in productQuery) { Console.WriteLine("Color={0}, Price={1}", v.Color, v.Price); }
11. 다음 예제에서 표시된 것처럼, 암시적으로 형식이 지정된 지역 변수와 배열을 결합하여 익명 형식의 요소 배열을 만들 수 있습니다.
var anonArray = new[] { new { name = "apple", diam = 4 }, new { name = "grape", diam = 1 }};
12. 익명 형식은 object에서 직접 파생되고 object를 제외한 어떤 형식으로 캐스팅될 수 없는 class 형식입니다.
연습: 쿼리 작성
Student 클래스와 초기화된 students 목록을 추가
public class Student { public string First { get; set; } public string Last { get; set; } public int ID { get; set; } public List<int> Scores; } // Create a data source by using a collection initializer. static 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}}, new Student {First="Debra", Last="Garcia", ID=115, Scores= new List<int> {35, 72, 91, 70}}, new Student {First="Fadi", Last="Fakhouri", ID=116, Scores= new List<int> {99, 86, 90, 94}}, new Student {First="Hanying", Last="Feng", ID=117, Scores= new List<int> {93, 92, 80, 87}}, new Student {First="Hugo", Last="Garcia", ID=118, Scores= new List<int> {92, 90, 83, 78}}, new Student {First="Lance", Last="Tucker", ID=119, Scores= new List<int> {68, 79, 88, 92}}, new Student {First="Terry", Last="Adams", ID=120, Scores= new List<int> {99, 82, 81, 79}}, new Student {First="Eugene", Last="Zabokritski", ID=121, Scores= new List<int> {96, 85, 91, 60}}, new Student {First="Michael", Last="Tucker", ID=122, Scores= new List<int> {94, 92, 91, 91} } };
쿼리 만들기
1. 첫번째 시험에서 점수가 90보다 큰 점수를 가진 모든 학생을 출력하는 간단한 쿼리를 작성합니다.
// Create the query. // The first line could also be written as "var studentQuery =" IEnumerable<Student> studentQuery = from student in students where student.Scores[0] > 90 select student;
쿼리 실행
1. 쿼리를 실행하게 하는 foreach 루프를 작성합니다.
2. 반환된 시퀀스의 각 요소는 foreach 루프에서 반복 변수를 통해 액세스합니다.
3. 이 변수의 형식은 Student이고 쿼리 변수의 형식은 호환 가능한 IEnumerable<Student>입니다.
// Execute the query. // var could be used here also. foreach (Student student in studentQuery) { Console.WriteLine("{0}, {1}", student.Last, student.First); } // Output: // Omelchenko, Svetlana // Garcia, Cesar // Fakhouri, Fadi // Feng, Hanying // Garcia, Hugo // Adams, Terry // Zabokritski, Eugene // Tucker, Michael
다른 필터 조건을 추가하려면
1. 쿼리를 더 구체화하려면 where 절에서 여러 Boolean 조건을 결합합니다.
2. 다음 코드는 첫 시험은 90점을 넘고 마지막 점수가 80점 미만인 학생을 반환합니다.
where student.Scores[0] > 90 && student.Scores[3] < 80
쿼리 수정
1. 결과가 일정한 순서로 되어 있을 경우 결과를 더 쉽게 검색할 수 있습니다.
2. 소스 요소의 액세스 가능한 임의 필드별로 반환된 시퀀스의 순서를 지정할 수 있습니다.
3. 예를 들어 다음 orderby 절은 각 학생의 성에 따라 사전순으로 A부터 Z까지 결과의 순서를 지정합니다.
4. 다음 orderby 절을 쿼리에서 where 문 바로 뒤 및 select 문 앞에 추가합니다.
orderby student.Last ascending
5. 이제 첫 번째 테스트의 점수에 따라 최고 점수부터 최하 점수까지 역순으로 결과의 순서를 지정하도록 orderby 절을 변경합니다.
orderby student.Scores[0] descending
6. 점수를 볼 수 있도록 WriteLine 형식 문자열을 변경합니다.
Console.WriteLine("{0}, {1} {2}", student.Last, student.First, student.Scores[0]);
결과를 그룹화하려면
1. 그룹화는 쿼리 식의 강력한 기능입니다.
2. group 절이 있는 쿼리는 그룹 시퀀스를 생성하고 각 그룹 자체는 Key와 해당 그룹의 모든 멤버로 구성된 시퀀스를 포함합니다.
3. 다음 새 쿼리는 성의 첫 글자를 키로 사용하여 학생을 그룹화합니다.
// studentQuery2 is an IEnumerable<IGrouping<char, Student>> var studentQuery2 = from student in students group student by student.Last[0];
4. char 형식의 키와, Student 객체의 시퀀스를 가지는 그룹 시퀀스가 생성됩니다.
// studentGroup is a IGrouping<char, Student> foreach (var studentGroup in studentQuery2) { Console.WriteLine(studentGroup.Key); foreach (Student student in studentGroup) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } } // Output: // O // Omelchenko, Svetlana // O'Donnell, Claire // M // Mortensen, Sven // G // Garcia, Cesar // Garcia, Debra // Garcia, Hugo // F // Fakhouri, Fadi // Feng, Hanying // T // Tucker, Lance // Tucker, Michael // A // Adams, Terry // Z // Zabokritski, Eugene
변수의 형식이 암시적으로 지정되게 하려면
1. IGroupings의 IEnumerables에 대한 코드를 명시적으로 작성하는 작업은 금방 지루해질 수 있습니다.
2. var를 사용하면 동일한 쿼리와 foreach 루프를 훨씬 더 편리하게 작성할 수 있습니다.
3. var 키워드는 개체의 형식을 변경하지 않으며 단순히 형식을 유추할 것을 컴파일러에 지시합니다.
4. 형식을 변경 studentQuery 및 반복 변수 group 에 var 및 쿼리를 다시 실행 합니다.
5. 이 경우 내부 foreach 루프에서 반복 변수의 형식은 여전히 Student로 지정되며 쿼리가 이전과 동일하게 작동합니다.
6. s 반복 변수를 var로 변경하고 쿼리를 다시 실행하면 동일한 결과가 나타나는 것을 볼 수 있습니다.
var studentQuery3 = from student in students group student by student.Last[0]; foreach (var groupOfStudents in studentQuery3) { Console.WriteLine(groupOfStudents.Key); foreach (var student in groupOfStudents) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } } // Output: // O // Omelchenko, Svetlana // O'Donnell, Claire // M // Mortensen, Sven // G // Garcia, Cesar // Garcia, Debra // Garcia, Hugo // F // Fakhouri, Fadi // Feng, Hanying // T // Tucker, Lance // Tucker, Michael // A // Adams, Terry // Z // Zabokritski, Eugene
키 값별로 그룹의 순서를 지정하려면
1. 앞의 쿼리를 실행하면 그룹은 사전순으로 되어 있지 않습니다.
2. 그룹에 순서를 지정하려면 group 절 뒤에 orderby 절을 제공해야 합니다.
3. 그러나 orderby 절을 사용하려면 먼저 group 절에 의해 만들어진 그룹에 대한 참조로 사용되는 식별자가 필요합니다.
4. 다음과 같이 into 키워드를 사용하여 이 식별자를 제공합니다.
var studentQuery4 = from student in students group student by student.Last[0] into studentGroup orderby studentGroup.Key select studentGroup; foreach (var groupOfStudents in studentQuery4) { Console.WriteLine(groupOfStudents.Key); foreach (var student in groupOfStudents) { Console.WriteLine(" {0}, {1}", student.Last, student.First); } } // Output: //A // Adams, Terry //F // Fakhouri, Fadi // Feng, Hanying //G // Garcia, Cesar // Garcia, Debra // Garcia, Hugo //M // Mortensen, Sven //O // Omelchenko, Svetlana // O'Donnell, Claire //T // Tucker, Lance // Tucker, Michael //Z // Zabokritski, Eugene
let을 사용하여 식별자를 제공하려면
1. let 키워드를 사용하여 쿼리 식의 임의 식 결과에 대한 식별자를 제공할 수 있습니다.
2. 이 식별자는 다음 예제와 같이 편의상 제공될 수도 있고 여러 번 계산할 필요가 없도록 식 결과를 저장하여 성능을 향상시킬 수도 있습니다.
// studentQuery5 is an IEnumerable<string> // This query returns those students whose // first test score was higher than their // average score. var studentQuery5 = from student in students let totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3] where totalScore / 4 < student.Scores[0] select student.Last + " " + student.First; foreach (string s in studentQuery5) { Console.WriteLine(s); } // Output: // Omelchenko Svetlana // O'Donnell Claire // Mortensen Sven // Garcia Cesar // Fakhouri Fadi // Feng Hanying // Garcia Hugo // Adams Terry // Zabokritski Eugene // Tucker Michael
쿼리 식에서 메서드 구문을 사용하려면
1. Query Syntax and Method Syntax in LINQ (C#)에 설명된 것처럼 일부 쿼리 작업은 메서드 구문에 의해서만 표현될 수 있습니다.
2. 다음 코드는 소스 시퀀스에서 각 Student의 총 점수를 계산한 다음 해당 쿼리의 결과에서 Average() 메서드를 호출하여 클래스의 평균 점수를 계산합니다. 쿼리 식을 묶은 괄호에 주의합니다.
var studentQuery6 = from student in students let totalScore = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3] select totalScore; double averageScore = studentQuery6.Average(); Console.WriteLine("Class average score = {0}", averageScore); // Output: // Class average score = 334.166666666667
Select 절에서 변환 또는 프로젝션하려면
1. 쿼리에서는 소스 시퀀스의 요소와 다른 요소를 가진 시퀀스를 생성하는 것이 매우 일반적입니다.
2. 이전 쿼리 또는 실행 루프를 삭제하거나 주석으로 처리하고 다음 코드로 바꿉니다.
3. 쿼리는 Students가 아니라 문자열 시퀀스를 반환하며 foreach 루프에서 이 사실이 반영됩니다.
IEnumerable<string> studentQuery7 = from student in students where student.Last == "Garcia" select student.First; Console.WriteLine("The Garcias in the class are:"); foreach (string s in studentQuery7) { Console.WriteLine(s); } // Output: // The Garcias in the class are: // Cesar // Debra // Hugo
4. 이 연습의 앞부분에 있는 코드는 평균 클래스 점수가 약 334라는 것을 나타냈습니다.
5. 클래스 평균보다 총 점수가 높은 Students의 시퀀스를 생성하려면 해당 Student ID와 함께 익명 형식을 select 문에서 사용할 수 있습니다.
var studentQuery8 = from student in students let x = student.Scores[0] + student.Scores[1] + student.Scores[2] + student.Scores[3] where x > averageScore select new { id = student.ID, score = x }; foreach (var item in studentQuery8) { Console.WriteLine("Student ID: {0}, Score: {1}", item.id, item.score); } // Output: // Student ID: 113, Score: 338 // Student ID: 114, Score: 353 // Student ID: 116, Score: 369 // Student ID: 117, Score: 352 // Student ID: 118, Score: 343 // Student ID: 120, Score: 341 // Student ID: 122, Score: 368
LINQ 쿼리 식
https://msdn.microsoft.com/ko-kr/library/bb397676.aspx
class LINQQueryExpressions { static void Main() { // Specify the data source. int[] scores = new int[] { 97, 92, 81, 60 }; // Define the query expression. IEnumerable<int> scoreQuery = from score in scores where score > 80 select score; // Execute the query. foreach (int i in scoreQuery) { Console.Write(i + " "); } } } // Output: 97 92 81
항목 | 설명 |
---|---|
기본적인 쿼리 개념을 소개하고 C# 쿼리 구문의 예제를 제공합니다. | |
몇 가지 기본적인 쿼리 식 형식의 예제를 제공합니다. | |
잠재적 예외 throw 코드를 쿼리 식 외부로 이동하는 방법 및 시기를 보여 줍니다. | |
How to: Populate Object Collections from Multiple Sources (LINQ) | select 문을 사용하여 여러 소스의 데이터를 새 형식으로 병합하는 방법을 보여 줍니다. |
group 절을 사용하는 다양한 방법을 보여 줍니다. | |
중첩 그룹을 만드는 방법을 보여 줍니다. | |
쿼리의 하위 식을 새 쿼리의 데이터 소스로 사용하는 방법을 보여 줍니다. | |
스트리밍 데이터 소스에 대한 그룹화 작업을 수행할 수 있는 스레드로부터 안전한 표준 쿼리 연산자를 구현하는 방법을 보여 줍니다. | |
동등 비교에 사용할 임의 개수의 값을 where 절에 제공하는 방법을 보여 줍니다. | |
foreach 루프를 사용하지 않고도 쿼리 결과를 구체화하고 저장하는 방법을 보여 줍니다. | |
메서드에서 쿼리 변수를 반환하는 방법 및 이러한 변수를 입력 매개 변수로 메서드에 전달하는 방법을 보여 줍니다. | |
조건자 함수 종류를 기반으로 조인 작업을 수행하는 방법을 보여 줍니다. | |
둘 이상의 일치하는 키를 기반으로 두 개의 소스를 조인하는 방법을 보여 줍니다. | |
조인 작업으로 생성된 시퀀스를 정렬하는 방법을 보여 줍니다. | |
LINQ에서 내부 조인을 수행하는 방법을 보여 줍니다. | |
LINQ에서 그룹화된 조인을 생성하는 방법을 보여 줍니다. | |
LINQ에서 왼쪽 우선 외부 조인을 생성하는 방법을 보여 줍니다. | |
LINQ 쿼리에서 null 값을 처리하는 방법을 보여 줍니다. |
쿼리 식 기본 사항
소스 시퀀스가 지정되면 쿼리에서는 다음 세 가지 작업 중 하나를 수행할 수 있습니다.
1. 요소의 하위 집합을 검색하여 개별 요소를 수정하지 않고 새 시퀀스를 생성합니다.
IEnumerable<int> highScoresQuery = from score in scores where score > 80 orderby score descending select score;
2. 요소의 시퀀스를 검색하지만 이를 새 개체 형식으로 변환합니다. 다음 예제에서는 int에서 string으로 변환합니다.
IEnumerable<string> highScoresQuery2 = from score in scores where score > 80 orderby score descending select String.Format("The score is {0}", score);
3. 소스 데이터에 대한 다음과 같은 singleton 값을 검색합니다.
1) 특정 조건과 일치하는 요소 수
2) 가장 큰 값 또는 가장 작은 값을 갖는 요소
3) 조건과 일치하는 첫 번째 항목 또는 지정된 요소 집합에서 특정 값의 합.
int highScoreCount = (from score in scores where score > 80 select score) .Count();
4. 앞의 예제에서는 Count 메서드에 대한 호출 앞에 있는 쿼리 식을 괄호로 묶었습니다.
5. 또한 구체적인 결과를 저장할 새 변수를 사용하여 이 식을 표현할 수도 있습니다.
6. 이 기술을 사용하면 쿼리를 저장하는 변수와 결과를 저장하는 쿼리가 별도로 유지되기 때문에 읽기가 더 쉬워집니다.
IEnumerable<int> highScoresQuery3 = from score in scores where score > 80 select score; int scoreCount = highScoresQuery3.Count();
쿼리 식이란?
1. 쿼리 식은 from 절로 시작하고 select 또는 group 절로 끝나야 합니다.
2. 첫 번째 from 절과 마지막 select 또는 group 절 사이에는 where, orderby, join, let 및 추가 from 절과 같은 선택절 절 중 하나 이상이 포함될 수 있습니다.
3. 또한 into 키워드를 사용하여 join 또는 group 절의 결과를 같은 쿼리 식에 있는 다른 쿼리 절의 소스로 사용할 수 있습니다.
쿼리 변수
1. LINQ에서 쿼리 변수는 쿼리의 결과가 아닌 쿼리를 저장하는 변수입니다. 좀 더 구체적으로 말하면 쿼리 변수는 항상 열거 가능한 형식(enumerable type)이며 foreach 문이나 해당 IEnumerator.MoveNext 메서드에 대한 직접 호출에서 반복되는 경우 요소의 시퀀스를 생성합니다.
2. 다음 코드 예제에서는 하나의 데이터 소스, 하나의 필터링 절 및 순서 지정 절이 있고 소스 요소의 변환은 없는 간단한 쿼리 식을 보여 줍니다. select 절은 쿼리를 끝냅니다.
static void Main() { // Data source. int[] scores = { 90, 71, 82, 93, 75, 82 }; // Query Expression. IEnumerable<int> scoreQuery = //query variable from score in scores //required where score > 80 // optional orderby score descending // optional select score; //must end with select or group // Execute the query to produce the results foreach (int testScore in scoreQuery) { Console.WriteLine(testScore); } } // Outputs: 93 90 82 82
3. 앞의 예제에서 scoreQuery는 쿼리 변수이며 그냥 쿼리라고 하기도 합니다. 쿼리 변수에는 foreach 루프에서 생성되는 실제 결과 데이터가 저장되지 않습니다. 그리고 foreach 문을 실행할 때 쿼리 결과가 쿼리 변수 scoreQuery를 통해 반환되지 않습니다. 대신 반복 변수 testScore를 통해 반환됩니다. scoreQuery 변수는 두 번째 foreach 루프에서 반복될 수 있습니다. 이 변수는 해당 변수와 데이터 소스가 수정되지 않은 경우 동일한 결과를 생성합니다.
4. 쿼리 변수는 쿼리 구문이나 메서드 구문 또는 이 둘의 조합으로 표현한 쿼리를 저장할 수 있습니다. 다음 예제에서 queryMajorCities와 queryMajorCities2는 모두 쿼리 변수입니다.
//Query syntax IEnumerable<City> queryMajorCities = from city in cities where city.Population > 100000 select city; // Method-based syntax IEnumerable<City> queryMajorCities2 = cities.Where(c => c.Population > 100000);
5. 반면 다음 두 예제에서는 각각 쿼리로 초기화되어 있지만 쿼리 변수는 아닌 변수를 보여 줍니다. 이들은 결과를 저장하므로 쿼리 변수가 아닙니다:
int highestScore = (from score in scores select score) .Max(); // or split the expression IEnumerable<int> scoreQuery = from score in scores select score; int highScore = scoreQuery.Max(); List<City> largeCitiesList = (from country in countries from city in country.Cities where city.Population > 10000 select city) .ToList(); // or split the expression IEnumerable<City> largeCitiesQuery = from country in countries from city in country.Cities where city.Population > 10000 select city; List<City> largeCitiesList2 = largeCitiesQuery.ToList();
쿼리 변수에 대한 명시적 및 암시적 형식화
1. 일반적으로 이 문서에서는 쿼리 변수와 select 절 사이의 형식 관계를 보여 주기 위해 쿼리 변수의 명시적 형식을 제공합니다.
2. 그러나 var 키워드를 사용하여 컴파일러에서 컴파일할 때 쿼리 변수 또는 다른 지역 변수의 형식을 유추하도록 할 수도 있습니다.
3. 예를 들어 이 항목의 앞부분에 있는 쿼리 예제를 다음과 같이 암시적 형식화를 사용하여 표현할 수도 있습니다:
// Use of var is optional here and in all queries. // queryCities is an IEnumerable<City> just as // when it is explicitly typed. var queryCities = from city in cities where city.Population > 100000 select city;
쿼리 식 시작
1. 쿼리 식은 from 절로 시작해야 합니다.
2. 이 절은 범위 변수와 함께 데이터 소스를 지정합니다.
3. 범위 변수는 소스 시퀀스가 이동될 때 소스 시퀀스의 각 연속 요소를 나타냅니다.
4. 범위 변수는 데이터 소스에 있는 요소의 형식을 기반으로 하는 강력한 형식입니다.
5. 다음 예제에서 countries는 Country 개체의 배열이므로 범위 변수도 Country로 형식화됩니다.
6. 범위 변수는 강력한 형식이므로 도트 연산자를 사용하여 형식의 사용 가능한 멤버에 액세스할 수 있습니다.
IEnumerable<Country> countryAreaQuery = from country in countries where country.Area > 500000 //sq km select country;
7. 범위 변수는 쿼리가 세미콜론이나 continuation 절로 종료될 때까지 범위 내에 있습니다.
8. 쿼리 식에는 여러 from 절이 포함될 수 있습니다.
9. 소스 시퀀스의 각 요소가 컬렉션이거나 컬렉션을 포함하는 경우 추가 from 절을 사용합니다.
10. 예를 들어 Country 개체의 컬렉션이 있고 이들 각각에 Cities라는 City 개체의 컬렉션이 포함되어 있다고 가정합니다.
11. 각 Country에서 City 개체를 쿼리하려면 다음과 같이 두 개의 from 절을 사용합니다.
IEnumerable<City> cityQuery = from country in countries from city in country.Cities where city.Population > 10000 select city;
쿼리 식 종료
1. 쿼리 식은 select 절 또는 group 절로 끝나야 합니다.
group 절
1. group 을 사용하면 지정하는 키별로 구성된 그룹 시퀀스를 생성할 수 있습니다.
2. 키는 모든 데이터 형식을 사용할 수 있습니다.
3. 예를 들어 다음 쿼리에서는 하나 이상의 Country 개체를 포함하는 그룹 시퀀스를 생성하며 해당 키가 char 값입니다.
var queryCountryGroups = from country in countries group country by country.Name[0];
select 절
1. select 절을 사용하면 다른 모든 형식의 시퀀스를 생성할 수 있습니다.
2. 간단한 select 문에서는 데이터 소스에 포함된 개체와 동일한 형식의 개체 시퀀스를 생성합니다. 이 예제에서 데이터 소스에는 Country 개체가 포함되어 있습니다. orderby 절은 요소를 새로운 순서로 정렬하고 select 절은 다시 정렬된 Country 개체의 시퀀스를 생성합니다.
IEnumerable<Country> sortedQuery = from country in countries orderby country.Area select country;
3. select 절을 사용하여 소스 데이터를 새로운 형식의 시퀀스로 변환할 수도 있습니다.
4. 이러한 변환을 프로젝션이라고도 합니다.
다음 예제에서 select 절은 원래 요소에 있는 필드의 하위 집합만 포함하는 익명 형식의 시퀀스를 프로젝션합니다. 새 개체는 개체 이니셜라이저를 사용하여 초기화됩니다.
// Here var is required because the query // produces an anonymous type. var queryNameAndPop = from country in countries select new { Name = country.Name, Pop = country.Population };
"into"로 연속
1. select 또는 group 절에서 into 키워드를 사용하여 쿼리를 저장하는 임시 식별자를 만들 수 있습니다.
2. 그룹화 또는 선택 작업 후에 쿼리에서 추가 쿼리 작업을 수행해야 하는 경우에 이렇게 합니다.
3. 다음 예제에서는 1천만 범위 내에서 인구에 따라 countries를 그룹화합니다.
4. 이러한 그룹을 만든 후 추가 절에서 일부 그룹을 필터링한 다음 그룹을 오름차순으로 정렬합니다.
5. 이러한 추가 작업을 수행하려면 countryGroup으로 나타내는 연속이 필요합니다.
// percentileQuery is an IEnumerable<IGrouping<int, Country>> var percentileQuery = from country in countries let percentile = (int) country.Population / 10000000 group country by percentile into countryGroup where countryGroup.Key >= 20 orderby countryGroup.Key select countryGroup; // grouping is an IGrouping<int, Country> foreach (var grouping in percentileQuery) { Console.WriteLine(grouping.Key); foreach (var country in grouping) Console.WriteLine(country.Name + ":" + country.Population); }
필터링, 정렬 및 조인
1. 시작하는 from 절과 끝내는 select 또는 group 절 사이에 있는 다른 모든 절(where, join, orderby, from, let)은 선택적입니다.
2. 선택적 절은 쿼리 본문에서 사용하지 않거나 여러 번 사용할 수 있습니다.
where 절
1. where 절을 사용하면 하나 이상의 ㅈ 조건자 식을 기반으로 소스 데이터의 요소를 필터링할 수 있습니다.
2. 다음 예제의 where 절에는 두 개의 조건자가 있습니다.
IEnumerable<City> queryCityPop = from city in cities where city.Population < 200000 && city.Population > 100000 select city;
orderby 절
1. orderby 절을 사용하면 결과를 오름차순이나 내림차순으로 정렬할 수 있습니다.
2. 또한 2차 정렬 순서를 지정할 수도 있습니다. 다음 예제에서는 country 개체에 대해 Area 속성을 사용하여 기본 정렬을 수행합니다.
3. 그런 다음 Population 속성을 사용하여 2차 정렬을 수행합니다.
IEnumerable<Country> querySortedCountries = from country in countries orderby country.Area, country.Population descending select country;
4. ascending 키워드는 선택적이며 순서를 지정하지 않는 경우 기본 정렬 순서로 사용됩니다.
join 절
1. join 절을 사용하면 한 데이터 소스의 요소와 다른 데이터 소스의 열을 각 요소에서 지정한 키 사이의 같음 비교를 기반으로 연결 또는 결합할 수 있습니다.
2. LINQ에서 조인 작업은 해당 요소의 형식이 다른 개체의 시퀀스에 대해 수행합니다.
3. 두 시퀀스를 조인한 후에는 select 또는 group 문을 사용하여 출력 시퀀스에 저장할 요소를 지정해야 합니다.
4. 또한 익명 형식을 사용하여 연관된 각 요소 집합의 속성을 출력 시퀀스의 새 형식으로 결합할 수 있습니다.
5. 다음 예제에서는 해당 Category 속성이 categories 문자열 변수의 범주 중 하나와 일치하는 prod 개체를 연결합니다.
6. Category가 categories의 어떤 문자열과도 일치하지 않는 제품은 필터링됩니다.
7. select 문에서는 해당 속성을 cat 및 prod 모두에서 가져오는 새 형식을 프로젝션합니다.
var categoryQuery = from cat in categories join prod in products on cat equals prod.Category select new { Category = cat, Name = prod.Name };
8. 또한 into 키워드를 사용하여 join 작업의 결과를 임시 변수에 저장하는 방법을 통해 그룹 조인을 수행할 수도 있습니다.
let 절
1. let 절을 사용하면 메서드와 같은 식의 결과를 새 범위 변수에 저장할 수 있습니다.
2. 다음 예제에서 범위 변수 firstName는 Split에서 반환된 문자열 배열의 첫 번째 요소를 저장합니다.
string[] names = { "Svetlana Omelchenko", "Claire O'Donnell", "Sven Mortensen", "Cesar Garcia" }; IEnumerable<string> queryFirstNames = from name in names let firstName = name.Split(new char[] { ' ' })[0] select firstName; foreach (string s in queryFirstNames) Console.Write(s + " "); //Output: Svetlana Claire Sven Cesar
쿼리 식의 하위 쿼리
1. 쿼리 절 자체에 쿼리 식이 포함될 수 있으며 이를 하위 쿼리라고도 합니다.
2. 각 하위 쿼리는 자체의 from 절로 시작되며 이 절은 첫 번째 from 절의 동일한 데이터 소스를 가리키지 않아도 됩니다.
3. 예를 들어 다음 쿼리에서는 select 문에서 그룹화 작업의 결과를 검색하기 위해 사용된 쿼리 식을 보여 줍니다.
var queryGroupMax = from student in students group student by student.GradeLevel into studentGroup select new { Level = studentGroup.Key, HighestScore = (from student2 in studentGroup select student2.Scores.Average()) .Max() };
방법: C#에서 LINQ 쿼리 작성
1. 이 항목에서는 C#에서 LINQ 쿼리를 작성할 수 있는 세 가지 방법을 보여 줍니다.
1. 쿼리 구문 사용
2. 메서드 구문 사용
3. 쿼리 구문과 메서드 구문의 조합 사용
2. 다음 예제에서는 앞에서 설명한 각 방법을 사용하여 몇 가지 간단한 LINQ 쿼리를 보여 줍니다.
3. 일반적으로 규칙은 가능하면 (1)을 사용하고 필요에 따라 (2)와 (3)을 사용하는 것입니다.
예제
쿼리 구문
1. 대부분의 쿼리를 작성하는 데 권장되는 방법은 쿼리 구문을 사용하여 쿼리 식을 만드는 것입니다.
2. 다음 예제에서는 세 개의 쿼리 식을 보여 줍니다.
3. 첫 번째 쿼리 식에서는 where 절이 있는 조건을 적용하여 결과를 필터링하거나 제한하는 방법을 보여 줍니다.
4. 소스 시퀀스에서 값이 7보다 크거나 3보다 작은 요소를 모두 반환합니다.
5. 두 번째 식에서는 반환된 결과의 순서를 지정하는 방법을 보여 줍니다.
6. 세 번째 식에서는 키에 따라 결과를 그룹화하는 방법을 보여 줍니다.
7. 이 쿼리는 단어의 첫 번째 문자를 기준으로 두 개의 그룹을 반환합니다.
// Query #1. List<int> numbers = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; // The query variable can also be implicitly typed by using var IEnumerable<int> filteringQuery = from num in numbers where num < 3 || num > 7 select num; // Query #2. IEnumerable<int> orderingQuery = from num in numbers where num < 3 || num > 7 orderby num ascending select num; // Query #3. string[] groupingQuery = { "carrots", "cabbage", "broccoli", "beans", "barley" }; IEnumerable<IGrouping<char, string>> queryFoodGroups = from item in groupingQuery group item by item[0];
메서드 구문
1. 일부 쿼리 작업은 메서드 호출로 표시되어야 합니다.
3. 가장 일반적인 이러한 메서드는 Sum, Max, Min, Average 등과 같은 singleton 숫자 값을 반환하는 메서드입니다.
4. 이러한 메서드는 단일 값만 나타내며 추가 쿼리 작업에 대해 소스로 사용할 수 없으므로 항상 모든 쿼리에서 마지막으로 호출되어야 합니다.
5. 다음 예제에서는 쿼리 식에서 메서드를 호출하는 방법을 보여 줍니다.
List<int> numbers1 = new List<int>() { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 }; List<int> numbers2 = new List<int>() { 15, 14, 11, 13, 19, 18, 16, 17, 12, 10 }; // Query #4. double average = numbers1.Average(); // Query #5. IEnumerable<int> concatenationQuery = numbers1.Concat(numbers2);
6. 메서드에 매개 변수가 있으면 매개 변수가 다음 예제와 같이 람다 식 형식으로 제공됩니다.
// Query #6. IEnumerable<int> largeNumbersQuery = numbers2.Where(c => c > 15);
7. 앞의 쿼리에서 쿼리 #4만 즉시 실행됩니다.
8. 이는 쿼리가 단일 값을 반환하고 제네릭 IEnumerable<T> 컬렉션이 아니기 때문입니다.
9. 이 메서드 자체는 값을 계산하기 위해 foreach를 사용해야 합니다.
10. 앞의 각 쿼리는 다음 예제와 같이 var를 통한 암시적 형식 지정을 사용하여 작성할 수 있습니다.
// var is used for convenience in these queries var average = numbers1.Average(); var concatenationQuery = numbers1.Concat(numbers2); var largeNumbersQuery = numbers2.Where(c => c > 15);
혼합 쿼리와 메서드 구문
1. 이 예제에서는 쿼리 절의 결과로 메서드 구문을 사용하는 방법을 보여 줍니다.
2. 쿼리 식을 괄호로 묶은 다음 도트 연산자를 적용하고 메서드를 호출합니다.
3. 다음 예제에서는 쿼리 #7에서 값이 3과 7 사이인 숫자의 수를 반환합니다.
4. 그러나 일반적으로 두 번째 변수를 사용하여 메서드 호출의 결과를 저장하는 것이 좋습니다.
5. 이 경우 쿼리가 쿼리 결과와 혼동될 가능성은 낮습니다.
// Query #7. // Using a query expression with method syntax int numCount1 = (from num in numbers1 where num < 3 || num > 7 select num).Count(); // Better: Create a new variable to store // the method call result IEnumerable<int> numbersQuery = from num in numbers1 where num < 3 || num > 7 select num; int numCount2 = numbersQuery.Count();
6. 쿼리 #7에서는 컬렉션이 아닌 단일 값을 반환하기 때문에 쿼리가 즉시 실행됩니다.
7. 앞의 쿼리를 다음과 같이 var를 통한 암시적 형식 지정을 사용하여 작성할 수 있습니다.
var numCount = (from num in numbers...
8. 해당 쿼리는 다음과 같이 메서드 구문으로 작성될 수 있습니다.
var numCount = numbers.Where(n => n < 3 || n > 7).Count();
9. 해당 쿼리는 다음과 같이 명시적 형식 지정을 사용하여 작성할 수 있습니다.
int numCount = numbers.Where(n => n < 3 || n > 7).Count();
방법: 쿼리 식의 예외 처리
1. 쿼리 식의 컨텍스트에서 메서드를 호출할 수 있습니다.
2. 그러나 데이터 소스의 내용 변경 및 예외 throw 등 의도하지 않은 결과가 발생할 수 있는 쿼리 식에서는 메서드를 호출하지 않는 것이 좋습니다.
3. 이 예제에서는 예외 처리에 대한 일반적인 .NET Framework 지침을 위반하지 않고 쿼리 식에서 메서드를 호출할 때 예외가 발생하지 않도록 지정하는 방법을 보여 줍니다.
4. 이러한 지침에서는 해당 컨텍스트에서 특정 예외가 throw되는 이유를 알고 있는 경우 해당 예외를 catch할 수 있다고 설명합니다.
5. 자세한 내용은 최선의 예외 구현 방법를 참조하십시오.
6.마지막 예제에서는 쿼리 실행 중에 예외를 throw해야 하는 경우를 처리하는 방법을 보여 줍니다.
예제
1. 다음 예제에서는 예외 처리 코드를 쿼리 식 외부로 이동하는 방법을 보여 줍니다.
2. 이러한 작업은 메서드가 쿼리에 대한 지역 변수에 종속되지 않는 경우에만 가능합니다.
class ExceptionsOutsideQuery { static void Main() { // DO THIS with a datasource that might // throw an exception. It is easier to deal with // outside of the query expression. IEnumerable<int> dataSource; try { dataSource = GetData(); } catch (InvalidOperationException) { // Handle (or don't handle) the exception // in the way that is appropriate for your application. Console.WriteLine("Invalid operation"); goto Exit; } // If we get here, it is safe to proceed. var query = from i in dataSource select i * i; foreach (var i in query) Console.WriteLine(i.ToString()); //Keep the console window open in debug mode Exit: Console.WriteLine("Press any key to exit"); Console.ReadKey(); } // A data source that is very likely to throw an exception! static IEnumerable<int> GetData() { throw new InvalidOperationException(); } }
예제
1. 경우에 따라 쿼리 내에서 throw되는 예외에 대한 가장 좋은 응답은 쿼리 실행을 즉시 중지하는 것입니다.
2. 다음 예제에서는 쿼리 본문 내부에서 throw되는 예외를 처리하는 방법을 보여 줍니다.
3. SomeMethodThatMightThrow에서 쿼리 실행을 중지하는 예외를 발생시킬 수 있다고 가정합니다.
4. try 블록에서는 쿼리 자체가 아니라 foreach 루프를 포함합니다.
5. 이는 쿼리가 실제로 실행되는 지점에 foreach 루프가 있기 때문입니다.
6. 자세한 내용은 Introduction to LINQ Queries (C#)를 참조하십시오.
class QueryThatThrows { static void Main() { // Data source. string[] files = { "fileA.txt", "fileB.txt", "fileC.txt" }; // Demonstration query that throws. var exceptionDemoQuery = from file in files let n = SomeMethodThatMightThrow(file) select n; // Runtime exceptions are thrown when query is executed. // Therefore they must be handled in the foreach loop. try { foreach (var item in exceptionDemoQuery) { Console.WriteLine("Processing {0}", item); } } // Catch whatever exception you expect to raise // and/or do any necessary cleanup in a finally block catch (InvalidOperationException e) { Console.WriteLine(e.Message); } //Keep the console window open in debug mode Console.WriteLine("Press any key to exit"); Console.ReadKey(); } // Not very useful as a general purpose method. static string SomeMethodThatMightThrow(string s) { if (s[4] == 'C') throw new InvalidOperationException(); return @"C:\newFolder\" + s; } } /* Output: Processing C:\newFolder\fileA.txt Processing C:\newFolder\fileB.txt Operation is not valid due to the current state of the object. */
How to: Populate Object Collections from Multiple Sources (LINQ)
1. 이 예제에서는 서로 다른 소스의 데이터를 새 형시그이 시퀀스로 병합하는 방법을 보여줍니다.
class Student { public string FirstName { get; set; } public string LastName { get; set; } public int ID { get; set; } public List<int> ExamScores { get; set; } } class PopulateCollection { static void Main() { // These data files are defined in How to: Join Content from // Dissimilar Files (LINQ). // Each line of names.csv consists of a last name, a first name, and an // ID number, separated by commas. For example, Omelchenko,Svetlana,111 string[] names = System.IO.File.ReadAllLines(@"../../../names.csv"); // Each line of scores.csv consists of an ID number and four test // scores, separated by commas. For example, 111, 97, 92, 81, 60 string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv"); // Merge the data sources using a named type. // var could be used instead of an explicit type. Note the dynamic // creation of a list of ints for the ExamScores member. We skip // the first item in the split string because it is the student ID, // not an exam score. IEnumerable<Student> queryNamesScores = from nameLine in names let splitName = nameLine.Split(',') from scoreLine in scores let splitScoreLine = scoreLine.Split(',') where splitName[2] == splitScoreLine[0] select new Student() { FirstName = splitName[0], LastName = splitName[1], ID = Convert.ToInt32(splitName[2]), ExamScores = (from scoreAsText in splitScoreLine.Skip(1) select Convert.ToInt32(scoreAsText)). ToList() }; // Optional. Store the newly created student objects in memory // for faster access in future queries. This could be useful with // very large data files. List<Student> students = queryNamesScores.ToList(); // Display each student's name and exam score average. foreach (var student in students) { Console.WriteLine("The average score of {0} {1} is {2}.", student.FirstName, student.LastName, student.ExamScores.Average()); } //Keep console window open in debug mode Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } /* Output: The average score of Omelchenko Svetlana is 82.5. The average score of O'Donnell Claire is 72.25. The average score of Mortensen Sven is 84.5. The average score of Garcia Cesar is 88.25. The average score of Garcia Debra is 67. The average score of Fakhouri Fadi is 92.25. The average score of Feng Hanying is 88. The average score of Garcia Hugo is 85.75. The average score of Tucker Lance is 81.75. The average score of Adams Terry is 85.25. The average score of Zabokritski Eugene is 83. The average score of Tucker Michael is 92. */
2. select 절에서 개체 이니셜라이저는 두 소스의 데이터를 사용하여 새 Student 개체를 각각 인스턴스화하는 데 사용됩니다.
3. 쿼리 결과를 저장할 필요가 없는 경우 익명 형식이 명명된 형식보다 더 편리할 수 있습니다.
4. 쿼리가 실행되는 메서드 외부에 쿼리 결과를 전달하는 경우에는 명명된 형식이 필요합니다.
5. 다음 예제에서는 명명된 형식 대신 익명 형식을 사용하여 위의 예제와 동일한 작업을 수행합니다.
// Merge the data sources by using an anonymous type. // Note the dynamic creation of a list of ints for the // ExamScores member. We skip 1 because the first string // in the array is the student ID, not an exam score. var queryNamesScores2 = from nameLine in names let splitName = nameLine.Split(',') from scoreLine in scores let splitScoreLine = scoreLine.Split(',') where splitName[2] == splitScoreLine[0] select new { First = splitName[0], Last = splitName[1], ExamScores = (from scoreAsText in splitScoreLine.Skip(1) select Convert.ToInt32(scoreAsText)) .ToList() }; // Display each student's name and exam score average. foreach (var student in queryNamesScores2) { Console.WriteLine("The average score of {0} {1} is {2}.", student.First, student.Last, student.ExamScores.Average()); }
방법: 쿼리 결과 그룹화
1. 그룹화는 LINQ의 가장 강력한 기능 중 하나입니다.
2. 다음 예제에서는 다양한 방식으로 데이터를 그룹화하는 방법을 보여 줍니다.
1) 단일 속성 사용
2) 문자열 속성의 첫 글자 사용
3) 계산된 숫자 범위 사용
4) 부울 조건자 또는 기타 식 사용
5) 복합 키 사용
3. 또한 마지막 두 개의 쿼리는 학생의 이름과 성만 포함한 새 익명 형식으로 해당 결과를 반환합니다.
4. 자세한 내용은 group 절(C# 참조)을 참조하십시오.
예제
1. 이 항목의 모든 예제에서는 다음 도우미 클래스와 데이터 소스가 사용됩니다.
public class StudentClass { #region data protected enum GradeLevel { FirstYear = 1, SecondYear, ThirdYear, FourthYear }; protected class Student { public string FirstName { get; set; } public string LastName { get; set; } public int ID { get; set; } public GradeLevel Year; public List<int> ExamScores; } protected static List<Student> students = new List<Student> { new Student {FirstName = "Terry", LastName = "Adams", ID = 120, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 99, 82, 81, 79}}, new Student {FirstName = "Fadi", LastName = "Fakhouri", ID = 116, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 99, 86, 90, 94}}, new Student {FirstName = "Hanying", LastName = "Feng", ID = 117, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 93, 92, 80, 87}}, new Student {FirstName = "Cesar", LastName = "Garcia", ID = 114, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 97, 89, 85, 82}}, new Student {FirstName = "Debra", LastName = "Garcia", ID = 115, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 35, 72, 91, 70}}, new Student {FirstName = "Hugo", LastName = "Garcia", ID = 118, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 92, 90, 83, 78}}, new Student {FirstName = "Sven", LastName = "Mortensen", ID = 113, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 88, 94, 65, 91}}, new Student {FirstName = "Claire", LastName = "O'Donnell", ID = 112, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 75, 84, 91, 39}}, new Student {FirstName = "Svetlana", LastName = "Omelchenko", ID = 111, Year = GradeLevel.SecondYear, ExamScores = new List<int>{ 97, 92, 81, 60}}, new Student {FirstName = "Lance", LastName = "Tucker", ID = 119, Year = GradeLevel.ThirdYear, ExamScores = new List<int>{ 68, 79, 88, 92}}, new Student {FirstName = "Michael", LastName = "Tucker", ID = 122, Year = GradeLevel.FirstYear, ExamScores = new List<int>{ 94, 92, 91, 91}}, new Student {FirstName = "Eugene", LastName = "Zabokritski", ID = 121, Year = GradeLevel.FourthYear, ExamScores = new List<int>{ 96, 85, 91, 60}} }; #endregion //Helper method, used in GroupByRange. protected static int GetPercentile(Student s) { double avg = s.ExamScores.Average(); return avg > 0 ? (int)avg / 10 : 0; } public void QueryHighScores(int exam, int score) { var highScores = from student in students where student.ExamScores[exam] > score select new {Name = student.FirstName, Score = student.ExamScores[exam]}; foreach (var item in highScores) { Console.WriteLine("{0,-15}{1}", item.Name, item.Score); } } } public class Program { public static void Main() { StudentClass sc = new StudentClass(); sc.QueryHighScores(1, 90); // Keep the console window open in debug mode. Console.WriteLine("Press any key to exit"); Console.ReadKey(); } }
예제
1. 다음 예제에서는 요소의 단일 속성을 그룹 키로 사용하여 소스 요소를 그룹화하는 방법을 보여 줍니다.
2. 이 경우 키는 학생의 성 string입니다.
3. 또한 부분 문자열을 키에 사용할 수도 있습니다.
4. 그룹화 작업은 형식에 대해 기본 같음 비교자를 사용합니다.
5. 다음 메서드를 StudentClass 클래스에 붙여 넣습니다.
6. Main 메서드의 호출 문을 sc.GroupBySingleProperty()로 변경합니다.
public void GroupBySingleProperty() { Console.WriteLine("Group by a single property in an object:"); // Variable queryLastNames is an IEnumerable<IGrouping<string, // DataClass.Student>>. var queryLastNames = from student in students group student by student.LastName into newGroup orderby newGroup.Key select newGroup; foreach (var nameGroup in queryLastNames) { Console.WriteLine("Key: {0}", nameGroup.Key); foreach (var student in nameGroup) { Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName); } } } /* Output: Group by a single property in an object: Key: Adams Adams, Terry Key: Fakhouri Fakhouri, Fadi Key: Feng Feng, Hanying Key: Garcia Garcia, Cesar Garcia, Debra Garcia, Hugo Key: Mortensen Mortensen, Sven Key: O'Donnell O'Donnell, Claire Key: Omelchenko Omelchenko, Svetlana Key: Tucker Tucker, Lance Tucker, Michael Key: Zabokritski Zabokritski, Eugene */
예제
1. 다음 예제에서는 개체의 속성이 아닌 항목을 그룹 키에 사용하여 소스 요소를 그룹화하는 방법을 보여 줍니다.
2. 이 예제에서 키는 학생의 성 중에서 첫 번째 문자입니다.
3. 다음 메서드를 StudentClass 클래스에 붙여 넣습니다.
4. Main 메서드의 호출 문을 sc.GroupBySubstring()로 변경합니다.
public void GroupBySubstring() { Console.WriteLine("\r\nGroup by something other than a property of the object:"); var queryFirstLetters = from student in students group student by student.LastName[0]; foreach (var studentGroup in queryFirstLetters) { Console.WriteLine("Key: {0}", studentGroup.Key); // Nested foreach is required to access group items. foreach (var student in studentGroup) { Console.WriteLine("\t{0}, {1}", student.LastName, student.FirstName); } } } /* Output: Group by something other than a property of the object: Key: A Adams, Terry Key: F Fakhouri, Fadi Feng, Hanying Key: G Garcia, Cesar Garcia, Debra Garcia, Hugo Key: M Mortensen, Sven Key: O O'Donnell, Claire Omelchenko, Svetlana Key: T Tucker, Lance Tucker, Michael Key: Z Zabokritski, Eugene */
예제
1. 다음 예제에서는 부울 비교 식을 사용하여 소스 요소를 그룹화하는 방법을 보여 줍니다.
2. 이 예제에서 부울 식은 학생의 평균 시험 점수가 75점을 넘는지 확인합니다.
3. 위의 예제와 같이 완전한 소스 요소가 필요하지 않으므로 결과는 익명 형식으로 반환됩니다.
4. 익명 형식의 속성은 Key 멤버의 속성이 되고 쿼리 실행 시에 이름으로 액세스할 수 있습니다.
5. 다음 메서드를 StudentClass 클래스에 붙여 넣습니다.
6. Main 메서드의 호출 문을 sc.GroupByBoolean()로 변경합니다.
public void GroupByBoolean() { Console.WriteLine("\r\nGroup by a Boolean into two groups with string keys"); Console.WriteLine("\"True\" and \"False\" and project into a new anonymous type:"); var queryGroupByAverages = from student in students group new { student.FirstName, student.LastName } by student.ExamScores.Average() > 75 into studentGroup select studentGroup; foreach (var studentGroup in queryGroupByAverages) { Console.WriteLine("Key: {0}", studentGroup.Key); foreach (var student in studentGroup) Console.WriteLine("\t{0} {1}", student.FirstName, student.LastName); } } /* Output: Group by a Boolean into two groups with string keys "True" and "False" and project into a new anonymous type: Key: True Terry Adams Fadi Fakhouri Hanying Feng Cesar Garcia Hugo Garcia Sven Mortensen Svetlana Omelchenko Lance Tucker Michael Tucker Eugene Zabokritski Key: False Debra Garcia Claire O'Donnell */
예제
1. 다음 예제에서는 익명 형식을 사용하여 여러 값이 포함된 키를 캡슐화하는 방법을 보여 줍니다.
2. 이 예제에서 첫 번째 키 값은 학생의 성 중에서 첫 번째 문자입니다.
3. 두 번째 키 값은 학생이 첫 번째 시험에서 85점이 넘는 점수를 받았는지 여부를 지정하는 부울입니다.
4. 키의 임의 속성으로 그룹을 정렬할 수 있습니다.
5. 다음 메서드를 StudentClass 클래스에 붙여 넣습니다.
6. Main 메서드의 호출 문을 sc.GroupByCompositeKey()로 변경합니다.
public void GroupByCompositeKey() { var queryHighScoreGroups = from student in students group student by new { FirstLetter = student.LastName[0], Score = student.ExamScores[0] > 85 } into studentGroup orderby studentGroup.Key.FirstLetter select studentGroup; Console.WriteLine("\r\nGroup and order by a compound key:"); foreach (var scoreGroup in queryHighScoreGroups) { string s = scoreGroup.Key.Score == true ? "more than" : "less than"; Console.WriteLine("Name starts with {0} who scored {1} 85", scoreGroup.Key.FirstLetter, s); foreach (var item in scoreGroup) { Console.WriteLine("\t{0} {1}", item.FirstName, item.LastName); } } } /* Output: Group and order by a compound key: Name starts with A who scored more than 85 Terry Adams Name starts with F who scored more than 85 Fadi Fakhouri Hanying Feng Name starts with G who scored more than 85 Cesar Garcia Hugo Garcia Name starts with G who scored less than 85 Debra Garcia Name starts with M who scored more than 85 Sven Mortensen Name starts with O who scored less than 85 Claire O'Donnell Name starts with O who scored more than 85 Svetlana Omelchenko Name starts with T who scored less than 85 Lance Tucker Name starts with T who scored more than 85 Michael Tucker Name starts with Z who scored more than 85 Eugene Zabokritski */
방법: 중첩 그룹 만들기
예제
public void QueryNestedGroups() { var queryNestedGroups = from student in students group student by student.Year into newGroup1 from newGroup2 in (from student in newGroup1 group student by student.LastName) group newGroup2 by newGroup1.Key; // Three nested foreach loops are required to iterate // over all elements of a grouped group. Hover the mouse // cursor over the iteration variables to see their actual type. foreach (var outerGroup in queryNestedGroups) { Console.WriteLine("DataClass.Student Level = {0}", outerGroup.Key); foreach (var innerGroup in outerGroup) { Console.WriteLine("\tNames that begin with: {0}", innerGroup.Key); foreach (var innerGroupElement in innerGroup) { Console.WriteLine("\t\t{0} {1}", innerGroupElement.LastName, innerGroupElement.FirstName); } } } } /* Output: DataClass.Student Level = SecondYear Names that begin with: Adams Adams Terry Names that begin with: Garcia Garcia Hugo Names that begin with: Omelchenko Omelchenko Svetlana DataClass.Student Level = ThirdYear Names that begin with: Fakhouri Fakhouri Fadi Names that begin with: Garcia Garcia Debra Names that begin with: Tucker Tucker Lance DataClass.Student Level = FirstYear Names that begin with: Feng Feng Hanying Names that begin with: Mortensen Mortensen Sven Names that begin with: Tucker Tucker Michael DataClass.Student Level = FourthYear Names that begin with: Garcia Garcia Cesar Names that begin with: O'Donnell O'Donnell Claire Names that begin with: Zabokritski Zabokritski Eugene */
방법: 그룹화 작업에서 하위 쿼리 수행
1. 항목에서는 소스 데이터를 그룹으로 순서 정렬하는 쿼리를 만들어 각 그룹에 대한 하위 쿼리를 개별적으로 수행하는 두 가지 방법에 대해 설명합니다.
2. 각 예제에서 기본적인 방법은 newGroup이라는 연속 문자를 사용하여 newGroup에 대한 새로운 하위 쿼리를 생성하여 소스 개체를 그룹화하는 것입니다.
3. 이 하위 쿼리는 외부 쿼리를 사용하여 만든 각각의 새로운 그룹에 대해 실행됩니다.
4. 이 특정 예제에서 마지막 출력은 그룹이 아니라 익명 형식의 기본 시퀀스입니다.
5. 그룹화하는 방법에 대한 자세한 내용은 group 절(C# 참조)을 참조하십시오.
6. 연속에 대한 자세한 내용은 into(C# 참조)를 참조하십시오.
7. 다음 예제에서는 메모리 내 데이터 구조를 데이터 소스로 사용하지만 모든 종류의 LINQ 데이터 소스에 적용되는 동일한 원칙을 사용합니다.
public void QueryMax() { var queryGroupMax = from student in students group student by student.Year into studentGroup select new { Level = studentGroup.Key, HighestScore = (from student2 in studentGroup select student2.ExamScores.Average()).Max() }; int count = queryGroupMax.Count(); Console.WriteLine("Number of groups = {0}", count); foreach (var item in queryGroupMax) { Console.WriteLine(" {0} Highest Score={1}", item.Level, item.HighestScore); } }
방법: 연속 키를 기준으로 결과 그룹화
예제
using System; using System.Collections.Generic; using System.Linq; namespace ChunkIt { // Static class to contain the extension methods. public static class MyExtensions { public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector) { return source.ChunkBy(keySelector, EqualityComparer<TKey>.Default); } public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer) { // Flag to signal end of source sequence. const bool noMoreSourceElements = true; // Auto-generated iterator for the source array. var enumerator = source.GetEnumerator(); // Move to the first element in the source sequence. if (!enumerator.MoveNext()) yield break; // Iterate through source sequence and create a copy of each Chunk. // On each pass, the iterator advances to the first element of the next "Chunk" // in the source sequence. This loop corresponds to the outer foreach loop that // executes the query. Chunk<TKey, TSource> current = null; while (true) { // Get the key for the current Chunk. The source iterator will churn through // the source sequence until it finds an element with a key that doesn't match. var key = keySelector(enumerator.Current); // Make a new Chunk (group) object that initially has one GroupItem, which is a copy of the current source element. current = new Chunk<TKey, TSource>(key, enumerator, value => comparer.Equals(key, keySelector(value))); // Return the Chunk. A Chunk is an IGrouping<TKey,TSource>, which is the return value of the ChunkBy method. // At this point the Chunk only has the first element in its source sequence. The remaining elements will be // returned only when the client code foreach's over this chunk. See Chunk.GetEnumerator for more info. yield return current; // Check to see whether (a) the chunk has made a copy of all its source elements or // (b) the iterator has reached the end of the source sequence. If the caller uses an inner // foreach loop to iterate the chunk items, and that loop ran to completion, // then the Chunk.GetEnumerator method will already have made // copies of all chunk items before we get here. If the Chunk.GetEnumerator loop did not // enumerate all elements in the chunk, we need to do it here to avoid corrupting the iterator // for clients that may be calling us on a separate thread. if (current.CopyAllChunkElements() == noMoreSourceElements) { yield break; } } } // A Chunk is a contiguous group of one or more source elements that have the same key. A Chunk // has a key and a list of ChunkItem objects, which are copies of the elements in the source sequence. class Chunk<TKey, TSource> : IGrouping<TKey, TSource> { // INVARIANT: DoneCopyingChunk == true || // (predicate != null && predicate(enumerator.Current) && current.Value == enumerator.Current) // A Chunk has a linked list of ChunkItems, which represent the elements in the current chunk. Each ChunkItem // has a reference to the next ChunkItem in the list. class ChunkItem { public ChunkItem(TSource value) { Value = value; } public readonly TSource Value; public ChunkItem Next = null; } // The value that is used to determine matching elements private readonly TKey key; // Stores a reference to the enumerator for the source sequence private IEnumerator<TSource> enumerator; // A reference to the predicate that is used to compare keys. private Func<TSource, bool> predicate; // Stores the contents of the first source element that // belongs with this chunk. private readonly ChunkItem head; // End of the list. It is repositioned each time a new // ChunkItem is added. private ChunkItem tail; // Flag to indicate the source iterator has reached the end of the source sequence. internal bool isLastSourceElement = false; // Private object for thread syncronization private object m_Lock; // REQUIRES: enumerator != null && predicate != null public Chunk(TKey key, IEnumerator<TSource> enumerator, Func<TSource, bool> predicate) { this.key = key; this.enumerator = enumerator; this.predicate = predicate; // A Chunk always contains at least one element. head = new ChunkItem(enumerator.Current); // The end and beginning are the same until the list contains > 1 elements. tail = head; m_Lock = new object(); } // Indicates that all chunk elements have been copied to the list of ChunkItems, // and the source enumerator is either at the end, or else on an element with a new key. // the tail of the linked list is set to null in the CopyNextChunkElement method if the // key of the next element does not match the current chunk's key, or there are no more elements in the source. private bool DoneCopyingChunk { get { return tail == null; } } // Adds one ChunkItem to the current group // REQUIRES: !DoneCopyingChunk && lock(this) private void CopyNextChunkElement() { // Try to advance the iterator on the source sequence. // If MoveNext returns false we are at the end, and isLastSourceElement is set to true isLastSourceElement = !enumerator.MoveNext(); // If we are (a) at the end of the source, or (b) at the end of the current chunk // then null out the enumerator and predicate for reuse with the next chunk. if (isLastSourceElement || !predicate(enumerator.Current)) { enumerator = null; predicate = null; } else { tail.Next = new ChunkItem(enumerator.Current); } // tail will be null if we are at the end of the chunk elements // This check is made in DoneCopyingChunk. tail = tail.Next; } // Called after the end of the last chunk was reached. It first checks whether // there are more elements in the source sequence. If there are, it // Returns true if enumerator for this chunk was exhausted. internal bool CopyAllChunkElements() { while (true) { lock (m_Lock) { if (DoneCopyingChunk) { // If isLastSourceElement is false, // it signals to the outer iterator // to continue iterating. return isLastSourceElement; } else { CopyNextChunkElement(); } } } } public TKey Key { get { return key; } } // Invoked by the inner foreach loop. This method stays just one step ahead // of the client requests. It adds the next element of the chunk only after // the clients requests the last element in the list so far. public IEnumerator<TSource> GetEnumerator() { //Specify the initial element to enumerate. ChunkItem current = head; // There should always be at least one ChunkItem in a Chunk. while (current != null) { // Yield the current item in the list. yield return current.Value; // Copy the next item from the source sequence, // if we are at the end of our local list. lock (m_Lock) { if (current == tail) { CopyNextChunkElement(); } } // Move to the next ChunkItem in the list. current = current.Next; } } System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() { return GetEnumerator(); } } } // A simple named type is used for easier viewing in the debugger. Anonymous types // work just as well with the ChunkBy operator. public class KeyValPair { public string Key { get; set; } public string Value { get; set; } } class Program { // The source sequence. public static IEnumerable<KeyValPair> list; // Query variable declared as class member to be available // on different threads. static IEnumerable<IGrouping<string, KeyValPair>> query; static void Main(string[] args) { // Initialize the source sequence with an array initializer. list = new[] { new KeyValPair{ Key = "A", Value = "We" }, new KeyValPair{ Key = "A", Value = "Think" }, new KeyValPair{ Key = "A", Value = "That" }, new KeyValPair{ Key = "B", Value = "Linq" }, new KeyValPair{ Key = "C", Value = "Is" }, new KeyValPair{ Key = "A", Value = "Really" }, new KeyValPair{ Key = "B", Value = "Cool" }, new KeyValPair{ Key = "B", Value = "!" } }; // Create the query by using our user-defined query operator. query = list.ChunkBy(p => p.Key); // ChunkBy returns IGrouping objects, therefore a nested // foreach loop is required to access the elements in each "chunk". foreach (var item in query) { Console.WriteLine("Group key = {0}", item.Key); foreach (var inner in item) { Console.WriteLine("\t{0}", inner.Value); } } Console.WriteLine("Press any key to exit"); Console.ReadKey(); } } }
방법: 런타임에 동적으로 조건자 필터 지정
1. 때로는 런타임 전에 where 절에서 소스 요소에 적용해야 하는 조건자 수를 알 수 없습니다.
2. 다음 예제에서 볼 수 있듯이 동적으로 여러 개의 조건자 필터를 지정하는 한 가지 방법은 Contains<TSource> 메서드를 사용하는 것입니다.
class DynamicPredicates : StudentClass { static void Main(string[] args) { string[] ids = { "111", "114", "112" }; Console.WriteLine("Press any key to exit."); Console.ReadKey(); } static void QueryByID(string[] ids) { var queryNames = from student in students let i = student.ID.ToString() where ids.Contains(i) select new { student.LastName, student.ID }; foreach (var name in queryNames) { Console.WriteLine("{0}: {1}", name.LastName, name.ID); } } }
QueryById(ids);
switch 문을 사용하여 필터링하려면
1. switch 문을 사용하면 선택적이고 미리 지정되어 있는 여러 쿼리 중에서 원하는 쿼리를 선택할 수 있습니다.
2. 다음 예제에서 studentQuery는 런타임에 등급이 지정되었는지 연도가 지정되는지에 따라 다른 where 절을 사용합니다.
3. 다음 메서드를 복사하여 DynamicPredicates 클래스에 붙여 넣습니다.
// To run this sample, first specify an integer value of 1 to 4 for the command // line. This number will be converted to a GradeLevel value that specifies which // set of students to query. // Call the method: QueryByYear(args[0]); static void QueryByYear(string level) { GradeLevel year = (GradeLevel)Convert.ToInt32(level); IEnumerable<Student> studentQuery = null; switch (year) { case GradeLevel.FirstYear: studentQuery = from student in students where student.Year == GradeLevel.FirstYear select student; break; case GradeLevel.SecondYear: studentQuery = from student in students where student.Year == GradeLevel.SecondYear select student; break; case GradeLevel.ThirdYear: studentQuery = from student in students where student.Year == GradeLevel.ThirdYear select student; break; case GradeLevel.FourthYear: studentQuery = from student in students where student.Year == GradeLevel.FourthYear select student; break; default: break; } Console.WriteLine("The following students are at level {0}", year.ToString()); foreach (Student name in studentQuery) { Console.WriteLine("{0}: {1}", name.LastName, name.ID); } }
방법: 쿼리 결과를 메모리에 저장
1. 쿼리는 기본적으로 데이터를 검색하고 구성하는 방법에 대한 명령 집합입니다.
2. 쿼리를 실행하려면 해당 GetEnumerator 메서드를 호출해야 합니다.
3. 이 호출은 foreach 루프를 사용하여 요소를 반복하는 경우에 요청됩니다.
4. foreach 루프를 실행하지 않고 쿼리를 평가해서 해당 결과 저장하려면 쿼리 변수에 다음 메서드 중 하나를 호출합니다.
5. 쿼리 결과를 저장하는 경우 다음의 예제와 같이 새 변수에 반환된 컬렉션 개체를 할당하는 것이 좋습니다.
class StoreQueryResults { static List<int> numbers = new List<int>() { 1, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20 }; static void Main() { IEnumerable<int> queryFactorsOfFour = from num in numbers where num % 4 == 0 select num; // Store the results in a new variable // without executing a foreach loop. List<int> factorsofFourList = queryFactorsOfFour.ToList(); // Iterate the list just to prove it holds data. foreach (int n in factorsofFourList) { Console.WriteLine(n); } // Keep the console window open in debug mode. Console.WriteLine("Press any key"); Console.ReadKey(); } }
방법: 메서드에서 쿼리 반환
1. 이 예제에서는 메서드의 쿼리를 반환 값 및 out 매개 변수로 반환하는 방법을 보여 줍니다.
2. 모든 쿼리는 IEnumerable 또는 IEnumerable<T> 형식이거나 IQueryable<T>과 같은 파생 형식이어야 합니다.
3. 따라서 쿼리를 반환하는 메서드의 모든 반환 값 또는 out 매개 변수도 이와 같은 형식이어야 합니다.
4. 메서드에서 쿼리를 List<T> 또는 Array 형식으로 구체화할 경우 해당 메서드는 쿼리 자체가 아니라 쿼리 결과를 반환하는 것으로 간주됩니다.
5. 메서드에서 반환되는 쿼리 변수는 구성 또는 수정이 가능합니다.
예제
class MQ { // QueryMethhod1 returns a query as its value. IEnumerable<string> QueryMethod1(ref int[] ints) { var intsToStrings = from i in ints where i > 4 select i.ToString(); return intsToStrings; } // QueryMethod2 returns a query as the value of parameter returnQ. void QueryMethod2(ref int[] ints, out IEnumerable<string> returnQ) { var intsToStrings = from i in ints where i < 4 select i.ToString(); returnQ = intsToStrings; } static void Main() { MQ app = new MQ(); int[] nums = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; // QueryMethod1 returns a query as the value of the method. var myQuery1 = app.QueryMethod1(ref nums); // Query myQuery1 is executed in the following foreach loop. Console.WriteLine("Results of executing myQuery1:"); // Rest the mouse pointer over myQuery1 to see its type. foreach (string s in myQuery1) { Console.WriteLine(s); } // You also can execute the query returned from QueryMethod1 // directly, without using myQuery1. Console.WriteLine("\nResults of executing myQuery1 directly:"); // Rest the mouse pointer over the call to QueryMethod1 to see its // return type. foreach (string s in app.QueryMethod1(ref nums)) { Console.WriteLine(s); } IEnumerable<string> myQuery2; // QueryMethod2 returns a query as the value of its out parameter. app.QueryMethod2(ref nums, out myQuery2); // Execute the returned query. Console.WriteLine("\nResults of executing myQuery2:"); foreach (string s in myQuery2) { Console.WriteLine(s); } // You can modify a query by using query composition. A saved query // is nested inside a new query definition that revises the results // of the first query. myQuery1 = from item in myQuery1 orderby item descending select item; // Execute the modified query. Console.WriteLine("\nResults of executing modified myQuery1:"); foreach (string s in myQuery1) { Console.WriteLine(s); } // Keep console window open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } }
방법: 사용자 지정 조인 작업 수행
1. 이 예제에서는 join 절에서 사용할 수 없는 조인 작업을 수행하는 방법을 보여 줍니다.
2. 쿼리 식에서 join 절은 가장 일반적인 조인 작업 형식인 동등 조인으로 제한되며 이에 맞게 최적화되어 있습니다.
3. 동등 조인을 수행하는 경우 항상 join 절을 사용하여 최상의 성능을 얻을 수 있습니다.
1) 하지만 다음과 같은 경우에는 join 절을 사용할 수 없습니다.
2) 다름(비동등) 식에서 조인을 서술하는 경우
3) 둘 이상의 동등 또는 비동등 식에서 조인을 서술하는 경우
4.. 조인 작업 전에 오른쪽(내부) 시퀀스에 대해 임시 범위 변수를 적용해야 하는 경우
5. 동등 조인이 아닌 조인을 수행하려면 여러 개의 from 절을 사용하여 각 데이터 소스를 개별적으로 적용합니다.
6. 그런 다음 where 절의 조건자 식을 각 소스의 범위 변수에 적용합니다. 식이 메서드 호출 형태를 사용할 수도 있습니다.
예제
class CustomJoins { #region Data class Product { public string Name { get; set; } public int CategoryID { get; set; } } class Category { public string Name { get; set; } public int ID { get; set; } } // Specify the first data source. List<Category> categories = new List<Category>() { new Category(){Name="Beverages", ID=001}, new Category(){ Name="Condiments", ID=002}, new Category(){ Name="Vegetables", ID=003}, }; // Specify the second data source. List<Product> products = new List<Product>() { new Product{Name="Tea", CategoryID=001}, new Product{Name="Mustard", CategoryID=002}, new Product{Name="Pickles", CategoryID=002}, new Product{Name="Carrots", CategoryID=003}, new Product{Name="Bok Choy", CategoryID=003}, new Product{Name="Peaches", CategoryID=005}, new Product{Name="Melons", CategoryID=005}, new Product{Name="Ice Cream", CategoryID=007}, new Product{Name="Mackerel", CategoryID=012}, }; #endregion static void Main() { CustomJoins app = new CustomJoins(); app.CrossJoin(); app.NonEquijoin(); Console.WriteLine("Press any key to exit."); Console.ReadKey(); } void CrossJoin() { var crossJoinQuery = from c in categories from p in products select new { c.ID, p.Name }; Console.WriteLine("Cross Join Query:"); foreach (var v in crossJoinQuery) { Console.WriteLine("{0,-5}{1}", v.ID, v.Name); } } void NonEquijoin() { var nonEquijoinQuery = from p in products let catIds = from c in categories select c.ID where catIds.Contains(p.CategoryID) == true select new { Product = p.Name, CategoryID = p.CategoryID }; Console.WriteLine("Non-equijoin query:"); foreach (var v in nonEquijoinQuery) { Console.WriteLine("{0,-5}{1}", v.CategoryID, v.Product); } } } /* Output: Cross Join Query: 1 Tea 1 Mustard 1 Pickles 1 Carrots 1 Bok Choy 1 Peaches 1 Melons 1 Ice Cream 1 Mackerel 2 Tea 2 Mustard 2 Pickles 2 Carrots 2 Bok Choy 2 Peaches 2 Melons 2 Ice Cream 2 Mackerel 3 Tea 3 Mustard 3 Pickles 3 Carrots 3 Bok Choy 3 Peaches 3 Melons 3 Ice Cream 3 Mackerel Non-equijoin query: 1 Tea 2 Mustard 2 Pickles 3 Carrots 3 Bok Choy Press any key to exit. */
예제
1. 다음 예제에서는 쿼리가 내부(오른쪽) 시퀀스의 경우 조인 절 자체 전에 가져올 수 없는 일치하는 키를 기반으로 두 개의 시퀀스를 조인해야 합니다.
2. join 절을 사용하여 이 조인을 수행하면 각 요소에 대해 Split 메서드가 호출되어야 합니다.
3. 여러 개의 from 절을 사용하면 쿼리가 반복 메서드 호출의 오버헤드를 방지할 수 있습니다.
4. 그러나 join이 최적화되므로 이 특별한 경우에서는 여러 개의 from 절을 사용하는 것보다 속도가 더 빠를 수 있습니다.
5. 결과는 주로 메서드 호출의 비용에 따라 달라집니다.
class MergeTwoCSVFiles { static void Main() { // See section Compiling the Code for information about the data files. string[] names = System.IO.File.ReadAllLines(@"../../../names.csv"); string[] scores = System.IO.File.ReadAllLines(@"../../../scores.csv"); // Merge the data sources using a named type. // You could use var instead of an explicit type for the query. IEnumerable<Student> queryNamesScores = // Split each line in the data files into an array of strings. from name in names let x = name.Split(',') from score in scores let s = score.Split(',') // Look for matching IDs from the two data files. where x[2] == s[0] // If the IDs match, build a Student object. select new Student() { FirstName = x[0], LastName = x[1], ID = Convert.ToInt32(x[2]), ExamScores = (from scoreAsText in s.Skip(1) select Convert.ToInt32(scoreAsText)). ToList() }; // Optional. Store the newly created student objects in memory // for faster access in future queries List<Student> students = queryNamesScores.ToList(); foreach (var student in students) { Console.WriteLine("The average score of {0} {1} is {2}.", student.FirstName, student.LastName, student.ExamScores.Average()); } //Keep console window open in debug mode Console.WriteLine("Press any key to exit."); Console.ReadKey(); } } class Student { public string FirstName { get; set; } public string LastName { get; set; } public int ID { get; set; } public List<int> ExamScores { get; set; } } /* Output: The average score of Omelchenko Svetlana is 82.5. The average score of O'Donnell Claire is 72.25. The average score of Mortensen Sven is 84.5. The average score of Garcia Cesar is 88.25. The average score of Garcia Debra is 67. The average score of Fakhouri Fadi is 92.25. The average score of Feng Hanying is 88. The average score of Garcia Hugo is 85.75. The average score of Tucker Lance is 81.75. The average score of Adams Terry is 85.25. The average score of Zabokritski Eugene is 83. The average score of Tucker Michael is 92. */
방법: 복합 키를 사용하여 조인
Join 절 결과의 순서 정렬
1. 이 예제에서는 조인 연산 결과의 순서를 정렬하는 방법을 보여 줍니다.
2. 순서 정렬 작업은 조인 이후에 수행됩니다.
3. 조인하기 전에 하나 이상의 소스 시퀀스에 대해 orderby 절을 사용할 수도 있지만 이 방법은 사용하지 않는 것이 좋습니다.
4. 일부 LINQ 공급자에서는 조인 이후에 이 정렬 순서를 유지하지 않기 때문입니다.
class HowToOrderJoins { #region Data class Product { public string Name { get; set; } public int CategoryID { get; set; } } class Category { public string Name { get; set; } public int ID { get; set; } } // Specify the first data source. List<Category> categories = new List<Category>() { new Category(){Name="Beverages", ID=001}, new Category(){ Name="Condiments", ID=002}, new Category(){ Name="Vegetables", ID=003}, new Category() { Name="Grains", ID=004}, new Category() { Name="Fruit", ID=005} }; // Specify the second data source. List<Product> products = new List<Product>() { new Product{Name="Cola", CategoryID=001}, new Product{Name="Tea", CategoryID=001}, new Product{Name="Mustard", CategoryID=002}, new Product{Name="Pickles", CategoryID=002}, new Product{Name="Carrots", CategoryID=003}, new Product{Name="Bok Choy", CategoryID=003}, new Product{Name="Peaches", CategoryID=005}, new Product{Name="Melons", CategoryID=005}, }; #endregion static void Main() { HowToOrderJoins app = new HowToOrderJoins(); app.OrderJoin1(); // Keep console window open in debug mode. Console.WriteLine("Press any key to exit."); Console.ReadKey(); } void OrderJoin1() { var groupJoinQuery2 = from category in categories join prod in products on category.ID equals prod.CategoryID into prodGroup orderby category.Name select new { Category = category.Name, Products = from prod2 in prodGroup orderby prod2.Name select prod2 }; foreach (var productGroup in groupJoinQuery2) { Console.WriteLine(productGroup.Category); foreach (var prodItem in productGroup.Products) { Console.WriteLine(" {0,-10} {1}", prodItem.Name, prodItem.CategoryID); } } } /* Output: Beverages Cola 1 Tea 1 Condiments Mustard 2 Pickles 2 Fruit Melons 5 Peaches 5 Grains Vegetables Bok Choy 3 Carrots 3 */ }
방법: 내부 조인 수행
1. 관계형 데이터베이스에서 말하는 내부 조인이란 첫 번째 컬렉션의 각 요소가 두 번째 컬렉션에서 일치하는 모든 요소에 대해 한 번만 표시되는 결과 집합을 생성하는 것입니다.
2. 첫 번째 컬렉션의 요소와 일치하는 요소가 없는 경우 해당 요소가 결과 집합에 표시되지 않습니다.
3. C#에서 join 절로 호출되는 Join 메서드는 내부 조인을 구현합니다.
4. 이 항목에서는 다음과 같은 내부 조인의 네 가지 변형을 수행하는 방법을 보여 줍니다.
1) 간단한 키를 기준으로 두 데이터 소스의 요소를 연관시키는 간단한 내부 조인
2) 복합 키를 기준으로 두 데이터 소스의 요소를 연관시키는 내부 조인. 둘 이상의 값으로 구성된 복합 키를 사용하면 둘 이상의 속성을 기준으로 요소를 연관시킬 수 있습니다.
3) 연속된 조인 작업이 서로에게 추가되는 여러 조인
4) 그룹 조인을 사용하여 구현된 내부 조인
간단한 키 조인 예제
1. 다음 예제에서는 두 사용자 정의 형식(Person 및 Pet)의 개체를 포함하는 두 컬렉션을 만듭니다.
2. 이 쿼리에서는 C#의 join 절을 사용하여 Owner가 해당 Person인 Pet 개체와 Person 개체가 일치하는지 확인합니다.
3. C#의 select 절은 결과 개체의 표시 모양을 정의합니다.
4. 이 예제에서 결과 개체는 소유자의 이름과 애완 동물의 이름으로 구성된 익명 형식입니다.
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } /// <summary> /// Simple inner join. /// </summary> public static void InnerJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Person rui = new Person { FirstName = "Rui", LastName = "Raposo" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet bluemoon = new Pet { Name = "Blue Moon", Owner = rui }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; // Create two lists. List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; // Create a collection of person-pet pairs. Each element in the collection // is an anonymous type containing both the person's name and their pet's name. var query = from person in people join pet in pets on person equals pet.Owner select new { OwnerName = person.FirstName, PetName = pet.Name }; foreach (var ownerAndPet in query) { Console.WriteLine("\"{0}\" is owned by {1}", ownerAndPet.PetName, ownerAndPet.OwnerName); } } // This code produces the following output: // // "Daisy" is owned by Magnus // "Barley" is owned by Terry // "Boots" is owned by Terry // "Whiskers" is owned by Charlotte // "Blue Moon" is owned by Rui
복합 키 조인 예제
1. 하나의 속성을 기준으로 요소를 연관시키는 대신에 복합 키를 사용하여 여러 가지 속성을 기준으로 요소를 비교할 수 있습니다.
2. 이를 위해 각 컬렉션에서 사용자가 비교할 속성으로 구성된 익명 형식을 반환하도록 키 선택기 함수를 지정합니다.
3. 속성에 레이블을 지정하는 경우 각 키의 익명 형식에 동일한 레이블을 가져야 합니다.
4. 또한 속성은 동일한 순서로 나타나야 합니다.
5. 다음 예제에서는 Employee 개체의 목록과 Student 개체의 목록을 사용하여 학생이기도 한 직원을 확인할 수 있습니다.
6. 이러한 형식에는 모두 String 형식의 FirstName과 LastName 속성이 있습니다.
7. 각 목록의 요소로부터 조인 키를 만드는 함수는 각 요소의 FirstName 및 LastName 속성으로 구성된 익명 형식을 반환합니다.
8. 조인 작업은 이러한 복합 키가 같은지 비교하여 각 목록에서 이름과 성이 모두 일치하는 개체의 쌍을 반환합니다.
class Employee { public string FirstName { get; set; } public string LastName { get; set; } public int EmployeeID { get; set; } } class Student { public string FirstName { get; set; } public string LastName { get; set; } public int StudentID { get; set; } } /// <summary> /// Performs a join operation using a composite key. /// </summary> public static void CompositeKeyJoinExample() { // Create a list of employees. List<Employee> employees = new List<Employee> { new Employee { FirstName = "Terry", LastName = "Adams", EmployeeID = 522459 }, new Employee { FirstName = "Charlotte", LastName = "Weiss", EmployeeID = 204467 }, new Employee { FirstName = "Magnus", LastName = "Hedland", EmployeeID = 866200 }, new Employee { FirstName = "Vernette", LastName = "Price", EmployeeID = 437139 } }; // Create a list of students. List<Student> students = new List<Student> { new Student { FirstName = "Vernette", LastName = "Price", StudentID = 9562 }, new Student { FirstName = "Terry", LastName = "Earls", StudentID = 9870 }, new Student { FirstName = "Terry", LastName = "Adams", StudentID = 9913 } }; // Join the two data sources based on a composite key consisting of first and last name, // to determine which employees are also students. IEnumerable<string> query = from employee in employees join student in students on new { employee.FirstName, employee.LastName } equals new { student.FirstName, student.LastName } select employee.FirstName + " " + employee.LastName; Console.WriteLine("The following people are both employees and students:"); foreach (string name in query) Console.WriteLine(name); } // This code produces the following output: // // The following people are both employees and students: // Terry Adams // Vernette Price
여러 조인 예제
1. 여러 조인을 수행하기 위해 서로에게 추가될 수 있는 조인 작업의 수에는 제한이 없습니다.
2. C#의 각 join 절은 지정된 데이터 소스와 이전 조인의 결과를 연관시킵니다.
3. 다음 예제에서는 세 개의 컬렉션인 Person 개체 목록, Cat 개체 목록 및 Dog 개체 목록을 만듭니다.
4. C#의 첫 번째 join 절은 Cat.Owner와 일치하는 Person을 기준으로 일치하는 사람과 고양이를 찾습니다.
5. Person 개체와 Cat.Name을 포함하는 익명 형식의 시퀀스를 반환합니다.
6. C#의 두 번째 join 절은 Person 형식의 Owner 속성 및 해당 동물의 이름 첫글자로 구성된 복합 키를 기준으로 첫 번째 조인으로 반환된 익명 형식을 제공된 개 목록의 Dog 개체와 연관시킵니다.
7. 일치하는 각 쌍에서 Cat.Name 및 Dog.Name 속성을 포함하는 익명 형식의 시퀀스를 반환합니다.
8. 이 조인은 내부 조인이므로 두 번째 데이터 소스에 일치하는 항목이 있는 첫 번째 데이터 소스의 해당 개체만 반환됩니다.
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } class Cat : Pet { } class Dog : Pet { } public static void MultipleJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Person rui = new Person { FirstName = "Rui", LastName = "Raposo" }; Person phyllis = new Person { FirstName = "Phyllis", LastName = "Harris" }; Cat barley = new Cat { Name = "Barley", Owner = terry }; Cat boots = new Cat { Name = "Boots", Owner = terry }; Cat whiskers = new Cat { Name = "Whiskers", Owner = charlotte }; Cat bluemoon = new Cat { Name = "Blue Moon", Owner = rui }; Cat daisy = new Cat { Name = "Daisy", Owner = magnus }; Dog fourwheeldrive = new Dog { Name = "Four Wheel Drive", Owner = phyllis }; Dog duke = new Dog { Name = "Duke", Owner = magnus }; Dog denim = new Dog { Name = "Denim", Owner = terry }; Dog wiley = new Dog { Name = "Wiley", Owner = charlotte }; Dog snoopy = new Dog { Name = "Snoopy", Owner = rui }; Dog snickers = new Dog { Name = "Snickers", Owner = arlene }; // Create three lists. List<Person> people = new List<Person> { magnus, terry, charlotte, arlene, rui, phyllis }; List<Cat> cats = new List<Cat> { barley, boots, whiskers, bluemoon, daisy }; List<Dog> dogs = new List<Dog> { fourwheeldrive, duke, denim, wiley, snoopy, snickers }; // The first join matches Person and Cat.Owner from the list of people and // cats, based on a common Person. The second join matches dogs whose names start // with the same letter as the cats that have the same owner. var query = from person in people join cat in cats on person equals cat.Owner join dog in dogs on new { Owner = person, Letter = cat.Name.Substring(0, 1) } equals new { dog.Owner, Letter = dog.Name.Substring(0, 1) } select new { CatName = cat.Name, DogName = dog.Name }; foreach (var obj in query) { Console.WriteLine( "The cat \"{0}\" shares a house, and the first letter of their name, with \"{1}\".", obj.CatName, obj.DogName); } } // This code produces the following output: // // The cat "Daisy" shares a house, and the first letter of their name, with "Duke". // The cat "Whiskers" shares a house, and the first letter of their name, with "Wiley".
그룹화 조인을 사용한 내부 조인 예제
1. 다음 예제에서는 그룹 조인을 사용하여 내부 조인을 구현하는 방법을 보여 줍니다.
2. query1에서 Person 개체 목록은 Pet.Owner 속성에 일치하는 Person을 기준으로 Pet 개체 목록에 그룹 조인됩니다.
3. 그룹 조인은 각 그룹이 Person 개체 및 일치하는 Pet 개체의 시퀀스로 구성된 중간 그룹의 컬렉션을 만듭니다.
4. 쿼리에 두 번째 from 절을 추가하면 시퀀스의 해당 시퀀스가 보다 긴 시퀀스에 결합됩니다. 최종 시퀀스의 요소 형식은 select 절에서 지정됩니다.
5. 이 예제에서 형식은 Person.FirstName 및 일치하는 각 쌍에 대한 Pet.Name 속성으로 구성된 익명 형식입니다.
6. query1의 결과는 내부 조인을 수행하기 위해 C#의 into 절을 사용하지 않고 join 절을 사용하여 얻은 결과 집합과 동일합니다. query2 변수에서는 이와 동일한 쿼리를 보여 줍니다.
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } /// <summary> /// Performs an inner join by using GroupJoin(). /// </summary> public static void InnerGroupJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; // Create two lists. List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; var query1 = from person in people join pet in pets on person equals pet.Owner into gj from subpet in gj select new { OwnerName = person.FirstName, PetName = subpet.Name }; Console.WriteLine("Inner join using GroupJoin():"); foreach (var v in query1) { Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName); } var query2 = from person in people join pet in pets on person equals pet.Owner select new { OwnerName = person.FirstName, PetName = pet.Name }; Console.WriteLine("\nThe equivalent operation using Join():"); foreach (var v in query2) Console.WriteLine("{0} - {1}", v.OwnerName, v.PetName); } // This code produces the following output: // // Inner join using GroupJoin(): // Magnus - Daisy // Terry - Barley // Terry - Boots // Terry - Blue Moon // Charlotte - Whiskers // // The equivalent operation using Join(): // Magnus - Daisy // Terry - Barley // Terry - Boots // Terry - Blue Moon // Charlotte - Whiskers
방법: 그룹화 조인 수행
1. 그룹 조인은 계층적 데이터 구조를 생성하는 데 유용합니다.
2. 그룹 조인은 첫 번째 컬렉션의 각 요소와 두 번째 컬렉션의 연관된 요소 집합을 쌍으로 만듭니다.
3. 예를 들어, Student라는 클래스나 관계형 데이터베이스 테이블은 Id 및 Name이라는 두 개의 필드를 포함할 수 있습니다.
4. Course라는 두 번째 클래스나 관계형 데이터베이스 테이블은 StudentId 및 CourseTitle이라는 두 개의 필드를 포함할 수 있습니다.
5. 이 경우 일치하는 Student.Id 및 Course.StudentId를 기반으로 이러한 두 데이터 소스의 그룹 조인은 각 Student를 Course 개체(비어 있을 수도 있음)의 컬렉션과 그룹화합니다.
참고
첫 번째 컬렉션의 각 요소는 두 번째 컬렉션에서 연관된 요소가 발견되는지 여부에 상관없이 그룹 조인의 결과 집합에 나타납니다. 연관된 요소가 발견되지 않을 경우 해당 요소에 대한 연관된 요소의 시퀀스는 비어 있습니다. 따라서 결과 선택기는 첫 번째 컬렉션의 모든 요소에 액세스할 수 있습니다. 이는 두 번째 컬렉션에 일치하는 항목이 없는 첫 번째 컬렉션의 요소에 액세스할 수 없는 비그룹 조인의 결과 선택기와 다릅니다.
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } /// <summary> /// This example performs a grouped join. /// </summary> public static void GroupJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; // Create two lists. List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; // Create a list where each element is an anonymous type // that contains the person's first name and a collection of // pets that are owned by them. var query = from person in people join pet in pets on person equals pet.Owner into gj select new { OwnerName = person.FirstName, Pets = gj }; foreach (var v in query) { // Output the owner's name. Console.WriteLine("{0}:", v.OwnerName); // Output each of the owner's pet's names. foreach (Pet pet in v.Pets) Console.WriteLine(" {0}", pet.Name); } } // This code produces the following output: // // Magnus: // Daisy // Terry: // Barley // Boots // Blue Moon // Charlotte: // Whiskers // Arlene:
XML을 만들기 위한 그룹 조인 예제
1. 그룹 조인은 LINQ to XML을 사용하여 XML을 만드는 데 이상적입니다.
2. 다음 예제는 결과 선택기 함수에서 익명 형식을 만드는 대신에 조인된 개체를 나타내는 XML 요소를 만든다는 점을 제외하고 위의 예제와 비슷합니다.
3. LINQ to XML에 대한 자세한 내용은 LINQ to XML을 참조하십시오.
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } /// <summary> /// This example creates XML output from a grouped join. /// </summary> public static void GroupJoinXMLExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; // Create two lists. List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; // Create XML to display the hierarchical organization of people and their pets. XElement ownersAndPets = new XElement("PetOwners", from person in people join pet in pets on person equals pet.Owner into gj select new XElement("Person", new XAttribute("FirstName", person.FirstName), new XAttribute("LastName", person.LastName), from subpet in gj select new XElement("Pet", subpet.Name))); Console.WriteLine(ownersAndPets); } // This code produces the following output: // // <PetOwners> // <Person FirstName="Magnus" LastName="Hedlund"> // <Pet>Daisy</Pet> // </Person> // <Person FirstName="Terry" LastName="Adams"> // <Pet>Barley</Pet> // <Pet>Boots</Pet> // <Pet>Blue Moon</Pet> // </Person> // <Person FirstName="Charlotte" LastName="Weiss"> // <Pet>Whiskers</Pet> // </Person> // <Person FirstName="Arlene" LastName="Huff" /> // </PetOwners>
방법: 왼쪽 우선 외부 조인 수행
1. 왼쪽 외부 조인은 두 번째 컬렉션에 연관된 요소가 있는지 여부에 상관없이 첫 번째 컬렉션의 각 요소가 반환되는 조인입니다.
2. 사용 하면 LINQ 를 호출하여 왼쪽된 외부 조인을 수행 하는 DefaultIfEmpty<TSource> 메서드 그룹 조인의 결과를 합니다.
예제
1. 다음 예제에서는 그룹 조인의 결과에서 DefaultIfEmpty<TSource> 메서드를 사용하여 왼쪽 외부 조인을 수행하는 방법을 보여 줍니다.
2. 두 컬렉션의 왼쪽 외부 조인을 생성하는 첫 번째 단계는 그룹 조인을 사용하여 내부 조인을 수행하는 것입니다.
3. 이 프로세스에 대한 설명은 방법: 내부 조인 수행(C# 프로그래밍 가이드)을 참조하십시오.
4. 이 예제에서는 목록 Person 개체는 목록에 내부 조인 Pet 기반으로 하는 개체는 Person 일치 하는 개체 Pet.Owner.
두 번째 단계는 해당 요소가 오른쪽 컬렉션에 일치하는 항목이 없는 경우에도 결과 집합에서 첫 번째(왼쪽) 컬렉션의 각 요소를 포함하는 것입니다. 이렇게 하려면 그룹 조인에서 일치하는 요소의 각 시퀀스에 DefaultIfEmpty<TSource>를 호출합니다. 이 예제에서 DefaultIfEmpty<TSource> 각 시퀀스에 일치 하는 라고 Pet 개체입니다. 메서드 경우 단일 기본값이 포함 된 컬렉션이 반환 시퀀스에 일치 하는 Pet 개체 수에 대 한 빈 Person 함으로써 각 보장 하는 개체를 Person 결과 컬렉션에 개체를 표현 합니다.
class Person { public string FirstName { get; set; } public string LastName { get; set; } } class Pet { public string Name { get; set; } public Person Owner { get; set; } } public static void LeftOuterJoinExample() { Person magnus = new Person { FirstName = "Magnus", LastName = "Hedlund" }; Person terry = new Person { FirstName = "Terry", LastName = "Adams" }; Person charlotte = new Person { FirstName = "Charlotte", LastName = "Weiss" }; Person arlene = new Person { FirstName = "Arlene", LastName = "Huff" }; Pet barley = new Pet { Name = "Barley", Owner = terry }; Pet boots = new Pet { Name = "Boots", Owner = terry }; Pet whiskers = new Pet { Name = "Whiskers", Owner = charlotte }; Pet bluemoon = new Pet { Name = "Blue Moon", Owner = terry }; Pet daisy = new Pet { Name = "Daisy", Owner = magnus }; // Create two lists. List<Person> people = new List<Person> { magnus, terry, charlotte, arlene }; List<Pet> pets = new List<Pet> { barley, boots, whiskers, bluemoon, daisy }; var query = from person in people join pet in pets on person equals pet.Owner into gj from subpet in gj.DefaultIfEmpty() select new { person.FirstName, PetName = (subpet == null ? String.Empty : subpet.Name) }; foreach (var v in query) { Console.WriteLine("{0,-15}{1}", v.FirstName + ":", v.PetName); } } // This code produces the following output: // // Magnus: Daisy // Terry: Barley // Terry: Boots // Terry: Blue Moon // Charlotte: Whiskers // Arlene:
방법: 쿼리 식의 Null 값 처리
1. 이 예제에서는 소스 컬렉션에 포함될 수 있는 null 값의 처리 방법을 보여 줍니다.
2. IEnumerable<T>과 같은 개체 컬렉션에는 값이 null인 요소가 포함될 수 있습니다.
3. 소스 컬렉션이 null이거나 값이 null인 요소가 소스 컬렉션에 들어 있고 쿼리에서 null 값을 처리하지 않는 경우, 쿼리를 실행하면 NullReferenceException이 throw됩니다.
예제
1. 다음 예제와 같이 null 참조 예외가 발생하지 않도록 코드를 작성할 수 있습니다.
var query1 = from c in categories where c != null join p in products on c.ID equals (p == null ? null : p.CategoryID) select new { Category = c.Name, Name = p.Name };
2. 위의 예제에서 where 절은 범주 시퀀스에서 null 요소를 모두 필터링하여 제외합니다.
3. 이 기술은 조인 절의 null 검사 기능과는 관계없습니다.
4. 이 예제에서 null을 사용하는 조건 식은 Products.CategoryID가 int? 형식(Nullable<int>의 축약형)이기 때문에 제대로 작동합니다.
예제
1. 조인 절에서 비교 키 중 하나만 nullable 값 형식인 경우에는 쿼리 식에서 다른 키를 nullable 형식으로 캐스팅할 수 있습니다.
2. 다음 예제에서는 EmployeeID 열에 int? 형식의 값이 들어 있다고 가정합니다.
void TestMethod(Northwind db) { var query = from o in db.Orders join e in db.Employees on o.EmployeeID equals (int?)e.EmployeeID select new { o.OrderID, e.FirstName }; }
'프로그래밍 > C#' 카테고리의 다른 글
인터페이스, 이벤트, 인덱서 (0) | 2016.08.04 |
---|---|
제네릭 요약 (0) | 2016.08.02 |
형 변환 예제 (0) | 2016.07.10 |
StringBuilder 클래스 (0) | 2016.07.09 |
대리자 Action, Func (0) | 2016.07.08 |