web-dev-qa-db-ja.com

IEnumerable <T>とIQueryable <T>を返す

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;

どちらも実行が延期されるのでしょうか。また、どちらが優先されるのでしょうか。

990

はい、両方で 遅延実行 となります。

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

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

コードでは:

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> に取り組むことは多くの場合データベースからあまりにも多くの行を返すことからあなたを救うことができます。もう1つの主な例はページングを行うことです。 Take および Skip on IQueryable を使用すると、要求された行数のみが得られます。 IEnumerable<T> に対してこれを実行すると、すべての行がメモリにロードされます。

1652
driis

一番上の答えは良いですが、2つのインタフェースが「どのように」異なるのかを説明する式ツリーについては言及していません。基本的に、LINQ拡張には2つの同一のセットがあります。 Where()Sum()Count()FirstOrDefault()などはすべて2つのバージョンがあります。1つは関数を受け入れるもの、もう1つは式を受け入れるものです。

  • IEnumerableバージョンのシグネチャは次のとおりです。Where(Func<Customer, bool> predicate)

  • IQueryableバージョンのシグネチャは次のとおりです。Where(Expression<Func<Customer, bool>> predicate)

どちらも同じ構文を使用して呼び出されるため、実際には気付かずに両方を使用しています。

例えばWhere(x => x.City == "<City>")IEnumerableIQueryableの両方で機能します。

  • IEnumerableコレクションでWhere()を使用する場合、コンパイラはコンパイル済み関数をWhere()に渡します。

  • IQueryableコレクションでWhere()を使用する場合、コンパイラは式ツリーをWhere()に渡します。式ツリーはリフレクションシステムに似ていますが、コード用です。コンパイラは、コードをデータ構造に変換します。このデータ構造には、コードの処理内容が簡単に解釈できる形式で記述されています。

どうしてこの表現木のことに悩むのですか? Where()に自分のデータをフィルタしてほしいだけです。 主な理由は、EFとLinq2SQL ORMの両方が式ツリーを直接SQLに変換できるため、コードの実行速度がずっと速くなることです。

ああ、それは無料のパフォーマンス向上のように思えます、その場合はどこでもAsQueryable()を使うべきですか? いいえ、IQueryableは、基礎となるデータプロバイダがそれを使用して何かを実行できる場合にのみ役立ちます。通常のListのようなものをIQueryableに変換しても何の利益も得られません。

262
Jacob

はい、どちらも遅延実行を使用します。 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()ごとにこれらのオブジェクトに対するメモリ内の別のフィルタになります。

上記の例でIQueryableの代わりにIEnumerableを使用した場合(2行目):

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

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

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

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

72
Kasper Roma

どちらも延期された実行になります。

どちらが優先されるかは、基礎となるデータソースによって異なります。

IEnumerableを返すと、ランタイムは自動的にLINQ to Objectsを使用してコレクションをクエリします。

IQueryableを実装する)IEnumerableを返すことで、クエリを基礎となるソース(LINQ to SQL、LINQ to XMLなど)でより適切に動作するようなものに変換するための追加機能が提供されます。

55
Justin Niessner

一般的に言えば、私は以下をお勧めします。

  • メソッドを使用して開発者が実行前に返すクエリを調整できるようにする場合は、IQueryable<T>を返します。

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

それが何であるかというようにIQueryableを想像してみてください。 IEnumerableはあなたが列挙することができるオブジェクトの集合(これは既に受け取られたか、または作成された)です。

27
sebastianmehler

一般的には、元の静的型のクエリが問題になるまでそのままにしておきます。

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

あなたがIQueryable<>で始めるなら、それを変更する何らかの説得力のある理由があるまでそれをIQueryable<>として保存したいでしょう。これは、クエリプロセッサにできるだけ多くの情報を与えたいからです。たとえば、10個の結果のみを使用する場合(Take(10)という名前)、SQL Serverにそのことを知らせて、クエリプランを最適化し、使用するデータだけを送信できるようにします。

型をIQueryable<>からIEnumerable<>に変更するための説得力のある理由は、特定のオブジェクトでのIQueryable<>の実装が処理できないか、または非効率的に処理する拡張関数を呼び出していることかもしれません。その場合は、(たとえばIEnumerable<>型の変数に代入するか、またはAsEnumerable拡張メソッドを使用して)型をIEnumerable<>に変換し、呼び出した拡張関数がEnumerableクラスの代わりになるようにします。 Queryableクラス.

