1. ホーム
  2. entity-framework

[解決済み】.ToList(), .AsEnumerable(), AsQueryable()の違いは何ですか?

2022-04-12 16:49:29

質問

LINQ to EntitiesとLINQ to Objectsの違いについて知っています。 IQueryable を実装しており、後者は IEnumerable で、私の質問範囲はEF 5の中です。

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

  1. その方法とは、具体的にどのようなものなのでしょうか?

  2. パフォーマンス上の問題や、どちらかを使用した方が良いというようなことはありますか?

  3. なぜ、例えば、1を使うのか。 .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-to-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 を表します。 IEnumerable<T> コレクションを IQueryable<T> データソース.quot; (MSDN)。

用途は?

AsEnumerable は頻繁に使用され、任意の IQueryable の実装をLINQ to objects (L2O) に置き換えたもので、前者はL2Oが持つ関数をサポートしていないことが主な理由です。詳しくは LINQ エンティティに対する AsEnumerable() の効果は何ですか? .

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

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

ToList - を変換するものです。 IEnumerable<T>List<T> - は、この目的にもよく使われます。を使うことの利点は AsEnumerable vs. 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文にきちんと変換されます。 フィルター ( 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 . これは、.Net の内部クラスで 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 を呼び出して得られた AsEnumerable の上に IQueryable オブジェクトを実行すると、その下にある IQueryable . その後に実行される IEnumerable が再び実行されます。 IQueryable . これは非常に高くつくかもしれません。

具体的な実装方法

ここまでは Queryable.AsQueryable Enumerable.AsEnumerable 拡張メソッドです。しかし、もちろん、誰でも同じ名前(関数)のインスタンスメソッドや拡張メソッドを書くことができます。

実際、一般的な例として、特定の AsEnumerable 拡張メソッドは DataTableExtensions.AsEnumerable . DataTable は実装されていません。 IQueryable または IEnumerable ということで、通常の拡張メソッドは適用されません。