これについては言うことがたくさんあります。私はに焦点を当ててみよう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);
}
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)。
使用は何ですか?
AsEnumerable
IQueryable
実装から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
薬物のように機能します。これは迅速な修正ですが、コストがかかり、根本的な問題に対処しません。
多くのスタックオーバーフローの回答で、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
ます。これは、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
実装しないため、通常の拡張メソッドは適用されません。IQueryable
IEnumerable