IEnumerable<T>와 IQueryable<T>는 거의 동일한 API정의를 가진다.
대부분의 경우 두 인터페이스는 상호 교환이 가능한다. 하지만 사실 이 둘은 동작 방식도 다르고 성능도 크게 차이 난다.
//IQueryable<T>
var q = from c in dbContext.Customers
where c.City = "London"
select c;
var finalAnswer = from c in q
orderby c.Name
select c;
//IEumerable<T>
var q = (from c in dbContext.Customers
where c.City == "London"
select c).AsEumerable();
var finalAnswer = from c in q
orderby c.Name
select c;
위 코드의 결과는 동일하지만 동작 방식은 상의하다.
첫번째 예는 일반적인 LINQ to SQL 쿼리이며 IQueryable<T>의 기능을 사용한다.
LINQ to SQL 라이브러리가 모든 쿼리문을 결합하여 단번에 SQL 결과를 생성한다. 단 한차례 데이터베이스를 호출한다.
두 번째 예는 데이터베이스 객체를 IEnumerable<T> 시퀀스로 변경하기 때문에 데이터 베이스가 아니라 로컬에서 더 많은 작업을 수행하게 된다. 쿼리문이 IEnumerable<T> 시퀀스를 반환하므로 그 다음 작업은 LINQ to Objects 구현체와 델리케이트를 이용하여 수행된다. 첫 번째 쿼리문이 수행되면 데이터베이스에 쿼리를 전달하여 City값이 London인 모든 레코드를 가져온다. 이후 로컬 머신에서 Name 필드에 따라 정렬한다.
대부분의 경우 쿼리 작업을 수행할 때 IEnumerable<T> 보다는 IQueryable<T>를 사용하는편이 훨씬 효율적이다.
Enumerable<T>
쿼리식 내의 람다 표현식과 함수 매개변수를 나타내기 위해 델리게이트를 사용한다.
모든 메서드가 로컬 머신에서 수행된다. 따라서 모든 데이터를 메모리로 가져와야 한다.
Queryble<T>
표현식 트리를 이용하여 이를 처리한다.
데이터가 실제 위치하고 있는 컴퓨터에서 수행한다.
코드로 표현할 수 있는 쿼리의 표현식이 IEnumerable<T>에 비해 제한적이다.
다른 메서드를 호출
IQueryable<T>는 각각의 메서드를 분석하지 않는다.
따라서 쿼리 표현식이 다른 메서드를 호출한다면 Enumerable구현체를 사용하도록 변경해야한다.
private bool isValidProduct(Product p) =>
p.ProductName.LastIndexOf('C') == 0;
//다음 코드는 정상 작동한다.
var q1 = from p in dbContext.Products.AsEnumerable()
where isValidProduct(p)
select p;
//다음 코드는 반환도니 컬렉션을 순회할 때 예외를 유발한다.
var q2 = from p in dbContext.Products
where isValidProduct(p)
select p;
따라서 성능보다 안정성을 원한다면 쿼리 결과를 IEnumerable<T>로 변환하여 예외를 회피할 수 있다.
AsQueryable()
시퀀스의 런타임 타입을 확인한다. 만약 런타임 타입이 IQueryable이라면 IQueryable을 반환하고, 반대로 IEnumerable 타입이면 LINQ to Objects를 사용하여 IQueryable을 구현한 래퍼를 생성하여 반환한다.
public static IEnumerable<Product> BalidProducts(this IEnumerable<Product> products) =>
from p in products.AsQueryable()
where p.ProductName.LastInexOf('C') == 0
select p;
즉, IQueryable을 구현하고 있는 경우 그 구현체를 사용하고, IEnumerable만 구현하고 있는 경우에도 문젱벗이 동작한다.
결론
IQueryable<T>와 IEnumerable<T>는 동일한 기능을 제공하는 것 처럼 보이지만 사실 동작 방식에서 매우 다르기 때문에 데이터 원본이 어떤 인터페이스르 제공하느냐에 따라 쿼리를 구성하도록 해야한다.
'C# > Effective C#' 카테고리의 다른 글
[Effective C#] Item 44 바인딩 된 변수는 수정하지 말라 (0) | 2023.05.23 |
---|---|
[Effective C#] Item 43 쿼리 결과의 의미를 명확히 강제하고, Single()과 First()를 사용하라. (0) | 2023.05.11 |
[Effective C#] Item 40 지연 수행과 즉시 수행을 구분하라 (0) | 2023.05.02 |
[Effective C# Item 37] 쿼리를 사용할 때는 즉시 평가보다 지연 평가가 낫다 (0) | 2023.05.02 |
[Effective C# Item 34] 함수를 매개변수로 사용하여 결합도를 낮춰라 (0) | 2023.01.10 |