web-dev-qa-db-ja.com

LINQ to SQLの.AsEnumerable()を理解する

次のLINQ to SQLクエリがある場合:

_var test = from i in Imports
           where i.IsActive
           select i;
_

解釈されるSQLステートメントは次のとおりです。

_SELECT [t0].[id] AS [Id] .... FROM [Imports] AS [t0] WHERE [t0].[isActive] = 1
_

SelectでSQLに変換できないアクションを実行したいとします。これを達成するための従来の方法は、AsEnumerable()を実行し、それを実行可能なオブジェクトに変換することであるという私の理解です。

この更新されたコードを考えると:

_var test = from i in Imports.AsEnumerable()
           where i.IsActive
           select new 
           { 
               // Make some method call 
           };
_

そして更新されたSQL:

_SELECT [t0].[id] AS [Id] ... FROM [Imports] AS [t0] 
_

実行されたSQLステートメントにwhere句がないことに注意してください。

これは、「インポート」テーブル全体がメモリにキャッシュされることを意味しますか?テーブルに大量のレコードが含まれている場合、このパフォーマンスはまったく低下しますか?

ここで舞台裏で実際に何が起こっているのかを理解してください。

44
Mike Fielden

AsEnumerable の理由は

AsEnumerable(TSource)(IEnumerable(TSource))は、シーケンスがIEnumerable(T)を実装するが、利用可能なパブリッククエリメソッドの異なるセットがある場合、クエリ実装から選択するために使用できます。

したがって、以前にWhereメソッドを呼び出していたときは、IEnumerable.Whereとは異なるWhereメソッドを呼び出していました。 WhereステートメントはLINQがSQLに変換するためのものでした。新しいWhereIEnumerableを取り、それを列挙して一致するアイテムを生成するIEnumerableのものです。これが、異なるSQLが生成されるのを見る理由です。コードの2番目のバージョンでWhere拡張が適用される前に、テーブルはデータベースから完全に取得されます。これにより、テーブル全体がメモリ内に存在する必要があるか、さらに悪いことにテーブル全体がサーバー間を移動する必要があるため、深刻なボトルネックが発生する可能性があります。 SQLサーバーがWhereを実行できるようにし、最善を尽くします。

34

列挙が列挙される時点で、データベースが照会され、結果セット全体が取得されます。

部分的な解決策が道になります。検討する

var res = (
    from result in SomeSource
    where DatabaseConvertableCriterion(result)
    && NonDatabaseConvertableCriterion(result)
    select new {result.A, result.B}
);

また、NonDatabaseConvertableCriterionには結果のフィールドCが必要だとしましょう。 NonDatabaseConvertableCriterionはその名前が示すとおりのことを行うため、列挙として実行する必要があります。ただし、以下を考慮してください。

var partWay =
(
    from result in SomeSource
    where DatabaseConvertableCriterion(result)
    select new {result.A, result.B, result.C}
);
var res =
(
    from result in partWay.AsEnumerable()
    where NonDatabaseConvertableCriterion select new {result.A, result.B}
);

この場合、resが列挙、照会、または使用されると、可能な限り多くの作業がデータベースに渡され、ジョブを続行するのに十分な量が返されます。すべての作業をデータベースに送信できるように書き換えることは実際には不可能であると仮定すると、これは適切な妥協案となる可能性があります。

6
Jon Hanna

AsEnumerableには3つの実装があります。

_DataTableExtensions.AsEnumerable_

DataTableを拡張してIEnumerableインターフェイスを与えるため、DataTableに対してLinqを使用できます。

_Enumerable.AsEnumerable<TSource>_ および _ParallelEnumerable.AsEnumerable<TSource>_

AsEnumerable<TSource>(IEnumerable<TSource>)メソッドは、ソースのコンパイル時の型を_IEnumerable<T>_を実装する型から_IEnumerable<T>_自体に変更する以外には効果がありません。

AsEnumerable<TSource>(IEnumerable<TSource>)は、シーケンスが_IEnumerable<T>_を実装するが、利用可能なパブリッククエリメソッドの異なるセットがある場合、クエリ実装から選択するために使用できます。たとえば、TableWhereSelectなどの_IEnumerable<T>_を実装し、独自のメソッドを持つ汎用クラスSelectManyWhereを呼び出すと、Whereのpublic Tableメソッドが呼び出されます。データベーステーブルを表すTable型には、式ツリーとして述語引数を取り、リモート実行のためにツリーをSQLに変換するWhereメソッドを含めることができます。たとえば、述語がローカルメソッドを呼び出すためにリモート実行が望ましくない場合は、_AsEnumerable<TSource>_メソッドを使用してカスタムメソッドを非表示にし、代わりに標準クエリ演算子を使用可能にすることができます。

つまり、

私が持っている場合

_IQueryable<X> sequence = ...;
_

entity FrameworkのようなLinqProviderから、そして私は、

_sequence.Where(x => SomeUnusualPredicate(x));
_

そのクエリが作成され、サーバー上で実行されます。 EntityFrameworkはSomeUnusualPredicateをSQLに変換する方法を知らないため、これは実行時に失敗します。

代わりにLinq to Objectsを使用してステートメントを実行したい場合は、

_sequence.AsEnumerable().Where(x => SomeUnusualPredicate(x));
_

サーバーはすべてのデータを返し、Linqからオブジェクトへの_Enumerable.Where_がクエリプロバイダーの実装の代わりに使用されます。

Entity FrameworkがSomeUnusualPredicateの解釈方法を知らなくても構いません。私の関数は直接使用されます。 (ただし、これはすべての行がサーバーから返されるため、非効率的なアプローチになる可能性があります。)

5
Jodrell

AsEnumerableはコンパイラに使用する拡張メソッド(この場合はIQueryableの代わりにIEnumerableに定義されているメソッド)を伝えるだけだと思います。クエリの実行は、ToArrayを呼び出すか、列挙するまで延期されます。

2
Razvi