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


1085

帰国の違いは何である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;

回答:


1778

はい、どちらも遅延実行を提供します。

違いは、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>ことで、多くの場合、データベースから返される行が多すぎないようにすることができます。もう一つの典型的な例は、ページングをやっている:あなたが使用している場合TakeSkipIQueryable、あなただけ要求された行の数を取得します。これを行うと、IEnumerable<T>すべての行がメモリに読み込まれます。


32
素晴らしい説明。IEnumerableがIQueryableよりも望ましい状況はありますか?
fjxx

8
したがって、IQueryableを使用してメモリオブジェクトをクエリしている場合、IEnumerableとIQueryableの違いはないと言えるでしょう。
Tarik

11
警告:記述された最適化のため、IQueryableは魅力的なソリューションである可能性がありますが、リポジトリまたはサービスレイヤーを超えて許可することはできません。これは、「LINQ式のスタック」によって引き起こされるオーバーヘッドからデータベースを保護するためです。
Yorro 2013

48
@fjxxはい。元の結果(複数の最終結果)で繰り返しフィルタリングする場合。IQueryableインターフェイスでそれを行うと、データベースへのいくつかの
ラウンドトリップが行われます。IEnumerable

34
好むもう一つの理由IEnumerableにはIQueryableない、すべてのLINQの操作は、すべてのLINQプロバイダによってサポートされていることです。そのため、何をしているのかがわかっている限りIQueryable、クエリのほとんどをLINQプロバイダー(LINQ2SQL、EF、NHibernate、MongoDBなど)にプッシュするために使用できます。しかし、他のコードに必要なことをすべて実行させると、IQueryableクライアントコードのどこかでサポートされていない操作が使用されるため、最終的に問題が発生します。IQueryableリポジトリまたは同等のレイヤーを超えて「実際に」リリースしないという推奨事項に同意します。
Avish、2014

302

一番良い答えは良いですが、2つのインターフェースの「違い」を説明する式ツリーについては触れていません。基本的に、LINQ拡張機能の同じセットが2つあります。Where()Sum()Count()FirstOrDefault()の機能を受け入れ1と式を受け付け1:などすべての2つのバージョンを持っています。

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

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

両方とも同じ構文を使用して呼び出されるため、おそらく両方ともそれを使用せずに使用しているでしょう。

たとえばWhere(x => x.City == "<City>")、両方IEnumerableで動作しますIQueryable

  • コレクションで使用Where()する場合IEnumerable、コンパイラはコンパイルされた関数をWhere()

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

なぜこの表現ツリーのことを気にするのですか?Where()データをフィルタリングしたいだけです。 主な理由は、EFとLinq2SQLの両方のORMが式ツリーをSQLに直接変換できるため、コードの実行速度が大幅に向上するためです。

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


9
IMOそれは受け入れられた答えよりも優れています。ただし、1つは得られません。IQueryableは通常のオブジェクトには何のメリットもありませんが、何らかの形で悪いのでしょうか。何のメリットもない場合は、IEnumerableを選択する理由として十分ではないため、場所全体でIQueryableを使用するという考えは有効なままです。
Sergei Tachenov 2016

1
Sergey、IQueryableはIEnumerableを拡張するため、IQueryableを使用すると、IEnumerableのインスタンス化よりも多くのメモリをロードできます。だからここに議論があります。(stackoverflow.com/questions/12064828/…c ++これを外挿できると思いましたが)
Viking

これが最良の答えであるとセルゲイに同意する(受け入れられた答えは問題ありません)。私は私の経験では、ということを付け加えたいIQueryableほどもない解析機能をしIEnumerableています。例えば、あなたがの要素がどの知りたい場合DBSet<BookEntity>にはないList<BookObject>dbSetObject.Where(e => !listObject.Any(o => o.bookEntitySource == e))例外がスローされますExpression of type 'BookEntity' cannot be used for parameter of type 'BookObject' of method 'Boolean Contains[BookObject] (IEnumerable[BookObject], BookObject)'.ToList()後に追加する必要がありましたdbSetObject
Jean-David Lanz

