web-dev-qa-db-ja.com

IQueryable、List、IEnumeratorの違いは?

IQueryable、List、IEnumeratorの違いは何ですか、それぞれをいつ使用する必要がありますか?

たとえば、Linq to SQLを使用するときは、次のようにします。

public List<User> GetUsers()
{
   return db.User.where(/* some query here */).ToList();
}

今、私は代わりにIQueryableを使用すべきかどうか疑問に思っています。リスト上でそれを使用することの利点がわかりません。

61
chobo2

_IQueryable<T>_は、クエリプロバイダー(たとえば、LINQ to SQLやEntity FrameworkなどのORM)がクエリに含まれる式を使用してリクエストを別の形式に変換できるようにすることを目的としています。つまり、LINQ-to-SQLは、使用しているエンティティのプロパティと比較を行い、実際にSQLステートメントを作成して、同等の要求を(できれば)表現します。

_IEnumerable<T>_は_IQueryable<T>_より汎用的です(ただし、_IQueryable<T>_のすべてのインスタンスは_IEnumerable<T>_を実装します)。シーケンスのみを定義します。ただし、Enumerableクラス内で使用可能な拡張メソッドがあり、そのインターフェイスでいくつかのクエリタイプ演算子を定義し、通常のコードを使用してこれらの条件を評価します。

_List<T>_は単なる出力形式であり、_IEnumerable<T>_を実装しますが、クエリとは直接関係しません。

言い換えると、_IQueryable<T>_を使用している場合、他の何かに変換されるexpressionを定義していることになります。あなたがコードを書いているとしても、そのコードはexecutedを取得することはなく、inspectedを取得するだけで、実際のSQLクエリのような何かに変わります。このため、これらの式では特定のもののみが有効です。たとえば、LINQ-to-SQLは呼び出しをSQLステートメントに変換する方法を知らないため、これらの式内から定義する通常の関数を呼び出すことはできません。これらの制限のほとんどは、残念ながら実行時にのみ評価されます。

クエリに_IEnumerable<T>_を使用する場合、LINQ-to-Objectsを使用します。つまり、クエリの評価または結果の変換に使用される実際のコードを記述しているため、一般に制限はありません。あなたができることについて。これらの式内から他の関数​​を自由に呼び出すことができます。

LINQ to SQLを使用

上記の区別と密接に関連して、これが実際にどのように機能するかを念頭に置くことも重要です。 LINQ to SQLでデータコンテキストクラスに対してクエリを作成すると、_IQueryable<T>_が生成されます。何をするにしても_IQueryable<T>_自体に対してはSQLに変換されるため、フィルタリングと変換はサーバー上で行われます。これに対して行うことは何でも_IEnumerable<T>_として、アプリケーションレベルで行われます。これは望ましい場合があります(たとえば、クライアント側のコードを使用する必要がある場合)が、多くの場合、これは意図的ではありません。

たとえば、Customersテーブルを表すCustomerプロパティを持つコンテキストがあり、各顧客にCustomerId列がある場合、このクエリを実行する2つの方法を見てみましょう。

_var query = (from c in db.Customers where c.CustomerId == 5 select c).First();
_

これにより、Customerが5に等しいCustomerIdレコードをデータベースに照会するSQLが生成されます。

_select CustomerId, FirstName, LastName from Customer where CustomerId = 5
_

ここで、AsEnumerable()拡張メソッドを使用してCustomersを_IEnumerable<Customer>_に変換するとどうなりますか?

_var query = (from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c).First();
_

この単純な変更は重大な結果をもたらします。 Customersを_IEnumerable<Customer>_に変換しているため、これによりテーブル全体が返され、クライアント側でフィルタリングされます(厳密に言えば、テーブル内のすべての行が返されます条件に合うものに遭遇するまで、しかしポイントは同じです)。

ToList()

