LINQ to Entities의 쿼리
쿼리는 데이터 소스에서 데이터를 검색하는 식입니다. 관계형 데이터베이스에는 SQL이 사용되고 XML에는 XQuery가 사용되는 것과 같이 쿼리는 일반적으로 특수화된 쿼리 언어로 표현됩니다. 따라서 개발자는 쿼리하는 데이터 소스나 데이터 형식에 따라 새로운 쿼리 언어를 배워야 했습니다. LINQ(Language-Integrated Query)는 다양한 데이터 소스 및 형식에 사용할 수 있는 간단하고 일관된 모델을 제공합니다. LINQ 쿼리에서는 항상 프로그래밍 개체가 사용됩니다.
LINQ 쿼리 작업은 데이터 소스 가져오기, 쿼리 작성, 쿼리 실행의 세 가지 동작으로 구성됩니다.
IEnumerable 제네릭 인터페이스 또는 IQueryable 제네릭 인터페이스를 구현하는 데이터 소스는 LINQ를 통해 쿼리할 수 있습니다. 제네릭 IQueryable 인터페이스를 구현하는 제네릭 ObjectQuery 클래스의 인스턴스는 LINQ to Entities 쿼리의 데이터 소스로 사용됩니다. ObjectQuery 제네릭 클래스는 형식화된 엔터티의 컬렉션 또는 인스턴스를 반환하는 쿼리를 나타냅니다. 개발자는 CLR 개체로 EDM(엔터티 데이터 모델)과 상호 작용하기 위한 기본 클래스인 ObjectContext에서 인스턴스를 생성합니다.
쿼리에는 데이터 소스에서 검색하려는 정보를 정확히 지정해야 합니다. 또한 정보를 반환하기 전에 정보를 정렬, 그룹화 및 구체화하는 방법을 쿼리에 지정할 수 있습니다. LINQ에서 쿼리는 변수에 저장됩니다. 값 시퀀스를 반환하는 쿼리의 경우에는 쿼리 변수 자체가 쿼리 가능한 형식이어야 합니다. 이 쿼리 변수는 어떠한 작업을 수행하거나 데이터를 반환하지 않고 쿼리 정보를 저장하기만 합니다. 쿼리를 만든 후에는 해당 쿼리를 실행하여 데이터를 검색해야 합니다.
값 시퀀스를 반환하는 쿼리의 경우 쿼리 변수 자체에는 쿼리 결과가 저장되지 않고 쿼리 명령만 저장됩니다. 쿼리 실행은 foreach 또는 For Each 루프에서 쿼리 변수가 반복될 때까지 지연됩니다. 쿼리가 구성된 다음 쿼리가 실행되므로 지연된 실행이라고 합니다. 즉, 원하는 때에 언제라도 쿼리를 실행할 수 있습니다. 예를 들어 이 기능은 다른 응용 프로그램에서 업데이트되는 데이터베이스가 있을 때 유용합니다. 사용자의 응용 프로그램에서 최신 정보를 검색하는 쿼리를 만든 다음 쿼리를 반복적으로 실행하여 업데이트된 정보를 항상 반환할 수 있습니다.
값 시퀀스를 반환하는 지연된 쿼리와는 달리 singleton 값을 반환하는 쿼리는 즉시 실행됩니다. singleton 쿼리의 예로는 Count, Max, Average 및 First가 있습니다. singleton 결과를 계산하려면 쿼리 결과가 필요하므로 이러한 쿼리는 즉시 실행됩니다. 또한 쿼리에서 ToList 또는 ToArray 메서드를 사용하여 singleton 값을 생성하지 않는 쿼리를 즉시 실행할 수도 있습니다. 쿼리 결과를 캐시하려는 경우 즉시 실행을 적용하는 이러한 기술을 유용하게 사용할 수 있습니다. 지연된 쿼리 및 즉시 쿼리 실행에 대한 자세한 내용은 LINQ 소개을 참조하십시오.
쿼리
LINQ to Entities 쿼리는 쿼리 식 구문과 메서드 기반 쿼리 구문이라는 두 가지 구문으로 작성할 수 있습니다. 쿼리 식 구문은 C# 3.0과 Visual Basic 9.0에 새로 도입된 것으로서 Transact-SQL 또는 XQuery와 유사한 선언적 구문으로 작성된 일련의 절로 구성되어 있습니다. 그러나 .NET Framework CLR(공용 언어 런타임)에서는 쿼리 식 구문을 자체적으로 인식하지 못합니다. 따라서, 컴파일 과정에서 쿼리 식은 CLR에서 인식할 수 있는 형태인 메서드 호출로 변환됩니다. 이러한 메서드를 표준 쿼리 연산자라고 합니다. 개발자는 쿼리 구문을 사용하는 대신 메서드 구문을 사용하여 표준 쿼리 연산자를 직접 호출할 수도 있습니다. 자세한 내용은 쿼리 구문과 메서드 구문 비교(LINQ)를 참조하십시오. 표준 쿼리 연산자를 사용하는 방법에 대한 자세한 내용은 LINQ 일반 프로그래밍 가이드를 참조하십시오.
쿼리 식 구문
쿼리 식은 선언적 쿼리 구문입니다. 이 구문을 사용하면 개발자는 Transact-SQL과 비슷한 높은 수준의 언어로 쿼리를 작성할 수 있습니다. 쿼리 식 구문을 사용하면 최소한의 코드로 데이터 소스에 대해 복잡한 필터링, 정렬 및 그룹화 작업을 수행할 수 있습니다. 자세한 내용은 LINQ 쿼리 식(C# 프로그래밍 가이드) 및 기본 쿼리 작업(Visual Basic)을 참조하십시오.
다음 예제에서는 Select를 사용하여 Product 테이블의 모든 행을 반환하고 제품 이름을 표시합니다.
Using AWEntities As New AdventureWorksEntities
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim productNames = _
From p In products _
Select p.Name
Console.WriteLine("Product Names:")
For Each productName In productNames
Console.WriteLine(productName)
Next
End Using
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<string> productNames =
from p in products
select p.Name;
Console.WriteLine("Product Names:");
foreach (var productName in productNames)
{
Console.WriteLine(productName);
}
}
메서드 기반 쿼리 구문
메서드 기반 쿼리를 사용하여 LINQ to Entities 쿼리를 작성할 수도 있습니다. 메서드 기반 쿼리 구문은 람다 식을 매개 변수로 전달하는 LINQ 연산자 메서드에 대한 직접 메서드 호출의 시퀀스입니다. 자세한 내용은 람다 식(C# 프로그래밍 가이드)을 참조하십시오.
이 예제에서는 Select를 사용하여 Product 테이블의 모든 행을 반환하고 제품 이름을 표시합니다.
Using AWEntities As New AdventureWorksEntities
Dim productNames = AWEntities.Product.Select(Function(p) p.Name)
Console.WriteLine("Product Names:")
For Each productName In productNames
Console.WriteLine(productName)
Next
End Using
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<string> productNames = products.Select(p => p.Name);
Console.WriteLine("Product Names:");
foreach (var productName in productNames)
{
Console.WriteLine(productName);
}
}
쿼리 작성
위에서 설명한 것처럼 값 시퀀스를 반환하도록 쿼리가 디자인된 경우 쿼리 변수 자체에는 쿼리 명령만 저장됩니다. 쿼리에 즉시 실행을 발생시키는 메서드가 없으면 foreach 또는 For Each 루프에서 쿼리 변수를 반복할 때까지 쿼리의 실제 실행이 지연됩니다. 지연된 실행은 여러 쿼리를 결합하거나 하나의 쿼리를 확장 가능하게 합니다. 확장된 쿼리는 새 작업을 포함할 수 있도록 수정되며, 실행 결과에 변경 사항이 반영됩니다. 다음 예제의 첫 번째 쿼리에서는 모든 제품을 반환합니다. 두 번째 쿼리에서는 Where를 사용하여 첫 번째 쿼리를 확장하고 크기가 "L"인 모든 제품을 반환합니다.
Using AWEntities As New AdventureWorksEntities()
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim productsQuery = _
From p In products _
Select p
Dim largeProducts = _
productsQuery.Where(Function(p) p.Size = "L")
Console.WriteLine("Products of size 'L':")
For Each product In largeProducts
Console.WriteLine(product.Name)
Next
End Using
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> productsQuery =
from p in products
select p;
IQueryable<Product> largeProducts = productsQuery.Where(p => p.Size == "L");
Console.WriteLine("Products of size 'L':");
foreach (var product in largeProducts)
{
Console.WriteLine(product.Name);
}
}
쿼리를 실행한 이후의 모든 쿼리에서는 메모리 내 LINQ 연산자를 사용합니다. foreach 또는 For Each 문을 사용하거나 LINQ 변환 연산자를 호출하는 방법으로 쿼리 변수를 반복하면 즉시 실행됩니다. 이러한 변환 연산자에는 ToList, ToArray, ToLookup, ToDictionary가 있습니다.
다음 예제의 첫 번째 쿼리에서는 모든 제품을 반환합니다. 두 번째 쿼리에서는 첫 번째 쿼리 실행 이후 빨간색 제품을 반환하도록 첫 번째 쿼리를 확장합니다.
Using AWEntities As New AdventureWorksEntities()
Dim products As ObjectQuery(Of Product) = AWEntities.Product
Dim productsQuery = _
From p In products _
Select p
Console.WriteLine("The list of products:")
For Each Product As Product In productsQuery
Console.WriteLine(Product.Name)
Next
Dim redProducts = productsQuery _
.Where(Function(p) p.Color = "Red") _
.Select(Function(p) p)
Console.WriteLine("")
Console.WriteLine("The list of red products:")
For Each redProduct As Product In redProducts
Console.WriteLine(redProduct.Name)
Next
End Using
using (AdventureWorksEntities AWEntities = new AdventureWorksEntities())
{
ObjectQuery<Product> products = AWEntities.Product;
IQueryable<Product> productsQuery =
from p in products
select p;
Console.WriteLine("The list of products:");
foreach (Product product in productsQuery)
{
Console.WriteLine(product.Name);
}
IQueryable<Product> redProducts = productsQuery
.Where(p => p.Color == "Red")
.Select(p => p);
Console.WriteLine("");
Console.WriteLine("The list of red products:");
foreach (Product redProduct in redProducts)
{
Console.WriteLine(redProduct.Name);
}
}