답변:
예, 둘 다 실행 연기 를 제공합니다 .
차이점은 IQueryable<T>
LINQ-to-SQL (LINQ.-to-anything)이 작동하도록하는 인터페이스입니다. 따라서에 대한 쿼리를 더 세분화 IQueryable<T>
하면 가능한 경우 해당 쿼리가 데이터베이스에서 실행됩니다.
이 IEnumerable<T>
경우 LINQ-to-Object가되므로 원래 쿼리와 일치하는 모든 개체를 데이터베이스에서 메모리로로드해야합니다.
코드에서 :
IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
이 코드는 SQL을 실행하여 골드 고객 만 선택합니다. 반면 다음 코드는 데이터베이스에서 원래 쿼리를 실행 한 다음 메모리에서 비 골드 고객을 필터링합니다.
IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);
이것은 매우 중요한 차이점이며, 작업을 IQueryable<T>
하면 데이터베이스에서 너무 많은 행을 리턴하지 않아도됩니다. 또 다른 주요 예는 페이징을 수행하는 것입니다. Take
and Skip
on 을 사용 IQueryable
하면 요청 된 행 수만 가져옵니다. 이 작업을 수행 IEnumerable<T>
하면 모든 행이 메모리에로드됩니다.
IEnumerable
로는 IQueryable
모든 LINQ 작업이 모든 LINQ 공급자에서 지원하는 것입니다. 따라서 수행중인 작업을 알고있는 IQueryable
한 많은 쿼리를 LINQ 공급자 (LINQ2SQL, EF, NHibernate, MongoDB 등)에 푸시하는 데 사용할 수 있습니다 . 그러나 다른 코드를 사용하여 원하는 작업을 수행 IQueryable
하면 일부 클라이언트 코드가 지원되지 않는 작업을 사용했기 때문에 결국 문제가 생길 수 있습니다. 나는 IQueryable
저장소 나 그와 동등한 계층을지나 "와일드로" 릴리스하지 말 것을 권장 한다.
가장 좋은 대답은 좋지만 두 인터페이스가 어떻게 다른지 설명하는 표현 트리는 언급하지 않습니다. 기본적으로 두 개의 동일한 LINQ 확장 세트가 있습니다. Where()
, Sum()
, Count()
, FirstOrDefault()
기능을 받아들이와 표현을 허용 하나, 등 모두 두 가지 버전이있다.
IEnumerable
버전의 서명은 다음과 같습니다Where(Func<Customer, bool> predicate)
IQueryable
버전의 서명은 다음과 같습니다Where(Expression<Func<Customer, bool>> predicate)
둘 다 동일한 구문을 사용하여 호출되기 때문에 실현하지 않고 두 가지를 모두 사용했을 것입니다.
예를 들어,이 Where(x => x.City == "<City>")
모두에서 작동 IEnumerable
하고IQueryable
사용시 Where()
온 IEnumerable
컬렉션 컴파일러로 컴파일 된 함수를 전달Where()
사용하는 경우 Where()
온 IQueryable
모음, 컴파일러에 식 트리를 전달합니다 Where()
. 표현식 트리는 리플렉션 시스템과 유사하지만 코드 용입니다. 컴파일러는 코드를 쉽게 소화 할 수있는 형식으로 코드의 기능을 설명하는 데이터 구조로 코드를 변환합니다.
왜이 표현 트리에 신경을 쓰나요? Where()
데이터를 필터링 하고 싶습니다 .
주된 이유는 EF와 Linq2SQL ORM이 모두 표현식 트리를 SQL로 직접 변환하여 코드가 훨씬 빠르게 실행될 수 있기 때문입니다.
아, 그것은 무료 성능 향상처럼 들립니다 AsQueryable()
. 그 경우 어디에서나 사용해야 합니까?
아니요, IQueryable
기본 데이터 공급자가 무언가를 할 수있는 경우에만 유용합니다. 정기적처럼 뭔가를 변환 List
하는 것은 IQueryable
당신에게 어떤 혜택을 제공하지 않습니다.
IQueryable
만큼 잘하지 구문 분석 기능을하지 IEnumerable
않습니다 : 예를 들어, 당신은의 요소를 알고 싶은 경우 DBSet<BookEntity>
A의없는 List<BookObject>
, dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))
예외가 발생합니다 : Expression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'
. .ToList()
후에 추가해야했습니다 dbSetObject
.
예, 둘 다 지연된 실행을 사용합니다. SQL Server 프로파일 러를 사용하여 차이점을 설명하겠습니다 ....
다음 코드를 실행할 때 :
MarketDevEntities db = new MarketDevEntities();
IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);
Console.Write(result.First().UserName);
SQL Server 프로파일 러에서 다음과 같은 명령을 찾습니다.
"SELECT * FROM [dbo].[WebLog]"
1 백만 개의 레코드가있는 WebLog 테이블에 대해 해당 코드 블록을 실행하는 데 약 90 초가 걸립니다.
따라서 모든 테이블 레코드는 메모리에 객체로로드 된 다음 각 .Where ()와 함께 이러한 객체에 대한 메모리의 또 다른 필터가됩니다.
위의 예 (두 번째 줄) IQueryable
대신에 사용할 경우 IEnumerable
:
SQL Server 프로파일 러에서 다음과 같은 명령을 찾습니다.
"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"
를 사용하여이 코드 블록을 실행하는 데 약 4 초가 걸립니다 IQueryable
.
IQueryable에는 예제에서 지연된 실행이라고하는 Expression
사용시 생성되기 시작하는 트리 표현식을 저장 하는 속성이 있으며 result
,이 표현식은 데이터베이스 엔진에서 실행되도록 SQL 쿼리로 변환됩니다.
이전에 많은 이야기가 있지만 더 기술적 인 방법으로 뿌리로 돌아 왔습니다.
IEnumerable
는 열거 할 수있는 메모리에있는 객체의 모음입니다. 메모리 내에서 반복 할 수있는 메모리 내 시퀀스입니다 ( foreach
루프 내에서 쉽게 사용할 수 있지만, 계속 사용할 수는 IEnumerator
있음). 그것들은 그대로 메모리에 상주합니다.IQueryable
최종 결과에 대해 열거 할 수있는 능력을 가진 어떤 시점에서 다른 것으로 번역 될 표현 트리 입니다 . 나는 이것이 대부분의 사람들을 혼란스럽게하는 것 같아요.그들은 분명히 다른 의미를 가지고 있습니다.
IQueryable
LINQ 집계 함수 (Sum, Count 등) 또는 ToList [Array, Dictionary 등의 릴리스 API가 호출 되 자마자 기본 쿼리 공급자가 다른 것으로 변환 할 식 트리 (단순히 쿼리)를 나타냅니다. ..]. 그리고 IQueryable
또한 구현하는 객체 IEnumerable
, IEnumerable<T>
그래서 그들은 쿼리를 표현하는 경우 해당 쿼리의 결과를 반복 할 수있다. 이는 IQueryable이 쿼리 일 필요는 없음을 의미합니다. 올바른 용어는 표현 트리 입니다.
이제 이러한 표현식이 실행되는 방식과 전환 대상은 모두 쿼리 제공자 (우리가 생각할 수있는 표현식 실행기)에 달려 있습니다.
에서 엔티티 프레임 워크 (즉 신비 기본 데이터 소스 제공 업체 또는 쿼리 제공 업체입니다) 세계 IQueryable
표현식은 네이티브로 번역되는 T-SQL의 쿼리. Nhibernate
그들과 비슷한 일을합니다. 예를 들어 LINQ : IQueryable Provider 링크 작성에 설명 된 개념에 따라 고유 한 것을 작성할 수 있으며 제품 상점 제공자 서비스에 대한 사용자 정의 조회 API를 원할 수 있습니다.
따라서 기본적으로 IQueryable
객체는 명시 적으로 릴리스하고 시스템에 객체를 SQL 등으로 다시 작성하고 이후 처리를 위해 실행 체인을 보내도록 지시 할 때까지 오래 지속됩니다.
실행 을 연기하는 것처럼 LINQ
특정 API가 시퀀스 (동일한 Count, ToList 등)에 대해 호출 될 때마다 메모리에서 표현식 트리 체계를 유지하고 필요할 때만 실행으로 전송하는 기능입니다.
두 가지 모두의 올바른 사용법은 특정 사례에 직면 한 작업에 따라 크게 다릅니다. 잘 알려진 리포지토리 패턴의 경우 개인적으로을 반환하도록 선택합니다 IList
. 즉 IEnumerable
목록 (인덱서 등)입니다. 따라서 IQueryable
코드의 다른 곳에서 저장소 및 IEnumerable 내에서만 사용하는 것이 좋습니다. 우려 분리 원칙을 IQueryable
세분화하고 파괴 하는 테스트 가능성 우려에 대해서는 언급하지 않습니다 . 리포지토리 내에서 식을 반환하면 소비자는 원하는대로 지속성 계층을 사용할 수 있습니다.
엉망에 약간의 추가 :) (댓글의 토론에서)) 실제 유형이 아니기 때문에 메모리에있는 객체는 없습니다. 유형에 대한 마커입니다. 그러나 그것은 의미가 있습니다 (심지어 이유입니다 MSDN은 그것을 이런 식으로 넣어) 식 트리로 IQueryables 반면 메모리 컬렉션으로 IEnumerables 생각. 요점은 IQueryable 인터페이스가 IEnumerable 인터페이스를 상속하므로 쿼리를 나타내는 경우 해당 쿼리 결과를 열거 할 수 있다는 것입니다. 열거하면 IQueryable 개체와 관련된 식 트리가 실행됩니다. 따라서 실제로 메모리에 객체가 없으면 IEnumerable 멤버를 호출 할 수 없습니다. 어쨌든 비어 있지 않으면 거기에 들어갑니다. IQueryables는 데이터가 아니라 쿼리 일뿐입니다.
일반적으로 쿼리의 원래 정적 유형은 중요 할 때까지 유지하려고합니다.
이러한 이유로 IQueryable<>
또는 대신 변수를 'var'로 정의 할 수 IEnumerable<>
있으며 유형이 변경되지 않음을 알 수 있습니다.
로 시작하는 경우 IQueryable<>
일반적 IQueryable<>
으로을 변경해야 할 몇 가지 이유가있을 때까지 이를 유지하려고 합니다. 그 이유는 가능한 많은 정보를 쿼리 프로세서에 제공하기 때문입니다. 예를 들어 10 개의 결과 ()를 사용 Take(10)
하려는 경우 쿼리 계획을 최적화하고 사용할 데이터 만 보낼 수 있도록 SQL Server가 그 사실을 알기를 원합니다.
에서 눈길을 끄는 이유는 유형을 변경하기 IQueryable<>
에 IEnumerable<>
당신의 구현 몇 가지 확장 기능을 호출하는 것을 수 있습니다 IQueryable<>
특정 객체가 비효율적으로 처리하거나 핸들 할 수없는 하나를. 이 경우 호출하는 확장 함수가 클래스 대신 클래스 의 함수가되도록 유형을 IEnumerable<>
(변수 유형에 할당 IEnumerable<>
하거나 AsEnumerable
확장 메서드를 사용하여) 로 변환 할 수 있습니다 .Enumerable
Queryable
오용이 IEnumerable<T>
LINQ 쿼리 성능에 크게 영향을 미치는 방법에 대한 간단한 소스 코드 샘플이 포함 된 블로그 게시물이 있습니다 ( Entity Framework : IQueryable vs. IEnumerable) .
더 깊이 파고 소스를 살펴보면 다음과 같은 확장 방법이 분명히 다른 것을 알 수 있습니다 IEnumerable<T>
.
// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
public static IEnumerable<TSource> Where<TSource>(
this IEnumerable<TSource> source,
Func<TSource, bool> predicate)
{
return (IEnumerable<TSource>)
new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
}
}
그리고 IQueryable<T>
:
// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
public static IQueryable<TSource> Where<TSource>(
this IQueryable<TSource> source,
Expression<Func<TSource, bool>> predicate)
{
return source.Provider.CreateQuery<TSource>(
Expression.Call(
null,
((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
new Type[] { typeof(TSource) }),
new Expression[]
{ source.Expression, Expression.Quote(predicate) }));
}
}
첫 번째는 열거 가능한 반복자를 반환하고 두 번째는 IQueryable
소스에 지정된 쿼리 공급자를 통해 쿼리를 만듭니다 .
최근에 IEnumerable
v. 문제가 발생 했습니다 IQueryable
. 사용되는 알고리즘은 먼저 IQueryable
일련의 결과를 얻기 위해 쿼리를 수행했습니다 . 그런 다음 foreach
항목을 Entity Framework (EF) 클래스로 인스턴스화 하여 루프 로 전달했습니다 . 이 EF 클래스는 from
Linq to Entity 쿼리 의 절 에서 사용되어 결과가되었습니다 IEnumerable
.
저는 EF와 Linq for Entities를 처음 접했기 때문에 병목 현상이 무엇인지 파악하는 데 시간이 걸렸습니다. MiniProfiling을 사용하여 쿼리를 찾은 다음 모든 개별 작업을 하나의 IQueryable
Linq for Entities 쿼리로 변환했습니다. 실행하는 IEnumerable
데 15 초가 IQueryable
걸리고 0.5 초가 걸렸습니다. 세 개의 테이블이 관련되어 있었고, 이것을 읽은 후에는 IEnumerable
쿼리가 실제로 3 개의 테이블 교차 제품을 형성하고 결과를 필터링 했다고 생각합니다 .
IQueryables를 일반적으로 사용하고 변경 사항을 측정 할 수 있도록 작업을 프로파일 링하십시오.
IQueryable
API 중 하나를 호출하면 메모리에 갇혀 있지만 그렇지 않은 경우 표현식을 레이어 스택 위로 전달하고 API 호출까지 필터를 사용할 수 있습니다. 잘 설계된 DAL은 잘 설계된 리포지토리로 이러한 종류의 문제를 해결할 것입니다.)
나는 상충되는 응답으로 인해 (주로 IEnumerable을 둘러싼) 몇 가지 사항을 분명히하고 싶습니다.
(1) 인터페이스를 IQueryable
확장합니다 IEnumerable
. ( 오류없이 IQueryable
예상되는 것을 보낼 수 있습니다 IEnumerable
.)
(2) IQueryable
및 IEnumerable
LINQ는 결과 집합을 반복 할 때 지연로드를 시도합니다. (구현은 각 유형에 대한 인터페이스 확장 메소드에서 볼 수 있습니다.)
다시 말해, IEnumerables
독점적으로 "메모리 내"가 아닙니다. IQueryables
데이터베이스에서 항상 실행되는 것은 아닙니다. IEnumerable
추상 데이터 공급자가 없으므로 메모리에 항목을로드해야합니다 (한 번 검색되면 지연 될 수 있음). IQueryables
LINQ-to-SQL과 같은 추상 공급자를 사용하지만 .NET 인 메모리 공급자 일 수도 있습니다.
샘플 사용 사례
(a) IQueryable
EF 컨텍스트에서 와 같이 레코드 목록을 검색합니다 . (메모리에 기록이 없습니다.)
(b) IQueryable
모델이 인 뷰로 전달 IEnumerable
합니다. (유효가. IQueryable
확장 IEnumerable
.)
(c) 뷰에서 데이터 세트의 레코드, 하위 엔터티 및 속성을 반복하고 액세스합니다. (예외가 발생할 수 있습니다!)
가능한 문제
(1) IEnumerable
게으른 로딩 시도 및 데이터 컨텍스트가 만료되었습니다. 공급자를 더 이상 사용할 수 없어서 예외가 발생했습니다.
(2) Entity Framework 엔터티 프록시가 활성화되어 있고 (기본값) 데이터 컨텍스트가 만료 된 관련 (가상) 개체에 액세스하려고합니다. (1)과 같습니다.
(3) 다중 활성 결과 세트 (MARS). 블록 IEnumerable
내에서 반복하면서 foreach( var record in resultSet )
동시에 액세스를 시도하는 record.childEntity.childProperty
경우 데이터 세트와 관계 엔티티 모두의 지연로드로 인해 MARS로 끝날 수 있습니다. 연결 문자열에서 활성화되지 않은 경우 예외가 발생합니다.
해결책
호출을 통해 쿼리를 실행하고 결과를 저장합니다. resultList = resultSet.ToList()
이것은 엔터티가 메모리에 있는지 확인하는 가장 간단한 방법 인 것 같습니다.
관련 엔터티에 액세스하는 경우에도 데이터 컨텍스트가 필요할 수 있습니다. 또는 엔티티 프록시 및 명시 적으로 Include
관련된 엔티티를에서 비활성화 할 수 있습니다 DbSet
.
“IEnumerable”과“IQueryable”의 주요 차이점은 필터 로직이 실행되는 위치입니다. 하나는 클라이언트 측 (메모리)에서 실행되고 다른 하나는 데이터베이스에서 실행됩니다.
예를 들어, 데이터베이스에 사용자에 대한 10,000 개의 레코드가 있고 활성 사용자 인 900 명을 예로 들어 보겠습니다.이 경우 "IEnumerable"을 사용하면 먼저 10,000 개의 레코드를 모두 메모리에로드하고 그런 다음 IsActive 필터를 적용하여 900 명의 활성 사용자를 반환합니다.
반면에 "IQueryable"을 사용하는 경우 데이터베이스에서 IsActive 필터를 직접 적용하여 900 명의 활성 사용자를 직접 반환합니다.
참조 링크
우리는 같은 방식으로 둘 다 사용할 수 있으며 성능면에서만 다릅니다.
IQueryable은 데이터베이스에 대해서만 효율적인 방식으로 실행됩니다. 전체 선택 쿼리를 작성하고 관련 레코드 만 가져옵니다.
예를 들어 이름이 'Nimal'로 시작 하는 상위 10 명의 고객을 확보 하려고합니다 . 이 경우 선택 쿼리는로 생성됩니다 select top 10 * from Customer where name like ‘Nimal%’
.
그러나 IEnumerable을 사용하면 쿼리가 유사 select * from Customer where name like ‘Nimal%’
하고 상위 10 개가 C # 코딩 수준에서 필터링됩니다 (데이터베이스에서 모든 고객 레코드를 가져 와서 C #으로 전달).
처음 두 가지 좋은 답변 외에도 (driis & Jacob의) :
IEnumerable 인터페이스는 System.Collections 네임 스페이스에 있습니다.
IEnumerable 개체는 메모리의 데이터 집합을 나타내며이 데이터를 앞으로 만 이동할 수 있습니다. IEnumerable 개체로 표시되는 쿼리는 즉시 완벽하게 실행되므로 응용 프로그램에서 데이터를 빠르게받습니다.
쿼리가 실행될 때 IEnumerable은 모든 데이터를로드하며, 필터링이 필요한 경우 필터링 자체가 클라이언트 측에서 수행됩니다.
IQueryable 인터페이스는 System.Linq 네임 스페이스에 있습니다.
IQueryable 객체는 데이터베이스에 대한 원격 액세스를 제공하며 데이터를 처음부터 끝까지 또는 반대 순서로 탐색 할 수 있습니다. 쿼리를 생성하는 과정에서 반환 된 개체는 IQueryable이며 쿼리가 최적화됩니다. 결과적으로 실행 중에 메모리 소비가 적고 네트워크 대역폭이 줄어들지 만 동시에 IEnumerable 개체를 반환하는 쿼리보다 약간 느리게 처리 될 수 있습니다.
무엇을 선택해야합니까?
전체 반환 데이터 집합이 필요한 경우 최대 속도를 제공하는 IEnumerable을 사용하는 것이 좋습니다.
반환 된 전체 데이터 집합이 필요하지 않고 일부 필터링 된 데이터 만 필요한 경우 IQueryable을 사용하는 것이 좋습니다.
위의 것 외에도 다음 IQueryable
대신에 사용하면 예외가 발생할 수 있습니다 IEnumerable
.
경우 다음은 잘 작동 products
입니다 IEnumerable
:
products.Skip(-4);
그러나 products
is IQueryable
이고 DB 테이블에서 레코드에 액세스하려고하면이 오류가 발생합니다.
OFFSET 절에 지정된 오프셋은 음수가 아닐 수 있습니다.
다음 쿼리가 생성 되었기 때문입니다.
SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS
OFFSET은 음수 값을 가질 수 없습니다.