c# - 複数 - IEnumerable<T>とIQueryable<T>を返す




linq メソッド (10)

IQueryable<T>IEnumerable<T>返す違いは何ですか?

IQueryable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

IEnumerable<Customer> custs = from c in db.Customers
where c.City == "<City>"
select c;

両方とも遅延実行され、どちらが優先されるべきなのか?


"IEnumerable"と "IQueryable"の主な違いは、フィルタロジックの実行場所です。 1つはクライアント側(メモリ内)で実行され、もう1つはデータベース上で実行されます。

たとえば、データベース内のあるユーザーのレコードが10,000件あり、アクティブなユーザーが900件であるとしましょう。この場合、「IEnumerable」を使用すると、10,000レコードすべてをメモリにロードし、 IsActiveフィルタを適用して、最終的に900人のアクティブユーザーを返します。

一方、同じケースでは "IQueryable"を使用すると、データベースにIsActiveフィルタが直接適用され、そこから直接900人のアクティブユーザーが返されます。

参照Link


LINQ to Entitiesを使用している間は、IEnumerableとIQueryableをいつ使用するかを理解することが重要です。 IEnumerableを使用すると、クエリはすぐに実行されます。 IQueryableを使用すると、アプリケーションが列挙を要求するまでクエリの実行が延期されます。 では、IQueryableとIEnumerableのどちらを使用するかを決定する際に考慮すべき点を見ていきましょう。 IQueryableを使用すると、データベースレベルでクエリを実行することなく、複数のステートメントを使用して複雑なLINQクエリを作成する機会が与えられます。 クエリは、最終的なLINQクエリが列挙されるときにのみ実行されます。


これまで多くのことが言われてきましたが、より技術的な方法で根に戻っています。

  1. IEnumerable は、列挙できるメモリ内のオブジェクトのコレクションです。つまり 、繰り返し実行できるようにするメモリ内シーケンスです( foreachループ内で簡単に使用できますが、 IEnumeratorのみで使用できます)。 彼らはそのままメモリに存在します。
  2. IQueryable は、ある時点何か別のものに翻訳されて最終的な結果を列挙する能力を持つ 式ツリーです。 私はこれがほとんどの人を混乱させるものだと思います。

彼らは明らかに異なる意味を持っています。

IQueryableは、LINQ集計関数(Sum、Countなど)やToList [配列、辞書など]のようにリリースAPIが呼び出されるとすぐに、基礎となるクエリプロバイダによって何かに変換される式ツリー(単にクエリ) ...]。 また、 IQueryableオブジェクトはIEnumerableIEnumerable<T>実装しているため、クエリを表す場合はそのクエリの結果を反復できます。 つまり、IQueryableはクエリである必要はありません。 正しい言葉は表現木です。

これらの式がどのように実行され、どのように振舞うかは、すべて、いわゆるクエリー・プロバイダー(私たちが考えることができる式実行プログラム)までです。

Entity Frameworkの世界(それは神秘的な基になるデータソースプロバイダまたはクエリプロバイダ)では、 IQueryable式がネイティブのT-SQLクエリに変換されT-SQLNhibernateはそれらと同様のことを行います。 たとえば、 LINQ:IQueryable Providerリンクの構築で説明されているコンセプトに従って独自の記述を作成できます。また、製品ストアプロバイダサービス用のカスタムクエリAPIを使用することもできます。

したがって、基本的にIQueryableオブジェクトは、明示的に解放してSQLやその他のものに書き直し、後続処理の実行チェーンを送信するように指示するまで、長い間構築されています。

あたかも実行を延期するかのように、 LINQ機能は、メモリ内の式ツリースキームを保持し、シーケンスに対して特定のAPI(同じCount、ToListなど)が呼び出されるたびにオンデマンドでのみ実行に送ります。

両方の適切な使用法は、特定のケースで直面しているタスクに大きく依存します。 よく知られているリポジトリパターンについては、私は個人的にIListを返すことを選択しました。つまり、リスト(インデクサなど)に対してIEnumerableです。 だから、リポジトリ内でのみIQueryableを使用し、コード内のどこでもIEnumerableを使用することは私のアドバイスです。 IQueryable が懸念の原理をIQueryable破壊するというテスト可能性の懸念については言及していません。 リポジトリ内から式を返すと、コンシューマは永続化レイヤを希望通りに再生できます。