これまで、IQueryableIEnumerableについてのみ説明してきました。これは、それらが類似した補完的なインターフェースであるためです。どちらの場合も、query;を定義しています。つまり、データを見つけるためにwhereを定義し、適用するフィルターをwhatに、そして返すデータにwhatを定義しています。これらは両方ともクエリです

_query = from c in db.Customers where c.CustomerId == 5 select c;
query = from c in db.Customers.AsEnumerable() where c.CustomerId == 5 select c;
_

前に説明したように、最初のクエリはIQueryableを使用し、2番目のクエリはIEnumerableを使用しています。ただし、どちらの場合も、これは単なるqueryです。クエリを定義しても、実際にはデータソースに対して何も実行されません。クエリは、コードがリストの繰り返しを開始すると実際に実行されます。これは複数の方法で発生します。 foreachループ、ToList()などの呼び出し.

クエリが実行されますthe firstおよびevery反復されます。 queryToList()を2回呼び出すと、完全に別個のオブジェクトを持つ2つのリストになります。それらには同じデータが含まれている場合がありますが、異なる参照になります。

コメント後に編集

私はただ、物事がクライアント側で行われるときと、それらがサーバー側で行われるときの区別について明確にしたいだけです。 _IQueryable<T>_を_IEnumerable<T>_として参照している場合、onlyクエリの完了afterは、_IEnumerable<T>_がクライアントに実行されます-側。たとえば、次のテーブルとLINQ-to-SQLコンテキストがあるとします。

_Customer
-----------
CustomerId
FirstName
LastName
_

まず、FirstNameに基づいてクエリを作成します。これにより、_IQueryable<Customer>_が作成されます。

_var query = from c in db.Customers where c.FirstName.StartsWith("Ad") select c;
_

ここで、そのクエリを_IEnumerable<Customer>_を取り、LastNameに基づいていくつかのフィルタリングを行う関数に渡します。

_public void DoStuff(IEnumerable<Customer> customers)
{
    foreach(var cust in from c in customers where c.LastName.StartsWith("Ro"))
    {
        Console.WriteLine(cust.CustomerId);
    }
}
_

ここで2番目のクエリを実行しましたが、_IEnumerable<Customer>_で実行されています。ここで何が起こるかは、最初のクエリが評価され、次のSQLが実行されることです。

_select CustomerId, FirstName, LastName from Customer where FirstName like 'Ad%'
_

したがって、FirstNameが_"Ad"_で始まるすべてのユーザーを戻すことにします。 LastNameについてはここに何もないことに注意してください。これは、クライアント側で除外されているためです。

これらの結果が返されると、プログラムは結果を反復処理し、LastNameが_"Ro"_で始まるレコードのみを配信します。これの欠点は、データを戻したことです。つまり、LastNamedoes n'tが_"Ro"_-- that couldで始まるすべての行です。 =サーバー上で除外されています。

107
Adam Robinson

_IQueryable<T>_:データベースアクセスを抽象化し、クエリの遅延評価をサポートします
_List<T>_:エントリのコレクション。遅延評価のサポートなし
_IEnumerator<T>_:繰り返し機能と_IEnumerable<T>_(_IQueryable<T>_と_List<T>_の両方)

そのコードの問題は非常に単純です-呼び出されたときに常にクエリを実行します。代わりにdb.User.Where(...)(_IQueryable<T>_)を返す場合、実際に必要になるまで(反復されるまで)クエリの評価を保持します。また、そのメソッドのユーザーがさらに述語を指定する必要がある場合、それらもデータベースで実行されるため、より高速になります。

8
Femaref

エンティティの強く型付けされたコレクションが必要な場合は、iListまたはList<item>を使用します。

ダムデータをオブジェクトのコレクションとして取得する場合は、IqueryableIenumuratorを使用します。ルーズタイプのコレクションとして返され、制限は適用されません。

リストラップを使用し、結果セットを厳密に型のコレクションにキャストするため、List<type>を使用します。

また、リストを使用すると、レイヤーを配列、Ienumurator、またはクエリ可能として追加、ソート、変換することができます。

0
Mahmoud Sayed