24
AJS

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

  1. IEnumerablename__ 列挙できるメモリ内のオブジェクトのコレクション - 繰り返し処理を可能にするメモリ内シーケンス(foreachname__ループ内では簡単に使用できますが、IEnumeratorname__のみを使用できます)。それらはそのまま記憶に残っています。
  2. IQueryablename__ は式ツリー で、ある時点で他のものに変換されます 最終結果を列挙することができます 。これがほとんどの人を混乱させるものだと思います。

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

IQueryablename__は、LINQ集約関数(Sum、Countなど)やToList [Array、Dictionary、などのように、リリースAPIが呼び出されるとすぐに、基礎となるクエリプロバイダによって別のものに変換される式ツリー(単純にクエリ)を表します。 ……。 IQueryablename__オブジェクトもIEnumerablename__、IEnumerable<T>を実装しているため、クエリを表す場合そのクエリの結果を繰り返すことができます。それはIQueryableがクエリだけである必要がないことを意味します。正しい用語は 式の木 です。

これらの式がどのように実行され、どのように変換されるかは、いわゆるクエリプロバイダ(式実行プログラム)のせいです。

Entity Framework world(つまり、神秘的な基礎となるデータソースプロバイダ、またはクエリプロバイダ)では、IQueryablename__式はネイティブの T-SQL query。Nhibernatename__)に変換されます。 LINQ:IQueryable Providerの構築 リンクなど)でよく説明されている概念に従って所有している場合は、商品ストアプロバイダサービス用のカスタムクエリAPIを使用することをお勧めします。

そのため、基本的に、IQueryablename__オブジェクトは明示的に解放してSQLなどに書き換えるようにシステムに指示し、その後の処理のために実行チェーンを送るようになるまでずっとずっと構築されていきます。

deferredexecutionの場合と同様に、シーケンスに対して特定のAPIが呼び出されるたびに、式ツリースキーマをメモリに保持し、オンデマンドでのみ実行に送信するLINQname__機能です。同じCount、ToListなど).

両方を正しく使用するかどうかは、特定のケースで直面しているタスクによって大きく異なります。よく知られているリポジトリパターンでは、私は個人的にIListname__を返すことを選択しています。それはリスト(インデクサーなど)の上でIEnumerablename__を返すことです。したがって、IQueryablename__をリ​​ポジトリ内およびIEnumerableコード内の他の場所でのみ使用することをお勧めします。 IQueryablename__が分解して 原則の分離 原則)を破壊するというテスト容易性の問題については言わないでください。リポジトリ内から式を返す場合、消費者は望み通りに永続層で遊ぶことができます。

混乱へのちょっとした追加:)(コメントの議論から))これらは実際の型ではないのでメモリ内のオブジェクトではありません。それらは型のマーカーです - それをさらに深く追求したい場合は。ただし、IEnumerableをインメモリコレクションと見なし、IQueryablesを式ツリーと見なすのは理にかなっています(したがって、 MSDN と言います)ことも重要です。)IQueryableインターフェイスはIEnumerableインターフェイスを継承します。列挙はIQueryableオブジェクトに関連付けられた式ツリーを実行させるので、実際には、オブジェクトをメモリに格納しない限り、IEnumerableメンバを呼び出すことはできません。 IQueryablesは、データではなく単なるクエリです。

24

IEnumerable<T>の誤用がLINQクエリのパフォーマンスにどのように劇的に影響するかについての簡単なソースコードサンプルを含むブログ投稿があります: Entity Framework:IQueryable vs. IEnumerable

さらに深く掘り下げてソースを調べると、IEnumerable<T>に対して明らかに異なる拡張メソッドが実行されていることがわかります。

// Type: System.Linq.Enumerable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Enumerable
{
    public static IEnumerable<TSource> Where<TSource>(
        this IEnumerable<TSource> source, 
        Func<TSource, bool> predicate)
    {
        return (IEnumerable<TSource>) 
            new Enumerable.WhereEnumerableIterator<TSource>(source, predicate);
    }
}

およびIQueryable<T>

// Type: System.Linq.Queryable
// Assembly: System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
// Assembly location: C:\Windows\Microsoft.NET\Framework\v4.0.30319\System.Core.dll
public static class Queryable
{
    public static IQueryable<TSource> Where<TSource>(
        this IQueryable<TSource> source, 
        Expression<Func<TSource, bool>> predicate)
    {
        return source.Provider.CreateQuery<TSource>(
            Expression.Call(
                null, 
                ((MethodInfo) MethodBase.GetCurrentMethod()).MakeGenericMethod(
                    new Type[] { typeof(TSource) }), 
                    new Expression[] 
                        { source.Expression, Expression.Quote(predicate) }));
    }
}

最初のものは列挙可能なイテレータを返し、2番目のものはIQueryable sourceで指定されたクエリプロバイダを介してクエリを作成します。

18