混乱への少しの追加:)(コメント内の議論から))それらのどれも、実際のタイプではないので、メモリ内のオブジェクトではなく、タイプのマーカーです。 しかし、IEnumerablesをメモリ内のコレクションとして考えるのは意味があります(それはMSDNでもそうです)。一方、IQueryablesは式ツリーとして考えています。 要点は、IQueryableインターフェイスがIEnumerableインターフェイスを継承し、クエリを表す場合はそのクエリの結果を列挙できることです。 Enumerationを使用すると、IQueryableオブジェクトに関連付けられた式ツリーが実行されます。 したがって、実際には、メモリ内にオブジェクトを持たずにIEnumerableメンバーを実際に呼び出すことはできません。 それが空でなければ、あなたがしたら、そこに入るでしょう。 IQueryablesはクエリであり、データではありません。


これらは、 IQueryable<T>IEnumerable<T>間のいくつかの違いです。


はい、どちらも遅延実行を与えます。

違いは、 IQueryable<T>は、LINQ-to-SQL(LINQ.-to-anything)が動作するようにするインターフェイスだということです。 したがって、 IQueryable<T>でクエリをさらに絞り込むと、可能な場合はそのクエリがデータベースで実行されます。

IEnumerable<T>場合は、LINQからオブジェクトになります。つまり、元のクエリに一致するすべてのオブジェクトをデータベースからメモリにロードする必要があります。

コード内:

IQueryable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

そのコードは金の顧客だけを選択するためにSQLを実行します。 一方、次のコードでは、データベース内の元のクエリを実行し、メモリ内の金以外の顧客をフィルタリングします。

IEnumerable<Customer> custs = ...;
// Later on...
var goldCustomers = custs.Where(c => c.IsGold);

これは非常に重要な違いですIQueryable<T>すると、多くの場合、データベースから多すぎる行を返すことがIQueryable<T>ます。 もう一つの重要な例はページングですIQueryableTakeSkipを使用すると、要求された行数だけが取得されます。 IEnumerable<T>行うと、すべての行がメモリにロードされます。


はい、どちらも遅延実行を使用します。 SQL Serverプロファイラを使用して違いを説明しましょう。

次のコードを実行すると:

MarketDevEntities db = new MarketDevEntities();

IEnumerable<WebLog> first = db.WebLogs;
var second = first.Where(c => c.DurationSeconds > 10);
var third = second.Where(c => c.WebLogID > 100);
var result = third.Where(c => c.EmailAddress.Length > 11);

Console.Write(result.First().UserName);

SQL Serverプロファイラでは、次のコマンドが見つかりました。

"SELECT * FROM [dbo].[WebLog]"

約100万レコードのWebLogテーブルに対して、そのブロックを実行するにはおよそ90秒かかります。

したがって、すべてのテーブルレコードはオブジェクトとしてメモリにロードされ、各.Where()ではメモリ内のこれらのオブジェクトに対する別のフィルタになります。

上記の例(2行目)でIEnumerable代わりにIQueryableを使用すると、次のようになります。

SQL Serverプロファイラでは、次のコマンドが見つかりました。

"SELECT TOP 1 * FROM [dbo].[WebLog] WHERE [DurationSeconds] > 10 AND [WebLogID] > 100 AND LEN([EmailAddress]) > 11"

IQueryableを使用してこのコードブロックを実行するには、およそ4秒かかります。

IQueryableにはExpressionと呼ばれるプロパティがあり、この例ではresultを使用したときに作成されるツリー式(遅延実行と呼ばれる)が格納され、最後にこの式はデータベースエンジンで実行されるSQLクエリに変換されます。


一般的には、重要な問題が発生するまで、クエリの元の静的型を保持する必要があります。

このため、 IQueryable<>またはIEnumerable<>の代わりに変数を 'var'として定義することができ、タイプを変更していないことがわかります。

あなたがIQueryable<>使い始めると、それを変更する魅力的な理由があるまで、通常はIQueryable<>として保持したいと考えています。 この理由は、クエリプロセッサにできるだけ多くの情報を提供したいからです。 たとえば、10個の結果( Take(10)呼び出したことがある)を使用する場合は、SQL Serverでそのことを知り、クエリプランを最適化し、使用するデータのみを送信できるようにします。

型をIQueryable<>からIEnumerable<>に変更する魅力的な理由は、特定のオブジェクト内のIQueryable<>実装が非効率的に処理または処理できない拡張機能を呼び出している可能性があります。 その場合、タイプをIEnumerable<>IEnumerable<>型の変数に代入するか、 AsEnumerable拡張メソッドを使用するなど)にAsEnumerableして、あなたが呼び出す拡張関数がQueryableクラスの代わりにQueryableクラス。


