[entity-framework] .ToList (), .AsEnumerable (), AsQueryable ()의 차이점은 무엇입니까?

나는 LINQ to Entities와 LINQ to Objects의 첫 번째 구현 IQueryable과 두 번째 구현의 차이점을 알고 있습니다.IEnumerable 이며 내 질문 범위는 EF 5 내에 있습니다.

내 질문은이 세 가지 방법의 기술적 차이점은 무엇입니까? 나는 많은 상황에서 그들 모두가 작동하는 것을 본다. 또한 같은 조합을 사용하는 것을 볼 수 있습니다 .ToList().AsQueryable().

  1. 그 방법은 정확히 무엇을 의미합니까?

  2. 성능 문제 또는 다른 것을 사용하는 데 문제가 있습니까?

  3. 예를 들어 왜 .ToList().AsQueryable()대신에 사용 .AsQueryable()합니까?



답변

이것에 대해 할 말이 많습니다. 저에 집중하자 AsEnumerableAsQueryable및 언급 ToList()길을 따라.

이 방법들은 무엇을 하는가?

AsEnumerableAsQueryable캐스트 또는 변환에 IEnumerable또는 IQueryable각각. 나는 이유를 가지고 캐스트하거나 변환 한다고 말합니다 .

  • 소스 객체가 이미 대상 인터페이스를 구현하면 소스 객체 자체가 반환되지만 대상 인터페이스로 캐스트 됩니다. 즉, 유형은 변경되지 않지만 컴파일 타임 유형은 변경됩니다.

  • 소스 객체가 대상 인터페이스를 구현하지 않으면 소스 객체가 대상 인터페이스를 구현하는 객체로 변환 됩니다. 따라서 유형과 컴파일 타임 유형이 모두 변경됩니다.

몇 가지 예를 보여 드리겠습니다. 컴파일 타임 유형과 객체의 실제 유형 ( Jon Skeet 제공 ) 을보고하는이 작은 방법이 있습니다 .

void ReportTypeProperties<T>(T obj)
{
    Console.WriteLine("Compile-time type: {0}", typeof(T).Name);
    Console.WriteLine("Actual type: {0}", obj.GetType().Name);
}

의 임의 LINQ – 투 – SQL 해보자 Table<T>, 구현을 IQueryable:

ReportTypeProperties(context.Observations);
ReportTypeProperties(context.Observations.AsEnumerable());
ReportTypeProperties(context.Observations.AsQueryable());

결과:

Compile-time type: Table`1
Actual type: Table`1

Compile-time type: IEnumerable`1
Actual type: Table`1

Compile-time type: IQueryable`1
Actual type: Table`1

테이블 클래스 자체는 항상 반환되지만 해당 표현은 변경됩니다.

이제가 IEnumerable아닌 을 구현하는 객체 IQueryable:

var ints = new[] { 1, 2 };
ReportTypeProperties(ints);
ReportTypeProperties(ints.AsEnumerable());
ReportTypeProperties(ints.AsQueryable());

결과 :

Compile-time type: Int32[]
Actual type: Int32[]

