.ToList()、. AsEnumerable()、AsQueryable()の違いは何ですか?


182

LINQ to EntitiesとLINQ to Objectsの最初の実装IQueryableと2番目の実装の違いがいくつかわかってIEnumerableおり、質問のスコープはEF 5内にあります。

私の質問は、これらの3つの方法の技術的な違いは何ですか?私は多くの状況でそれらすべてが機能することを確認します。のようにそれらを組み合わせて使用​​することもわかり.ToList().AsQueryable()ます。

  1. これらの方法は正確にはどういう意味ですか?

  2. パフォーマンスの問題など、どちらか一方をもう一方に使用することにつながるものはありますか?

  3. .ToList().AsQueryable()代わりに、例えば、なぜ代わりに使うの.AsQueryable()でしょうか?


回答:


354

これについては言うことがたくさんあります。私はに焦点を当ててみようAsEnumerableAsQueryableして言及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);
}

Table<T>実装する任意のlinq-to-sqlを試してみましょう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。これは「IEnumerable<T>コレクションをIQueryable<T>データソースとして表します」。(MSDN)。

使用は何ですか?

AsEnumerableIQueryable実装からLINQ toオブジェクト(L2O)への切り替えによく使用されます。これは主に、前者はL2Oが持つ機能をサポートしていないためです。詳細については、LINQエンティティに対するAsEnumerable()の影響を参照してください

たとえば、Entity Frameworkクエリでは、限られた数のメソッドしか使用できません。したがって、たとえば、クエリで独自のメソッドの1つを使用する必要がある場合、通常は次のように記述します。

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は、これを行う方法の1つです。

AsQueryable列挙可能なコレクションにLINQステートメントの式を許可するために使用できます。詳細については、ここを参照してください:コレクションでAsQueryable()を使用する必要がありますか?

薬物乱用に注意!

AsEnumerable薬物のように機能します。これは迅速な修正ですが、コストがかかり、根本的な問題に対処しません。

多くのスタックオーバーフローの回答で、AsEnumerableLINQ式でサポートされていないメソッドに関する問題の修正を申請している人がいます。しかし、価格は必ずしも明確ではありません。たとえば、次のようにした場合:

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

...すべてがきちんとSQLステートメントに変換され、フィルターWhere)とプロジェクトSelect)が行われます。つまり、SQL結果セットの長さと幅の両方がそれぞれ削減されます。

ここで、ユーザーがの日付部分のみを表示したいとします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実装する内部.Netクラスです。そのため、別のタイプへの変換が行われ、その後AsQueryableは元のソースを返すことができなくなります。

これの意味するところは、特定の機能を持つクエリプロバイダーを列挙型に魔法のように注入する方法でAsQueryableないということです。あなたがやるとします

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

where条件はSQLに変換されません。AsEnumerable()それに続くLINQステートメントは、エンティティフレームワーククエリプロバイダーとの接続を確実に切断します。

ここでは、たとえばInclude、を呼び出してコレクションに機能を「挿入」しようとする質問を見てきたので、この例を意図的に示しますAsQueryable。コンパイルして実行しますが、基礎となるオブジェクトにInclude実装がないため、何もしません。

実行する

両方AsQueryable及びAsEnumerable実行(又はない列挙ソースオブジェクト)。タイプまたは表現のみを変更します。関連するインターフェースIQueryableとの両方IEnumerableは、「発生を待機している列挙型」にすぎません。たとえば、前述のように、を呼び出すことによって強制的に実行される前に実行されませんToList()

つまり、オブジェクトをIEnumerable呼び出しAsEnumerableて取得したIQueryableを実行すると、基になるが実行されIQueryableます。続いてを実行すると、IEnumerable再びが実行されIQueryableます。これは非常に高価になる可能性があります。

特定の実装

これまでのところ、これはQueryable.AsQueryableおよびEnumerable.AsEnumerable拡張メソッドのみに関するものでした。しかしもちろん、誰でも同じ名前(および関数)のインスタンスメソッドまたは拡張メソッドを作成できます。

実際、特定のAsEnumerable拡張メソッドの一般的な例はDataTableExtensions.AsEnumerableです。またはをDataTable実装しないため、通常の拡張メソッドは適用されません。IQueryableIEnumerable


回答ありがとうございます。OP- 3の 3番目の質問に対する回答を共有していただけますか。たとえば、.AsQueryable()ではなく、.ToList()。AsQueryable()を使用するのはなぜですか?
kgzdev

@ikram役に立つと思うところは何もありません。私が説明したように、申請AsQueryable()はしばしば誤解に基づいています。しかし、しばらくの間、頭の後ろでくつろいで、その質問についてさらにカバーできるかどうか見てみましょう。
Gert Arnold

1
すばらしい答えです。IQueryableでAsEnumerable()を呼び出して取得したIEnumerableが複数回列挙された場合、どうなるか正確に教えていただけませんか?クエリは複数回実行されますか、それともデータベースからメモリに既にロードされているデータは再利用されますか?
antoninod

@antoninodいい考え。できました。
ガートアーノルド

46

ToList()

  • クエリをすぐに実行する

AsEnumerable()

  • レイジー(後でクエリを実行)
  • パラメータ: Func<TSource, bool>
  • ロードEVERYのアプリケーションのメモリに記録し、その後、フィルタ/それらを扱います。(たとえば、Where / Take / Skip、それはtable1から*をメモリに選択し、最初のX要素を選択します)(この場合、それは何をしたか:Linq-to-SQL + Linq-to-Object)

AsQueryable()

  • レイジー(後でクエリを実行)
  • パラメータ: Expression<Func<TSource, bool>>
  • 式をT-SQL(特定のプロバイダーを使用)に変換し、リモートでクエリを実行して、結果をアプリケーションメモリに読み込みます。
  • そのため、DbSet(Entity Framework内)もIQueryableを継承して、効率的なクエリを取得します。
  • すべてのレコードをロードしないでください。たとえば、Take(5)の場合、選択トップ5 * SQLがバックグラウンドで生成されます。これは、このタイプがSQLデータベースによりフレンドリーであることを意味します。そのため、このタイプは通常、パフォーマンスが高く、データベースを扱うときに推奨されます。
  • したがってAsQueryable()、通常AsEnumerable()は最初にT-SQLを生成するよりもはるかに高速に動作します。これには、Linqのすべてのwhere条件が含まれます。

14

ToList()はすべてメモリ内にあり、それから作業します。そのため、ToList()。where(フィルタを適用)はローカルで実行されます。AsQueryable()はすべてをリモートで実行します。つまり、フィルタがデータベースに送信されて適用されます。Queryableは、実行するまで何もしません。ToList、ただしすぐに実行されます。

また、この回答を見てください。なぜList()の代わりにAsQueryable()を使用するのですか?

編集:また、あなたの場合、ToList()を実行すると、その後のすべての操作はAsQueryable()を含めてローカルになります。ローカルで実行を開始すると、リモートに切り替えることはできません。これにより、もう少し明確になることを願っています。


2
「AsQueryable()はすべてをリモートで実行します」列挙型がすでにクエリ可能な場合のみ。そうでなければ、それは可能ではない、すべてがまだローカルで実行されます。質問には「.... ToList()。AsQueryable()」が含まれているため、IMOという回答でいくつかの説明を使用できます。

2

以下のコードでパフォーマンスが低下しました。

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のおかげで、1つの式でさらに簡略化できます。

T single =  objects is IQueryable<T> q? 
                    q.First(): 
                    objects.First();
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.