一般的に言えば、私は次のことをお勧めします:

  • メソッドを使用する開発者が実行前に返すクエリを絞り込むには、 IQueryable<T>を返します。

  • 列挙するオブジェクトのセットを移送する場合は、 IEnumerable返します。

IQueryableが何であるかを想像してみましょう。データの「クエリー」(あなたが望むなら絞り込むことができます)。 IEnumerableは、列挙できるオブジェクトのセットです(すでに受信されているか作成されています)。


最初の2つの本当に良い答え(driis&Jacobによる)に加えて:

IEnumerableインターフェイスはSystem.Collections名前空間にあります。

IEnumerableオブジェクトはメモリ内の一連のデータを表し、このデータだけを前方に移動できます。 IEnumerableオブジェクトによって表されるクエリは、すぐに完全に実行されるため、アプリケーションはデータをすばやく受信します。

クエリが実行されると、IEnumerableはすべてのデータをロードし、フィルタリングが必要な場合は、フィルタリング自体がクライアント側で実行されます。

IQueryableインターフェイスは、System.Linq名前空間にあります。

IQueryableオブジェクトは、データベースへのリモートアクセスを提供し、データを最初から最後まで、または逆の順序でナビゲートできます。 クエリを作成するプロセスでは、返されるオブジェクトはIQueryableであり、クエリは最適化されています。 その結果、実行中に消費されるメモリが少なくて済み、ネットワーク帯域幅は少なくなりますが、同時に、IEnumerableオブジェクトを返すクエリよりも少し遅く処理することができます。

何を選ぶ?

返されるデータ全体が必要な場合は、最大速度を提供するIEnumerableを使用する方がよいでしょう。

返されたデータ全体が必要なわけではなく、フィルタリングされたデータだけが必要な場合は、IQueryableを使用する方がよいでしょう。


私は一見矛盾している応答(ほとんどIEnumerableを取り巻く)のためにいくつかのことを明確にしたいと思います。

(1) IQueryableIEnumerableインターフェイスを拡張します。 ( IEnumerableをエラーなく期待するものにIQueryableを送ることができます)。

(2) IQueryableIEnumerable LINQは、結果セットを反復処理するときに遅延ロードを試みます。 (インプリメンテーションは各タイプのインタフェース拡張メソッドで見ることができます)。

言い換えれば、 IEnumerablesは排他的に "インメモリ"ではありません。 IQueryablesはデータベース上で常に実行されるわけではありません。 IEnumerableは抽象データプロバイダを持たないため、物事をメモリに読み込む必要があります。 IQueryablesは抽象プロバイダ(LINQ-to-SQLなど)に依存しますが、これは.NETのメモリ内プロバイダでもあります。

サンプルユースケース

(a)EFコンテキストからレコードのリストをIQueryableとして取得する。 (レコードはメモリ内にありません。)

(b)モデルがIEnumerableであるビューにIQueryableを渡します。 (有効、 IQueryableIEnumerable拡張します。)

(c)データセットのレコード、子エンティティおよびプロパティに反復してアクセスする。 (例外を引き起こす可能性があります!)

考えられる問題

(1) IEnumerableは遅延読み込みを試み、データコンテキストは期限切れです。 プロバイダが使用できなくなったためにスローされる例外です。

(2)Entity Frameworkエンティティ・プロキシが有効(デフォルト)であり、期限切れのデータ・コンテキストを持つ関連(仮想)オブジェクトにアクセスしようとします。 (1)と同じです。

(3)複数のアクティブな結果セット(MARS)。 foreach( var record in resultSet )ブロックforeach( var record in resultSet )IEnumerableを反復処理していて同時にrecord.childEntity.childPropertyにアクセスしようとすると、データセットとリレーショナルエンティティの両方の遅延読み込みが原因でMARSが終了する可能性があります。 接続文字列で有効になっていない場合は例外が発生します。

溶液

  • 私は、接続文字列でMARSを有効にすることが信頼できないことが分かった。 あなたがよく理解され、明示的に望まれていない限り、MARSを避ける​​ことをお勧めします。

resultList = resultSet.ToList()を呼び出すことによってクエリを実行し、結果を格納します。これは、エンティティがメモリ内にあることを保証する最も簡単な方法のようです。

関連するエンティティにアクセスしている場合は、依然としてデータコンテキストが必要な場合があります。 どちらか、またはエンティティプロキシを無効にし、明示的にDbSet関連エンティティをIncludeことができます。







iqueryable