다음 본문은 도서 이펙티브 C# (빌 와그너)에서 나오는 주제를 다룹니다.
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;
// IEnumerable<T>
var q = (from c in dbContext.Customers
where c.City == "London"
select c).AsEnumerable();
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>
쿼리식 내의 람다 표현식과 함수 매개변수를 나타내기 위해 델리게이트를 사용한다.
모든 메서드가 로컬 머신에서 수행된다. 따라서 모든 데이터를 메모리로 가져와야 한다.
Queryable<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> ValidProducts(
this IEnumerable<Product> products) =>
from p in products.AsQueryable()
where p.ProductName.LastIndexOf('C') == 0
select p;
즉, IQueryable을 구현하고 있는 경우 그 구현체를 사용하고, IEnumerable만을 구현하고 있는 경우에도 문제없이 동작한다.
결론
IQueryable<T>와 IEnumerable<T>는 동일한 기능을 제공하는 것처럼 보이지만 사실 동작 방식이 매우 다르기 때문에 데이터 원본이 어떤 인터페이스를 제공하느냐에 따라 쿼리를 구성하도록 하자.
'🌍 C# Study > 이펙티브 C#' 카테고리의 다른 글
[44] 바인딩 된 변수는 수정하지 말라 (0) | 2021.05.12 |
---|---|
[43] 쿼리 결과의 의미를 명확히 강제하고, Single()과 First()를 사용하라 (0) | 2021.05.12 |
[41] 값비싼 리소스를 캡처하지 마라 (0) | 2021.05.03 |
[40] 지연 수행과 즉시 수행을 구분하라 (0) | 2021.05.02 |
[39] function과 action 내에서는 예외가 발생하지 않도록 하라 (0) | 2021.05.02 |