帰国の違いは何である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>
対の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>
ことで、多くの場合、データベースから返される行が多すぎないようにすることができます。もう一つの典型的な例は、ページングをやっている:あなたが使用している場合Take
とSkip
でIQueryable
、あなただけ要求された行の数を取得します。これを行うと、IEnumerable<T>
すべての行がメモリに読み込まれます。
IEnumerable
にはIQueryable
ない、すべてのLINQの操作は、すべてのLINQプロバイダによってサポートされていることです。そのため、何をしているのかがわかっている限りIQueryable
、クエリのほとんどをLINQプロバイダー(LINQ2SQL、EF、NHibernate、MongoDBなど)にプッシュするために使用できます。しかし、他のコードに必要なことをすべて実行させると、IQueryable
クライアントコードのどこかでサポートされていない操作が使用されるため、最終的に問題が発生します。IQueryable
リポジトリまたは同等のレイヤーを超えて「実際に」リリースしないという推奨事項に同意します。
一番良い答えは良いですが、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
あなたに利益を与えることはありません。
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
。
はい、どちらも遅延実行を使用します。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クエリに変換されてデータベースエンジンで実行されます。
どちらも延期されます。
どちらが優先されるかは、基礎となるデータソースが何であるかに依存します。
を返すIEnumerable
と、ランタイムは自動的にLINQ to Objectsを使用してコレクションをクエリします。
IQueryable
(IEnumerable
ところでを実装する)を返すと、クエリを基になるソース(LINQ to SQL、LINQ to XMLなど)でパフォーマンスが向上するものに変換する追加機能が提供されます。
一般的に、私は以下をお勧めします:
IQueryable<T>
開発者がメソッドを使用して、実行前に返すクエリを調整できるようにする場合は、Returnを押します。
IEnumerable
列挙するオブジェクトのセットを転送する場合は、戻ります。
IQueryable
それが何であるかを想像してください-データの「クエリ」(必要に応じてこれを調整できます)。IEnumerable
あなたが列挙することができ、その上(すでに受信しているか、作成された)オブジェクトのセットです。
多くのことは以前に言われましたが、より技術的な方法でルーツに戻ります:
IEnumerable
列挙できるメモリ内のオブジェクトのコレクション -反復処理を可能にするメモリ内シーケンス(foreach
ループ内でIEnumerator
のみ実行できますが、ループ内での処理が非常に簡単になります)。それらはそのままメモリに常駐します。IQueryable
最終的な結果を列挙する機能を備えたある時点で別のものに変換される式ツリーです。これはほとんどの人を混乱させるものだと思います。彼らは明らかに異なる意味合いを持っています。
IQueryable
LINQ集計関数(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はクエリではなく、データです。
一般に、クエリの元の静的タイプは、重要になるまで保持する必要があります。
このため、IQueryable<>
またはの代わりに変数を 'var'として定義するIEnumerable<>
と、型を変更していないことがわかります。
を最初に使用する場合はIQueryable<>
、通常、IQueryable<>
それを変更する説得力のある理由があるまで、それをとして保持します。これは、クエリプロセッサにできるだけ多くの情報を提供するためです。たとえば、10個の結果のみを使用する場合(を呼び出したTake(10)
)、SQL Serverにそれを知らせて、クエリプランを最適化し、使用するデータのみを送信できるようにします。
タイプを変更するには説得力のある理由IQueryable<>
には、IEnumerable<>
あなたが実装することを、いくつかの拡張機能を呼び出していることかもしれないIQueryable<>
あなたの特定のオブジェクトでは、非効率的に処理するか、ハンドルすることはできませんいずれかを。その場合、IEnumerable<>
(たとえばIEnumerable<>
、型の変数に割り当てるか、AsEnumerable
拡張メソッドを使用して)型をに変換して、呼び出す拡張関数がEnumerable
クラスではなくクラス内の拡張関数になるようにすることができますQueryable
。
の誤用がどのように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
ソースで指定されたクエリプロバイダを通じてクエリを作成します。
最近、IEnumerable
vで問題が発生しましたIQueryable
。使用されているアルゴリズムは、最初にIQueryable
クエリを実行して一連の結果を取得しました。その後、これらはforeach
ループに渡され、アイテムはEntity Framework(EF)クラスとしてインスタンス化されました。次に、このEFクラスがfrom
Linq to Entityクエリの句で使用され、結果がIEnumerable
。
私はEFとLinq for Entitiesにかなり慣れていないので、ボトルネックが何であるかを理解するのにしばらく時間がかかりました。MiniProfilingを使用してクエリを見つけ、個々の操作をすべて単一のIQueryable
Linq for Entitiesクエリに変換しました。IEnumerable
15秒かかりましたし、IQueryable
実行するために0.5秒かかりました。3つのテーブルが関係していたため、これを読んだ後、IEnumerable
クエリが実際に3つのテーブルのクロス積を形成し、結果をフィルター処理したと思います。
経験則としてIQueryablesを使用し、変更を測定可能にするために作業のプロファイルを作成してください。
IQueryable
これらのAPIの1つを呼び出すと、メモリもスタックしますが、そうでない場合は、式をレイヤーのスタックに渡して、APIが呼び出されるまでフィルターをいじることができます。適切に設計されたリポジトリとして適切に設計されたDALは、この種の問題を解決します;)
一見矛盾する応答(主にIEnumerableを取り巻く)に起因するいくつかのことを明確にしたいと思います。
(1)インターフェースをIQueryable
拡張しIEnumerable
ます。(エラーなしIQueryable
で予期するものにを送信できIEnumerable
ます。)
(2)IQueryable
とIEnumerable
LINQの両方が、結果セットを反復するときに遅延読み込みを試みます。(実装は、各タイプのインターフェース拡張メソッドで確認できます。)
つまり、IEnumerables
「メモリ内」だけではありません。 IQueryables
常にデータベースで実行されるわけではありません。 IEnumerable
抽象データプロバイダーがないため、物事をメモリに読み込む必要があります(一度取得すると、場合によっては遅延して)。 IQueryables
抽象プロバイダー(LINQ-to-SQLなど)に依存しますが、これは.NETインメモリプロバイダーの場合もあります。
使用例
(a)IQueryable
EFコンテキストからのレコードのリストを取得します。(メモリ内にレコードはありません。)
(b)IQueryable
モデルがであるビューにを渡しIEnumerable
ます。(有効です。IQueryable
拡張されIEnumerable
ます。)
(c)データセットのレコードを繰り返し処理し、ビューから子エンティティおよびプロパティにアクセスします。(例外が発生する可能性があります!)
考えられる問題
(1)IEnumerable
遅延読み込みの試行とデータコンテキストの有効期限が切れています。プロバイダーが利用できなくなったために例外がスローされました。
(2)Entity Frameworkエンティティプロキシが有効になっていて(デフォルト)、期限切れのデータコンテキストで関連する(仮想)オブジェクトにアクセスしようとしている。(1)と同じ。
(3)複数のアクティブな結果セット(MARS)。ブロックIEnumerable
内でを反復し、foreach( var record in resultSet )
同時ににアクセスしようとするrecord.childEntity.childProperty
と、データセットとリレーショナルエンティティの両方の遅延読み込みが原因でMARSが発生する可能性があります。接続文字列で有効になっていない場合、これにより例外が発生します。
解決
クエリを実行し、呼び出して結果を保存するresultList = resultSet.ToList()
これは、エンティティがメモリ内にあることを確認する最も簡単な方法のようです。
関連エンティティにアクセスしている場合でも、データコンテキストが必要な場合があります。または、エンティティプロキシと明示的にInclude
関連するエンティティをから無効にすることができますDbSet
。
「IEnumerable」と「IQueryable」の主な違いは、フィルターロジックが実行される場所についてです。1つはクライアント側(メモリ内)で実行され、もう1つはデータベースで実行されます。
たとえば、データベースにユーザーのレコードが10,000ある例を考えて、アクティブなユーザーが900人だけであるとしましょう。この場合、「IEnumerable」を使用すると、最初にメモリに10,000レコードすべてが読み込まれ、次に、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に負の値を指定することはできません。