from 절
from 절 (C# 참조)
쿼리 식은 반드시 from 절로 시작해야 해요. 그리고 쿼리 식 안에는 또 다른 쿼리 식(하위 쿼리)이 들어갈 수 있는데, 이 하위 쿼리 역시 from 절로 시작하죠. from 절은 다음 두 가지를 지정해 줍니다.
- 쿼리 또는 하위 쿼리가 실행되는 데이터 원본
- 원본 시퀀스의 각 요소를 나타내는 지역 범위 변수(range variable)
범위 변수와 데이터 원본은 모두 강력한 형식(strongly typed)으로 지정돼요. from 절에서 참조하는 데이터 원본은 xref:System.Collections.IEnumerable, xref:System.Collections.Generic.IEnumerable`1 같은 형식이거나, xref:System.Linq.IQueryable`1처럼 이들에서 파생된 형식이어야 합니다.
[!INCLUDEcsharp-version-note]
아래 예제에서 numbers는 데이터 원본이고 num은 범위 변수예요. 두 변수 모두 var 키워드를 썼음에도 강력한 형식이라는 점에 주목해 주세요.
namespace FromClause;
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
범위 변수
데이터 원본이 xref:System.Collections.Generic.IEnumerable`1을 구현하면 컴파일러가 범위 변수의 형식을 추론해요. 예를 들어 원본이 IEnumerable<Customer> 형식이라면 범위 변수는 Customer로 추론되죠. 형식을 명시적으로 지정해야 하는 경우는 원본이 xref:System.Collections.ArrayList 같은 비제네릭 IEnumerable 형식일 때뿐이에요. 자세한 내용은 How to query an ArrayList with LINQ를 참고하세요.
앞의 예제에서 num은 int 형식으로 추론돼요. 범위 변수는 강력한 형식이기 때문에 그 위에서 메서드를 호출하거나 다른 연산에 활용할 수 있죠. 예를 들어 select num 대신 select num.ToString()을 쓰면 쿼리 식이 정수 대신 문자열 시퀀스를 반환하도록 만들 수 있어요. 또는 select num + 10을 쓰면 14, 11, 13, 12, 10 시퀀스를 반환하도록 할 수 있고요. 자세한 내용은 select 절을 참고하세요.
범위 변수는 foreach 문의 반복 변수와 비슷하지만 아주 중요한 차이점이 하나 있어요. 범위 변수는 원본의 데이터를 실제로 저장하지 않아요. 그저 쿼리가 실행될 때 어떤 일이 일어날지 기술할 수 있게 해 주는 문법적 편의일 뿐이죠. 자세한 내용은 Introduction to LINQ Queries (C#)를 참고하세요.
복합 from 절
원본 시퀀스의 각 요소가 그 자체로 시퀀스이거나 시퀀스를 포함하고 있을 때가 있어요. 예를 들어 데이터 원본이 IEnumerable<Student>인데 시퀀스 안의 각 Student 객체가 시험 점수 목록을 포함하고 있다고 생각해 봐요. 각 Student 요소 안의 내부 목록에 접근하려면 복합 from 절을 쓰면 됩니다. 이 기법은 중첩 foreach 문을 쓰는 것과 비슷해요. 두 from 절 중 어느 쪽에든 where나 orderby 절을 추가해서 결과를 필터링할 수도 있어요. 아래 예제는 각각 시험 점수를 나타내는 정수 List를 내부에 담고 있는 Student 객체 시퀀스를 보여 줍니다. 내부 목록에 접근하려면 복합 from 절을 쓰고, 필요하면 두 from 절 사이에 다른 절을 끼워 넣어도 돼요.
namespace FromClause;
class CompoundFrom
{
// The element type of the data source.
public class Student
{
public required string LastName { get; init; }
public required List<int> Scores {get; init;}
}
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 Student {LastName="Omelchenko", Scores= [97, 72, 81, 60]},
new Student {LastName="O'Donnell", Scores= [75, 84, 91, 39]},
new Student {LastName="Mortensen", Scores= [88, 94, 65, 85]},
new Student {LastName="Garcia", Scores= [97, 89, 85, 82]},
new Student {LastName="Beebe", Scores= [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($"{student.Last} Score: {student.score}");
}
}
}
/*
scoreQuery:
Omelchenko Score: 97
O'Donnell Score: 91
Mortensen Score: 94
Garcia Score: 97
Beebe Score: 91
*/
여러 from 절로 조인 수행하기
복합 from 절은 하나의 데이터 원본 안의 내부 컬렉션에 접근할 때 써요. 그런데 쿼리는 서로 독립적인 데이터 원본에서 보조 쿼리를 만들어 내는 from 절을 여러 개 포함할 수도 있어요. 이 기법을 이용하면 join 절로는 만들 수 없는 특정 조인 작업을 수행할 수 있습니다.
아래 예제는 두 개의 from 절이 두 데이터 원본의 완전한 교차 조인(cross join)을 어떻게 구성하는지 보여 줍니다.
namespace FromClause;
class CompoundFrom2
{
static void Main()
{
char[] upperCase = ['A', 'B', 'C'];
char[] lowerCase = ['x', 'y', 'z'];
// The type of joinQuery1 is IEnumerable<'a>, where 'a
// indicates an anonymous type. This anonymous type has two
// members, upper and lower, both of type char.
var joinQuery1 =
from upper in upperCase
from lower in lowerCase
select new { upper, lower };
// The type of joinQuery2 is IEnumerable<'a>, where 'a
// indicates an anonymous type. This anonymous type has two
// members, upper and lower, both of type char.
var joinQuery2 =
from lower in lowerCase
where lower != 'x'
from upper in upperCase
select new { lower, upper };
// Execute the queries.
Console.WriteLine("Cross join:");
// Rest the mouse pointer on joinQuery1 to verify its type.
foreach (var pair in joinQuery1)
{
Console.WriteLine($"{pair.upper} is matched to {pair.lower}");
}
Console.WriteLine("Filtered non-equijoin:");
// Rest the mouse pointer over joinQuery2 to verify its type.
foreach (var pair in joinQuery2)
{
Console.WriteLine($"{pair.lower} is matched to {pair.upper}");
}
// Keep the console window open in debug mode.
Console.WriteLine("Press any key to exit.");
Console.ReadKey();
}
}
/* Output:
Cross join:
A is matched to x
A is matched to y
A is matched to z
B is matched to x
B is matched to y
B is matched to z
C is matched to x
C is matched to y
C is matched to z
Filtered non-equijoin:
y is matched to A
y is matched to B
y is matched to C
z is matched to A
z is matched to B
z is matched to C
*/
여러 from 절을 이용한 조인 작업에 대한 자세한 내용은 Perform left outer joins를 참고하세요.