LINQ to EntitiesとLINQ to Objectsのいくつかの違いを知っています。最初の実装ではIQueryable
が実装され、2番目の実装ではIEnumerable
が実装されます。
私の質問は、これら3つの方法の技術的な違いは何ですか?多くの状況で、それらすべてが機能することがわかります。 .ToList().AsQueryable()
のようにそれらの組み合わせを使用することもわかります。
これらの方法は正確に何を意味するのでしょうか?
パフォーマンス上の問題や、一方を他方に使用することにつながる何かがありますか?
たとえば、.ToList().AsQueryable()
の代わりに.AsQueryable()
を使用するのはなぜですか?
これについて多くのことを言います。 AsEnumerable
とAsQueryable
に焦点を当てて、途中で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))
ToList
– IEnumerable<T>
をList<T>
に変換します–この目的にもよく使用されます。 AsEnumerable
とToList
を使用する利点は、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 })
...すべてがfilters(Where
)およびprojects(Select
)の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()
結果はどうなりますか?
Select
はWhereSelectEnumerableIterator
を生成します。これは、IEnumerable
、not 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
です。 DataTable
はIQueryable
またはIEnumerable
を実装しないため、通常の拡張メソッドは適用されません。
ToList()
AsEnumerable()
Func<TSource, bool>
AsQueryable()
Expression<Func<TSource, bool>>
AsQueryable()
はAsEnumerable()
よりもはるかに高速に動作します。これには、Linqのすべてのwhere条件が含まれます。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 のおかげで、1つの式でさらに簡略化できます。
T single = objects is IQueryable<T> q?
q.First():
objects.First();