80

はい、どちらも遅延実行を使用します。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"

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

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


5
これは、IEnumerableにキャストするときに、基になるIQueryableがIQueryable拡張メソッドを失うことを教えてくれます。
Yiping 2018

56

どちらも延期されます。

どちらが優先されるかは、基礎となるデータソースが何であるかに依存します。

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

IQueryableIEnumerableところでを実装する)を返すと、クエリを基になるソース(LINQ to SQL、LINQ to XMLなど)でパフォーマンスが向上するものに変換する追加機能が提供されます。


30

一般的に、私は以下をお勧めします:

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

  • IEnumerable列挙するオブジェクトのセットを転送する場合は、戻ります。

IQueryableそれが何であるかを想像してください-データの「クエリ」(必要に応じてこれを調整できます)。IEnumerableあなたが列挙することができ、その上(すでに受信しているか、作成された)オブジェクトのセットです。


2
「列挙可能」ではなく、「IEnumerable可能」ではありません。
ケーシー

28

多くのことは以前に言われましたが、より技術的な方法でルーツに戻ります:

  1. IEnumerable 列挙できるメモリ内のオブジェクトのコレクション -反復処理を可能にするメモリ内シーケンス(foreachループ内でIEnumeratorのみ実行できますが、ループ内での処理が非常に簡単になります)。それらはそのままメモリに常駐します。
  2. IQueryable 最終的な結果を列挙する機能を備えたある時点別のものに変換される式ツリーです。これはほとんどの人を混乱させるものだと思います。

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

IQueryableLINQ集計関数(Sum、Countなど)やToList [Array、Dictionary、など、リリースAPIが呼び出されるとすぐに、基になるクエリプロバイダーによって何かに変換される式ツリー(クエリ)を表します。 ..]。また、IQueryableオブジェクトもを実装IEnumerableしているIEnumerable<T>ため、クエリを表す場合、そのクエリの結果を反復できます。つまり、IQueryableはクエリだけである必要はありません。正しい用語は、それらが式ツリーであることです。

これらの式がどのように実行され、何が行われるかは、いわゆるクエリプロバイダー(考えられる式エグゼキューター)次第です。

では、エンティティフレームワーク(その神秘的な基礎となるデータソースプロバイダー、またはクエリプロバイダです)世界IQueryable表現がネイティブに翻訳されているT-SQLのクエリ。Nhibernate彼らと同様のことをします。たとえば、LINQ:IQueryable Providerリンクの構築で十分に説明されている概念に従って独自のコードを記述できます。また、製品ストアプロバイダーサービス用のカスタムクエリAPIが必要になる場合があります。

だから基本的に、 IQueryableオブジェクトは明示的に解放し、システムにオブジェクトをSQLなどに再書き込みして以降の処理のために実行チェーンを送信するように指示するまで、ずっと構築されています。

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

両方の適切な使用法は、特定のケースで直面しているタスクに大きく依存します。よく知られているリポジトリパターンの場合、私IListはを返すことを個人的に選択していますIEnumerable。したがって、IQueryableリポジトリ内でのみ使用し、コードの他の場所ではIEnumerable を使用することをお勧めします。懸念IQueryable分離の原則を破綻させてしまうテスト容易性の懸念については触れません。リポジトリ内から式を返す場合、コンシューマは永続化レイヤを自由に操作できます。

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


3
IEnumerableが常にメモリ内にあるというコメントは、必ずしも真実ではありません。IQueryableインターフェイスはIEnumerableインターフェイスを実装します。このため、LINQ-to-SQLクエリを表す生のIQueryableを、IEnumerableを期待するビューに直接渡すことができます。データコンテキストの有効期限が切れているか、MARS(複数のアクティブな結果セット)で問題が発生していることに驚くかもしれません。

そのため、実際には、メモリ内にオブジェクトがなければ、IEnumerableメンバーを実際に呼び出すことはできません。それが空でなければ、とにかく、そこに入るでしょう。IQueryableはクエリではなく、データです。しかし、私は本当にあなたの主張を理解しています。これについてコメントを追加します。
Arman McHitarian 2015

