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;
どちらも実行が延期されるのでしょうか。また、どちらが優先されるのでしょうか。
はい、両方で 遅延実行 となります。
違いは、 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>
に対してこれを実行すると、すべての行がメモリにロードされます。
一番上の答えは良いですが、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>")
はIEnumerable
とIQueryable
の両方で機能します。
IEnumerable
コレクションでWhere()
を使用する場合、コンパイラはコンパイル済み関数をWhere()
に渡します。
IQueryable
コレクションでWhere()
を使用する場合、コンパイラは式ツリーをWhere()
に渡します。式ツリーはリフレクションシステムに似ていますが、コード用です。コンパイラは、コードをデータ構造に変換します。このデータ構造には、コードの処理内容が簡単に解釈できる形式で記述されています。
どうしてこの表現木のことに悩むのですか? Where()
に自分のデータをフィルタしてほしいだけです。 主な理由は、EFとLinq2SQL ORMの両方が式ツリーを直接SQLに変換できるため、コードの実行速度がずっと速くなることです。
ああ、それは無料のパフォーマンス向上のように思えます、その場合はどこでもAsQueryable()
を使うべきですか? いいえ、IQueryable
は、基礎となるデータプロバイダがそれを使用して何かを実行できる場合にのみ役立ちます。通常のList
のようなものをIQueryable
に変換しても何の利益も得られません。
はい、どちらも遅延実行を使用します。 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クエリに変換してデータベースエンジンで実行します。
どちらも延期された実行になります。
どちらが優先されるかは、基礎となるデータソースによって異なります。
IEnumerable
を返すと、ランタイムは自動的にLINQ to Objectsを使用してコレクションをクエリします。
(IQueryable
を実装する)IEnumerable
を返すことで、クエリを基礎となるソース(LINQ to SQL、LINQ to XMLなど)でより適切に動作するようなものに変換するための追加機能が提供されます。
一般的に言えば、私は以下をお勧めします。
メソッドを使用して開発者が実行前に返すクエリを調整できるようにする場合は、IQueryable<T>
を返します。
列挙するオブジェクトのセットを転送したい場合はIEnumerable
を返します。
それが何であるかというようにIQueryable
を想像してみてください。 IEnumerable
はあなたが列挙することができるオブジェクトの集合(これは既に受け取られたか、または作成された)です。
一般的には、元の静的型のクエリが問題になるまでそのままにしておきます。
このため、変数をIQueryable<>
またはIEnumerable<>
の代わりに 'var'として定義すると、型を変更していないことがわかります。
あなたがIQueryable<>
で始めるなら、それを変更する何らかの説得力のある理由があるまでそれをIQueryable<>
として保存したいでしょう。これは、クエリプロセッサにできるだけ多くの情報を与えたいからです。たとえば、10個の結果のみを使用する場合(Take(10)
という名前)、SQL Serverにそのことを知らせて、クエリプランを最適化し、使用するデータだけを送信できるようにします。
型をIQueryable<>
からIEnumerable<>
に変更するための説得力のある理由は、特定のオブジェクトでのIQueryable<>
の実装が処理できないか、または非効率的に処理する拡張関数を呼び出していることかもしれません。その場合は、(たとえばIEnumerable<>
型の変数に代入するか、またはAsEnumerable
拡張メソッドを使用して)型をIEnumerable<>
に変換し、呼び出した拡張関数がEnumerable
クラスの代わりになるようにします。 Queryable
クラス.
以前より多くのことが言われてきましたが、より技術的な方法で根源に戻ります。
IEnumerable
name__ 列挙できるメモリ内のオブジェクトのコレクション - 繰り返し処理を可能にするメモリ内シーケンス(foreach
name__ループ内では簡単に使用できますが、IEnumerator
name__のみを使用できます)。それらはそのまま記憶に残っています。IQueryable
name__ は式ツリー で、ある時点で他のものに変換されます 最終結果を列挙することができます 。これがほとんどの人を混乱させるものだと思います。彼らは明らかに異なる含意を持っています。
IQueryable
name__は、LINQ集約関数(Sum、Countなど)やToList [Array、Dictionary、などのように、リリースAPIが呼び出されるとすぐに、基礎となるクエリプロバイダによって別のものに変換される式ツリー(単純にクエリ)を表します。 ……。 IQueryable
name__オブジェクトもIEnumerable
name__、IEnumerable<T>
を実装しているため、クエリを表す場合そのクエリの結果を繰り返すことができます。それはIQueryableがクエリだけである必要がないことを意味します。正しい用語は 式の木 です。
これらの式がどのように実行され、どのように変換されるかは、いわゆるクエリプロバイダ(式実行プログラム)のせいです。
Entity Framework world(つまり、神秘的な基礎となるデータソースプロバイダ、またはクエリプロバイダ)では、IQueryable
name__式はネイティブの T-SQL query。Nhibernate
name__)に変換されます。 LINQ:IQueryable Providerの構築 リンクなど)でよく説明されている概念に従って所有している場合は、商品ストアプロバイダサービス用のカスタムクエリAPIを使用することをお勧めします。
そのため、基本的に、IQueryable
name__オブジェクトは明示的に解放してSQLなどに書き換えるようにシステムに指示し、その後の処理のために実行チェーンを送るようになるまでずっとずっと構築されていきます。
deferredexecutionの場合と同様に、シーケンスに対して特定のAPIが呼び出されるたびに、式ツリースキーマをメモリに保持し、オンデマンドでのみ実行に送信するLINQ
name__機能です。同じCount、ToListなど).
両方を正しく使用するかどうかは、特定のケースで直面しているタスクによって大きく異なります。よく知られているリポジトリパターンでは、私は個人的にIList
name__を返すことを選択しています。それはリスト(インデクサーなど)の上でIEnumerable
name__を返すことです。したがって、IQueryable
name__をリポジトリ内およびIEnumerableコード内の他の場所でのみ使用することをお勧めします。 IQueryable
name__が分解して 原則の分離 原則)を破壊するというテスト容易性の問題については言わないでください。リポジトリ内から式を返す場合、消費者は望み通りに永続層で遊ぶことができます。
混乱へのちょっとした追加:)(コメントの議論から))これらは実際の型ではないのでメモリ内のオブジェクトではありません。それらは型のマーカーです - それをさらに深く追求したい場合は。ただし、IEnumerableをインメモリコレクションと見なし、IQueryablesを式ツリーと見なすのは理にかなっています(したがって、 MSDN と言います)ことも重要です。)IQueryableインターフェイスはIEnumerableインターフェイスを継承します。列挙はIQueryableオブジェクトに関連付けられた式ツリーを実行させるので、実際には、オブジェクトをメモリに格納しない限り、IEnumerableメンバを呼び出すことはできません。 IQueryablesは、データではなく単なるクエリです。
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で指定されたクエリプロバイダを介してクエリを作成します。
私は最近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を経験則として使用し、自分の変更を測定可能にするために自分の作業のプロファイルを作成するようにしてください。
一見矛盾しているように思われる応答(主にIEnumerableを取り巻く)のためにいくつかのことを明確にしたいと思います。
(1)IQueryable
はIEnumerable
インターフェースを拡張します。 (エラーなしでIQueryable
を期待するものにIEnumerable
を送ることができます。)
(2)IQueryable
とIEnumerable
の両方のLINQは、結果セットを反復処理するときに遅延ロードを試みます。 (実装は各型のインターフェース拡張メソッドで見られることに注意してください。)
言い換えれば、IEnumerables
はもっぱら「インメモリ」ではありません。 IQueryables
は常にデータベース上で実行されるわけではありません。 IEnumerable
は抽象データプロバイダを持っていないので、メモリに物をロードしなければなりません(一度取得されると、おそらく遅れて)。 IQueryables
は抽象プロバイダ(LINQ-to-SQLなど)に依存していますが、これも.NETのインメモリプロバイダになる可能性があります。
サンプルユースケース
(a)EFコンテキストからIQueryable
としてレコードのリストを取り出す。 (メモリー内にレコードはありません。)
(b)モデルがIQueryable
であるビューにIEnumerable
を渡します。 (有効。IQueryable
はIEnumerable
を拡張します。)
(c)ビューからデータセットのレコード、子エンティティ、およびプロパティを繰り返しアクセスします。 (例外が発生する可能性があります。)
考えられる問題
(1)IEnumerable
が遅延ロードを試行し、データコンテキストが期限切れになりました。プロバイダが利用できなくなったためにスローされた例外。
(2)Entity Frameworkのエンティティプロキシが有効(デフォルト)になっており、期限切れのデータコンテキストで関連(仮想)オブジェクトにアクセスしようとしています。 (1)と同じです。
(3)複数のアクティブ結果セット(MARS)。 foreach( var record in resultSet )
ブロック内のIEnumerable
を反復処理して同時にrecord.childEntity.childProperty
にアクセスしようとすると、データセットとリレーショナルエンティティの両方の遅延ロードが原因で、MARSが発生する可能性があります。接続文字列で有効になっていない場合、これは例外を引き起こします。
解決策
resultList = resultSet.ToList()
を呼び出してクエリを実行し結果を保存するこれはあなたのエンティティがインメモリであることを保証する最も直接的な方法のようです。
あなたが関連エンティティにアクセスしている場合でも、データコンテキストが必要になるかもしれません。それとも、あなたはエンティティプロキシを無効にして、あなたのInclude
から明示的にDbSet
関連のエンティティを無効にすることができます。
“ IEnumerable”と“ IQueryable”の主な違いは、フィルタロジックがどこで実行されるかです。一方はクライアント側(メモリ内)で実行され、もう一方はデータベース上で実行されます。
たとえば、データベースに1人のユーザーのレコードが10,000件あるとします。アクティブユーザーであるユーザーのうち900人しかいないとします。次にIsActiveフィルタを適用し、最終的に900人のアクティブユーザが返されます。
一方、同じケースで「IQueryable」を使用すると、データベースにIsActiveフィルタが直接適用され、そこから直接900人のアクティブユーザが返されます。
参照先 リンク
両方を同じ方法で使用できますが、パフォーマンスが異なるだけです。
IQueryableは効率的な方法でデータベースに対してのみ実行されます。つまり、選択クエリ全体が作成され、関連レコードのみが取得されます。
たとえば、名前が「Nimal」で始まる トップ10 の顧客を考えます。この場合、選択クエリはselect top 10 * from Customer where name like ‘Nimal%’
として生成されます。
しかし、IEnumerableを使用した場合、クエリはselect * from Customer where name like ‘Nimal%’
のようになり、上位10位はC#コーディングレベルでフィルタリングされます(データベースからすべての顧客レコードを取得してC#に渡します)。
最初の2つの本当に良い答え(driisとJacobによる)に加えて:
IEnumerableインターフェイスはSystem.Collections名前空間にあります。
IEnumerableオブジェクトはメモリ内のデータのセットを表し、このデータを前方にのみ移動できます。 IEnumerableオブジェクトによって表されるクエリは即時かつ完全に実行されるため、アプリケーションはデータをすばやく受信します。
クエリが実行されると、IEnumerableはすべてのデータをロードします。フィルタする必要がある場合は、フィルタ処理自体がクライアント側で行われます。
IQueryableインターフェイスはSystem.Linq名前空間にあります。
IQueryableオブジェクトはデータベースへのリモートアクセスを提供し、最初から最後までの直接的な順序、またはその逆の順序でデータをナビゲートすることを可能にします。クエリを作成する過程で、返されるオブジェクトはIQueryableであり、クエリは最適化されています。その結果、実行時に消費されるメモリ、ネットワーク帯域幅が少なくなりますが、同時にIEnumerableオブジェクトを返すクエリよりも処理が遅くなります。
何を選ぶ?
返されたデータのセット全体が必要な場合は、最大速度を提供するIEnumerableを使用することをお勧めします。
返されたデータのセット全体を必要とせず、フィルタリングされたデータだけを必要としない場合は、IQueryableを使用することをお勧めします。
上記に加えて、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に負の値を指定することはできません。