Compile-time type: IEnumerable`1
Actual type: Int32[]

Compile-time type: IQueryable`1
Actual type: EnumerableQuery`1

거기는. AsQueryable()배열을로 변환 했습니다 . ” 컬렉션을 데이터 소스 로 EnumerableQuery나타냅니다 .” (MSDN).IEnumerable<T>IQueryable<T>

용도는 무엇입니까?

AsEnumerableIQueryable구현이 LINQ에서 객체 (L2O) 로 전환하는 데 자주 사용됩니다 . 대부분 전자가 L2O의 기능을 지원하지 않기 때문입니다. 자세한 내용은 LINQ 엔터티에 AsEnumerable ()의 영향무엇입니까?를 참조하십시오 . .

예를 들어, Entity Framework 쿼리에서는 제한된 수의 메서드 만 사용할 수 있습니다. 예를 들어 쿼리에서 자체 메소드 중 하나를 사용해야하는 경우 일반적으로 다음과 같이 작성합니다.

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => MySuperSmartMethod(x))

ToList – 변환 IEnumerable<T>A를 List<T>– 자주뿐만 아니라이 목적을 위해 사용됩니다. 사용의 장점 AsEnumerable대는 ToList것입니다 AsEnumerable쿼리를 실행하지 않습니다. AsEnumerable지연된 실행을 유지하고 종종 쓸모없는 중간 목록을 작성하지 않습니다.

반면에 LINQ 쿼리를 강제로 실행하려는 경우이를 수행 ToList할 수 있습니다.

AsQueryable열거 가능한 컬렉션이 LINQ 문에서 식을 허용하도록하는 데 사용할 수 있습니다. 자세한 내용은 여기를 참조하십시오. 콜렉션에 AsQueryable ()을 사용해야합니까?.

약물 남용에 대한 참고 사항!

AsEnumerable마약처럼 작동합니다. 빠른 수정이지만 비용이 많이 들고 근본적인 문제를 해결하지 못합니다.

많은 스택 오버플로 답변 AsEnumerable에서 LINQ 표현식의 지원되지 않는 메소드 관련 문제를 해결하기 위해 신청 하는 사람들이 있습니다. 그러나 가격이 항상 명확한 것은 아닙니다. 예를 들어, 이렇게하면 :

context.MyLongWideTable // A table with many records and columns
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate })

… 모든 것이 깔끔하게 SQL 문으로 변환되어 ( ) 및 프로젝트 ( ) 를 필터링 합니다. 즉, SQL 결과 세트의 길이와 너비가 각각 줄어 듭니다.WhereSelect

이제 사용자가의 날짜 부분 만보고 싶어한다고 가정합니다 CreateDate. Entity Framework에서 다음을 신속하게 발견 할 수 있습니다.

.Select(x => new { x.Name, x.CreateDate.Date })

… (작성 당시)는 지원되지 않습니다. 아, 다행스럽게도 AsEnumerable수정 사항이 있습니다.

context.MyLongWideTable.AsEnumerable()
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, x.CreateDate.Date })

물론, 아마도 작동합니다. 그러나 전체 테이블을 메모리로 가져온 다음 필터와 프로젝션을 적용합니다. 글쎄, 대부분의 사람들은 Where첫 번째 일을 할만 큼 똑똑합니다 .

context.MyLongWideTable
       .Where(x => x.Type == "type").AsEnumerable()
       .Select(x => new { x.Name, x.CreateDate.Date })

그러나 여전히 모든 열을 먼저 가져오고 투영은 메모리에서 수행됩니다.

실제 수정 사항은 다음과 같습니다.

context.MyLongWideTable
       .Where(x => x.Type == "type")
       .Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })

(그러나 그것은 조금 더 지식이 필요합니다 …)

이 방법들은 무엇을하지 않습니까?

IQueryable 기능 복원

이제 중요한 경고입니다. 당신이 할 때

context.Observations.AsEnumerable()
                    .AsQueryable()

로 표시된 소스 객체로 끝납니다 IQueryable. (두 방법 모두 캐스트하고 변환하지 않기 때문에).

하지만 할 때

context.Observations.AsEnumerable().Select(x => x)
                    .AsQueryable()

결과는 어떻습니까?

는을 Select생성합니다 WhereSelectEnumerableIterator. 이것은 그 구현 내부 닷넷 클래스 IEnumerable, 없습니다IQueryable . 따라서 다른 유형으로의 변환이 수행되었으며AsQueryable 소스는 더 이상 원래 소스를 리턴 할 수 없습니다.

이것의 의미는 사용하는 AsQueryable것이 특정 기능을 가진 쿼리 공급자 를 열거 형 으로 마술처럼 주입하는 방법아니라는 것 입니다. 당신이한다고 가정

var query = context.Observations.Select(o => o.Id)
                   .AsEnumerable().Select(x => x.ToString())
                   .AsQueryable()
                   .Where(...)

where 조건은 SQL로 변환되지 않습니다. AsEnumerable()LINQ 문 다음에 엔터티 프레임 워크 쿼리 공급자와의 연결이 결정됩니다.

예를 들어 사람들 Include이 호출하여 기능을 컬렉션에 ‘주입’하려는 질문을 보았 기 때문에 의도적 으로이 예제를 보여줍니다 AsQueryable. 컴파일하고 실행하지만 기본 개체에Include 더 이상 구현 수행하지 않습니다.

실행

AsQueryableAsEnumerable실행 (또는하지 않는 열거 소스 객체). 유형이나 표현 만 변경합니다. 관련된 인터페이스 IQueryableIEnumerable는 “열거되는 열거”에 지나지 않습니다. 예를 들어 위에서 언급 한 것처럼을 호출하여 강제로 실행되기 전에는 실행되지 않습니다 ToList().

즉 , 객체 IEnumerable를 호출 하여 얻은 것을 실행하면 기본을 실행합니다 . 의 후속 실행 은 다시을 실행합니다 . 매우 비쌀 수 있습니다.AsEnumerableIQueryableIQueryableIEnumerableIQueryable

특정 구현

지금까지 이것은 Queryable.AsQueryableEnumerable.AsEnumerable확장 방법에 관한 것입니다. 물론 누구나 같은 이름 (및 함수)으로 인스턴스 메소드 또는 확장 메소드를 작성할 수 있습니다.

실제로 특정 AsEnumerable확장 방법 의 일반적인 예 는 DataTableExtensions.AsEnumerable입니다. 또는을 DataTable구현하지 않으므로 일반 확장 방법이 적용되지 않습니다.IQueryableIEnumerable


답변

ToList ()

  • 즉시 쿼리를 실행

AsEnumerable ()

  • 게으른 (나중에 쿼리 실행)
  • 매개 변수 : Func<TSource, bool>
  • 모든 레코드를 응용 프로그램 메모리에 로드 한 다음 처리 / 필터링하십시오. (예 : Where / Take / Skip, table1에서 메모리로 *를 선택한 다음 첫 번째 X 요소를 선택합니다) (이 경우 수행 한 작업 : Linq-to-SQL + Linq-to-Object)

AsQueryable ()

  • 게으른 (나중에 쿼리 실행)
  • 매개 변수 : Expression<Func<TSource, bool>>
  • Expression을 T-SQL (특정 공급자 사용)로 변환하고 원격으로 쿼리하고 결과를 응용 프로그램 메모리에로드합니다.
  • Entity Framework의 DbSet도 효율적인 쿼리를 얻기 위해 IQueryable을 상속합니다.
  • 모든 레코드를로드하지 마십시오 (예 : Take (5) 인 경우 백그라운드에서 상위 5 * SQL 선택을 생성 함). 이는이 유형이 SQL 데이터베이스에 더 친숙 함을 의미하므로이 유형이 일반적으로 더 높은 성능을 가지며 데이터베이스를 처리 할 때 권장됩니다.
  • 따라서 AsQueryable()일반적으로 AsEnumerable()Linq의 모든 위치 조건을 포함하여 처음에 T-SQL을 생성 할 때보 다 훨씬 빠르게 작동 합니다.

답변

ToList ()는 메모리의 모든 것이므로 작업하게됩니다. 따라서 ToList (). where (일부 필터 적용)가 로컬로 실행됩니다. AsQueryable ()은 모든 것을 원격으로 실행합니다. 즉, 필터가 적용되도록 데이터베이스에 전송됩니다. Queryable은 실행할 때까지 아무것도하지 않습니다. 그러나 ToList는 즉시 실행됩니다.

또한이 답변을보십시오 왜 List () 대신 AsQueryable ()을 사용합니까? .

편집 : 또한 귀하의 경우 ToList ()를 수행하면 모든 후속 작업은 AsQueryable ()을 포함하여 로컬입니다. 로컬에서 실행을 시작하면 원격으로 전환 할 수 없습니다. 이것이 좀 더 명확 해지기를 바랍니다.


답변

아래 코드에서 성능이 저하되었습니다.

void DoSomething<T>(IEnumerable<T> objects){
    var single = objects.First(); //load everything into memory before .First()
    ...
}

로 고정

void DoSomething<T>(IEnumerable<T> objects){
    T single;
    if (objects is IQueryable<T>)
        single = objects.AsQueryable().First(); // SELECT TOP (1) ... is used
    else
        single = objects.First();

}

IQueryable의 경우 가능하면 IQueryable을 유지하고 IEnumerable처럼 사용하지 마십시오.

업데이트 . Gert Arnold 덕분에 한 식으로 더 단순화 될 수 있습니다 .

T single =  objects is IQueryable<T> q?
                    q.First():
                    objects.First();


답변