@AlexanderPritchardそれらはそれ自体が実際の型ではないため、メモリ内のオブジェクトではなく、型のマーカーです。しかし、IEnumerablesをメモリ内コレクションと考えるのに対し、IQueryablesを式ツリーと考えるのは理にかなっています(そのため、MSDNでもこのようにしています)。重要なのは、IQueryableインターフェイスがIEnumerableインターフェイスを継承するため、それがクエリを表す場合、そのクエリの結果を列挙できるということです。列挙により、IQueryableオブジェクトに関連付けられた式ツリーが実行されます。
Arman McHitarian 2015

24

一般に、クエリの元の静的タイプは、重要になるまで保持する必要があります。

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

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

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


18

の誤用がどのようにIEnumerable<T>LINQクエリのパフォーマンスに劇的な影響を与えるかについての簡単なソースコードサンプルを含むブログ投稿があります。エンティティフレームワーク:IQueryableと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) }));
    }
}

1つ目は列挙可能なイテレータを返し、2つ目はIQueryableソースで指定されたクエリプロバイダを通じてクエリを作成します。


11

最近、IEnumerablevで問題が発生しましたIQueryable。使用されているアルゴリズムは、最初にIQueryableクエリを実行して一連の結果を取得しました。その後、これらはforeachループに渡され、アイテムはEntity Framework(EF)クラスとしてインスタンス化されました。次に、このEFクラスがfromLinq to Entityクエリの句で使用され、結果がIEnumerable

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

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


これは、IEnumerableリストがメモリ内オブジェクトであるときに、IQueryable式がEFでネイティブSQLに変換され、DBで直接実行されるためです。これらは、Count、Sum、またはTo ...などの集計関数を呼び出し、その後メモリで操作するときに、DBからフェッチされます。IQueryableこれらのAPIの1つを呼び出すと、メモリもスタックしますが、そうでない場合は、式をレイヤーのスタックに渡して、APIが呼び出されるまでフィルターをいじることができます。適切に設計されたリポジトリとして適切に設計されたDALは、この種の問題を解決します;)
Arman McHitarian 2014年

10

一見矛盾する応答(主にIEnumerableを取り巻く)に起因するいくつかのことを明確にしたいと思います。

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

(2)IQueryableIEnumerableLINQの両方が、結果セットを反復するときに遅延読み込みを試みます。(実装は、各タイプのインターフェース拡張メソッドで確認できます。)

つまり、IEnumerables「メモリ内」だけではありません。 IQueryables常にデータベースで実行されるわけではありません。 IEnumerable抽象データプロバイダーがないため、物事をメモリに読み込む必要があります(一度取得すると、場合によっては遅延して)。 IQueryables抽象プロバイダー(LINQ-to-SQLなど)に依存しますが、これは.NETインメモリプロバイダーの場合もあります。

使用例

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

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

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

考えられる問題

(1)IEnumerable遅延読み込みの試行とデータコンテキストの有効期限が切れています。プロバイダーが利用できなくなったために例外がスローされました。

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

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

解決

  • 接続文字列でMARSを有効にすると、動作が不安定になることがわかりました。MARSは、十分に理解され、明確に望まれていない限り、回避することをお勧めします。

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

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


9

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

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

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

参照リンク


パフォーマンスの点で最適化された軽量なものはどれですか?
Sitecoreサム

@Sam "IQueryable"は、最適化され、軽量であるという点でより推奨されます。
Tabish Usman 2018

6

どちらも同じ方法で使用でき、パフォーマンスのみが異なります。

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

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

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


5

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

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

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

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

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

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

何を選ぶ?

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

返されたデータのセット全体が必要ではなく、一部のフィルターされたデータのみが必要な場合は、IQueryableを使用することをお勧めします。


0

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

次の場合products、正常に機能しますIEnumerable

products.Skip(-4);

ただし、productsでありIQueryable、DBテーブルのレコードにアクセスしようとすると、次のエラーが発生します。

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.