私は最近IEnumerable v。IQueryableに関する問題に遭遇しました。使用されているアルゴリズムは、最初にIQueryableクエリを実行して一連の結果を取得しました。これらはEntity Framework(EF)クラスとしてインスタンス化されたアイテムと共にforeachループに渡されました。その後、このEFクラスがLinq to Entityクエリのfrom節で使用され、結果がIEnumerableになりました。

私はEFとLinq for Entitiesにかなり慣れていないので、ボトルネックが何であるかを理解するのにしばらく時間がかかりました。 MiniProfilingを使用して、クエリを見つけ、それから個々のオペレーションすべてを単一のIQueryable Linq for Entitiesクエリに変換しました。 IEnumerableの実行に15秒、IQueryableの実行に0.5秒かかりました。 3つのテーブルが関係しており、これを読んだ後、IEnumerableクエリは実際には3つのテーブルのクロス積を形成し、結果をフィルタリングしていたと思います。

IQueryablesを経験則として使用し、自分の変更を測定可能にするために自分の作業のプロファイルを作成するようにしてください。

11
sscheider

これらはIQueryable<T>IEnumerable<T>の違いです。

difference between returning IQueryable<T> vs. IEnumerable<T>

11

一見矛盾しているように思われる応答(主にIEnumerableを取り巻く)のためにいくつかのことを明確にしたいと思います。

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

(2)IQueryableIEnumerableの両方のLINQは、結果セットを反復処理するときに遅延ロードを試みます。 (実装は各型のインターフェース拡張メソッドで見られることに注意してください。)

言い換えれば、IEnumerablesはもっぱら「インメモリ」ではありません。 IQueryablesは常にデータベース上で実行されるわけではありません。 IEnumerableは抽象データプロバイダを持っていないので、メモリに物をロードしなければなりません(一度取得されると、おそらく遅れて)。 IQueryablesは抽象プロバイダ(LINQ-to-SQLなど)に依存していますが、これも.NETのインメモリプロバイダになる可能性があります。

サンプルユースケース

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

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

(c)ビューからデータセットのレコード、子エンティティ、およびプロパティを繰り返しアクセスします。 (例外が発生する可能性があります。)

考えられる問題

(1)IEnumerableが遅延ロードを試行し、データコンテキストが期限切れになりました。プロバイダが利用できなくなったためにスローされた例外。

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

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

解決策

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

resultList = resultSet.ToList()を呼び出してクエリを実行し結果を保存するこれはあなたのエンティティがインメモリであることを保証する最も直接的な方法のようです。

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

10

“ IEnumerable”と“ IQueryable”の主な違いは、フィルタロジックがどこで実行されるかです。一方はクライアント側(メモリ内)で実行され、もう一方はデータベース上で実行されます。

たとえば、データベースに1人のユーザーのレコードが10,000件あるとします。アクティブユーザーであるユーザーのうち900人しかいないとします。次にIsActiveフィルタを適用し、最終的に900人のアクティブユーザが返されます。

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

参照先 リンク

9
Tabish Usman

両方を同じ方法で使用できますが、パフォーマンスが異なるだけです。

IQueryableは効率的な方法でデータベースに対してのみ実行されます。つまり、選択クエリ全体が作成され、関連レコードのみが取得されます。

たとえば、名前が「Nimal」で始まる トップ10 の顧客を考えます。この場合、選択クエリはselect top 10 * from Customer where name like ‘Nimal%’として生成されます。

しかし、IEnumerableを使用した場合、クエリはselect * from Customer where name like ‘Nimal%’のようになり、上位10位はC#コーディングレベルでフィルタリングされます(データベースからすべての顧客レコードを取得してC#に渡します)。

5
user3710357

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

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

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

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

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

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

何を選ぶ?

返されたデータのセット全体が必要な場合は、最大速度を提供するIEnumerableを使用することをお勧めします。

返されたデータのセット全体を必要とせず、フィルタリングされたデータだけを必要としない場合は、IQueryableを使用することをお勧めします。

5
Gleb B

上記に加えて、IQueryableの代わりにIEnumerableを使用すると例外が発生する可能性があることに注意してください。

productsIEnumerableである場合、以下は正常に機能します。

products.Skip(-4);

ただし、productsIQueryableであり、DBテーブルのレコードにアクセスしようとすると、次のエラーが表示されます。

OFFSET句で指定されたオフセットは負の値にはできません。

これは、次のクエリが構築されたためです。

SELECT [p].[ProductId]
FROM [Products] AS [p]
ORDER BY (SELECT 1)
OFFSET @__p_0 ROWS

oFFSETに負の値を指定することはできません。

0
Backwards_Dave