web-dev-qa-db-ja.com

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

LINQ to EntitiesとLINQ to Objectsのいくつかの違いを知っています。最初の実装ではIQueryableが実装され、2番目の実装ではIEnumerableが実装されます。

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

  1. これらの方法は正確に何を意味するのでしょうか?

  2. パフォーマンス上の問題や、一方を他方に使用することにつながる何かがありますか?

  3. たとえば、.ToList().AsQueryable()の代わりに.AsQueryable()を使用するのはなぜですか?

160
Amin Saqi

これについて多くのことを言います。 AsEnumerableAsQueryableに焦点を当てて、途中でToList()に言及しましょう。

これらの方法は何をしますか?

AsEnumerableおよびAsQueryableは、それぞれIEnumerableまたはIQueryableにキャストまたは変換します。 キャストまたは変換と言います。理由は次のとおりです。

  • ソースオブジェクトが既にターゲットインターフェイスを実装している場合、ソースオブジェクト自体は返されますが、ターゲットインターフェイスにcastが返されます。つまり、型は変更されませんが、コンパイル時の型は変更されます。

  • ソースオブジェクトがターゲットインターフェイスを実装していない場合、ソースオブジェクトはconvertedで、ターゲットインターフェイスを実装するオブジェクトになります。したがって、型とコンパイル時の型の両方が変更されます。

これをいくつかの例で示しましょう。コンパイル時の型とオブジェクトの実際の型を報告するこの小さなメソッドを持っています( courtesy Jon Skeet ):

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

IQueryableを実装する任意のlinq-to-sql Table<T>を試してみましょう。

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)。

使用は何ですか?

AsEnumerableは、IQueryable実装からLINQ to object(L2O)への切り替えに頻繁に使用されます。これは主に、前者がL2Oの機能をサポートしていないためです。詳細については、 LINQエンティティに対するAsEnumerable()の効果は何ですか? を参照してください。

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

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

ToListIEnumerable<T>List<T>に変換します–この目的にもよく使用されます。 AsEnumerableToListを使用する利点は、AsEnumerableがクエリを実行しないことです。 AsEnumerableは、遅延実行を保持し、しばしば役に立たない中間リストを構築しません。

一方、LINQクエリの強制実行が必要な場合は、ToListを使用することができます。

AsQueryableを使用して、列挙可能なコレクションがLINQステートメントの式を受け入れるようにすることができます。詳細については、こちらを参照してください: コレクションでAsQueryable()を本当に使用する必要がありますか?

薬物乱用に関する注意!

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

多くのStack Overflowの回答では、AsEnumerableを適用して、LINQ式でサポートされていないメソッドに関する問題をほぼすべて修正している人がいます。しかし、価格は必ずしも明確ではありません。たとえば、これを行う場合:

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

...すべてがfiltersWhere)およびprojectsSelect)のSQLステートメントにきちんと変換されます。つまり、それぞれ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) })

(しかし、それはもう少し知識が必要です...)

これらの方法は何をしないのですか?

今重要な警告。するとき

context.Observations.AsEnumerable()
                    .AsQueryable()

最終的に、IQueryableとして表されるソースオブジェクトになります。 (両方のメソッドがキャストするだけで、変換しないため)。

しかし、あなたがするとき

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

結果はどうなりますか?

SelectWhereSelectEnumerableIteratorを生成します。これは、IEnumerablenot 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実装が含まれていないため、何も実行されません。

特定の実装

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

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

318
Gert Arnold

ToList()

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

AsEnumerable()

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

AsQueryable()

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

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

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

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

12
ashutosh raina

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

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();
2
Rm558