.ToList (), .AsEnumerable (), AsQueryable ()의 차이점은 무엇입니까?
LINQ to Entities와 LINQ to Objects의 차이점은 첫 번째 구현 IQueryable
과 두 번째 구현 IEnumerable
이며 내 질문 범위는 EF 5 내에 있습니다.
내 질문은이 세 가지 방법의 기술적 차이점은 무엇입니까? 나는 많은 상황에서 그들 모두가 작동하는 것을 본다. 또한 같은 조합을 사용하는 것을 볼 수 있습니다 .ToList().AsQueryable()
.
그 방법은 정확히 무엇을 의미합니까?
성능 문제 또는 다른 것을 사용하는 데 문제가 있습니까?
예를 들어 왜
.ToList().AsQueryable()
대신에 사용.AsQueryable()
합니까?
이것에 대해 할 말이 많습니다. 저에 집중하자 AsEnumerable
및 AsQueryable
및 언급 ToList()
길을 따라.
이 방법들은 무엇을 하는가?
AsEnumerable
및 AsQueryable
캐스트 또는 변환에 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>
용도는 무엇입니까?
AsEnumerable
는 IQueryable
구현이 LINQ에서 객체 (L2O) 로 전환하는 데 자주 사용됩니다 . 대부분 전자가 L2O의 기능을 지원하지 않기 때문입니다. 자세한 내용은 LINQ 엔터티에 AsEnumerable ()의 영향 은 무엇입니까?를 참조하십시오 . .
For example, in an Entity Framework query we can only use a restricted number of methods. So if, for example, we need to use one of our own methods in a query we would typically write something like
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => MySuperSmartMethod(x))
ToList
– which converts an IEnumerable<T>
to a List<T>
– is often used for this purpose as well. The advantage of using AsEnumerable
vs. ToList
is that AsEnumerable
does not execute the query. AsEnumerable
preserves deferred execution and does not build an often useless intermediate list.
On the other hand, when forced execution of a LINQ query is desired, ToList
can be a way to do that.
AsQueryable
can be used to make an enumerable collection accept expressions in LINQ statements. See here for more details: Do i really need use AsQueryable() on collection?.
Note on substance abuse!
AsEnumerable
works like a drug. It's a quick fix, but at a cost and it doesn't address the underlying problem.
In many Stack Overflow answers, I see people applying AsEnumerable
to fix just about any problem with unsupported methods in LINQ expressions. But the price isn't always clear. For instance, if you do this:
context.MyLongWideTable // A table with many records and columns
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate })
...everything is neatly translated into a SQL statement that filters (Where
) and projects (Select
). That is, both the length and the width, respectively, of the SQL result set are reduced.
Now suppose users only want to see the date part of CreateDate
. In Entity Framework you'll quickly discover that...
.Select(x => new { x.Name, x.CreateDate.Date })
...is not supported (at the time of writing). Ah, fortunately there's the AsEnumerable
fix:
context.MyLongWideTable.AsEnumerable()
.Where(x => x.Type == "type")
.Select(x => new { x.Name, x.CreateDate.Date })
Sure, it runs, probably. But it pulls the entire table into memory and then applies the filter and the projections. Well, most people are smart enough to do the Where
first:
context.MyLongWideTable
.Where(x => x.Type == "type").AsEnumerable()
.Select(x => new { x.Name, x.CreateDate.Date })
But still all columns are fetched first and the projection is done in memory.
The real fix is:
context.MyLongWideTable
.Where(x => x.Type == "type")
.Select(x => new { x.Name, DbFunctions.TruncateTime(x.CreateDate) })
(But that requires just a little bit more knowledge...)
What do these methods NOT do?
Now an important caveat. When you do
context.Observations.AsEnumerable()
.AsQueryable()
you will end up with the source object represented as IQueryable
. (Because both methods only cast and don't convert).
But when you do
context.Observations.AsEnumerable().Select(x => x)
.AsQueryable()
what will the result be?
The Select
produces a WhereSelectEnumerableIterator
. This is an internal .Net class that implements IEnumerable
, not IQueryable
. So a conversion to another type has taken place and the subsequent AsQueryable
can never return the original source anymore.
The implication of this is that using AsQueryable
is not a way to magically inject a query provider with its specific features into an enumerable. Suppose you do
var query = context.Observations.Select(o => o.Id)
.AsEnumerable().Select(x => x.ToString())
.AsQueryable()
.Where(...)
The where condition will never be translated into SQL. AsEnumerable()
followed by LINQ statements definitively cuts the connection with entity framework query provider.
I deliberately show this example because I've seen questions here where people for instance try to 'inject' Include
capabilities into a collection by calling AsQueryable
. It compiles and runs, but it does nothing because the underlying object does not have an Include
implementation anymore.
Specific Implementations
So far, this was only about the Queryable.AsQueryable
and Enumerable.AsEnumerable
extension methods. But of course anybody can write instance methods or extension methods with the same names (and functions).
In fact, a common example of a specific AsEnumerable
extension method is DataTableExtensions.AsEnumerable
. DataTable
does not implement IQueryable
or IEnumerable
, so the regular extension methods don't apply.
ToList()
- Execute the query immediately
AsEnumerable()
- lazy (execute the query later)
- Parameter:
Func<TSource, bool>
- Load EVERY record into application memory, and then handle/filter them. (e.g. Where/Take/Skip, it will select * from table1, into the memory, then select the first X elements) (In this case, what it did: Linq-to-SQL + Linq-to-Object)
AsQueryable()
- lazy (execute the query later)
- Parameter:
Expression<Func<TSource, bool>>
- Convert Expression into T-SQL (with the specific provider), query remotely and load result to your application memory.
- That’s why DbSet (in Entity Framework) also inherits IQueryable to get the efficient query.
- Do not load every record, e.g. if Take(5), it will generate select top 5 * SQL in the background. This means this type is more friendly to SQL Database, and that is why this type usually has higher performance and is recommended when dealing with a database.
- So
AsQueryable()
usually works much faster thanAsEnumerable()
as it generate T-SQL at first, which includes all your where conditions in your Linq.
ToList() will being everything in memory and then you will be working on it. so, ToList().where ( apply some filter ) is executed locally. AsQueryable() will execute everything remotely i.e. a filter on it is sent to the database for applying. Queryable doesn't do anything til you execute it. ToList, however executes immediately.
Also, look at this answer Why use AsQueryable() instead of List()?.
EDIT : Also, in your case once you do ToList() then every subsequent operation is local including AsQueryable(). You can't switch to remote once you start executing locally. Hope this makes it a little bit more clearer.
Encountered a bad performance on below code.
void DoSomething<T>(IEnumerable<T> objects){
var single = objects.First(); //load everything into memory before .First()
...
}
Fixed with
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();
}
For an IQueryable, stay in IQueryable when possible, try not be used like IEnumerable.
Update. It can be further simplified in one expression, thanks Gert Arnold.
T single = objects is IQueryable<T> q?
q.First():
objects.First();
'Programing' 카테고리의 다른 글
sed에서 환경 변수 대체 (0) | 2020.05.31 |
---|---|
Picasso를 사용하여 이미지를 전체 너비 및 고정 높이로 크기 조정 (0) | 2020.05.31 |
Angular 2 다른 모듈의 컴포넌트 사용 (0) | 2020.05.30 |
Express에서 멋진 형식의 HTML을 출력하려면 어떻게해야합니까? (0) | 2020.05.30 |
문자열을 n 문자 세그먼트로 나누려면 어떻게해야합니까? (0) | 2020.05.30 |