LinqでのIQueryableの使用


257

IQueryableLINQのコンテキストでの使用は何ですか?

拡張メソッドの開発やその他の目的に使用されていますか?

回答:


508

Marc Gravellの答えは非常に完全ですが、ユーザーの観点からもこれについて何か追加したいと思いました...


ユーザーの観点から見た主な違いは、IQueryable<T>(物事を正しくサポートするプロバイダーと共に)を使用すると、多くのリソースを節約できることです。

たとえば、多くのORMシステムでリモートデータベースに対して作業している場合、2つの方法でテーブルからデータをフェッチするオプションがあります。1つはを返しIEnumerable<T>、もう1つはを返しますIQueryable<T>。たとえば、Productsテーブルがあり、コストが25ドルを超えるすべての製品を取得するとします。

もし、するなら:

 IEnumerable<Product> products = myORM.GetProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

ここで何が起こるかというと、データベースがすべての製品をロードし、それらをネットワーク経由でプログラムに渡します。次に、プログラムはデータをフィルタリングします。本質的に、データベースはを実行し、SELECT * FROM Productsすべての製品をユーザーに返します。

IQueryable<T>一方、適切なプロバイダーを使用すると、次のことができます。

 IQueryable<Product> products = myORM.GetQueryableProducts();
 var productsOver25 = products.Where(p => p.Cost >= 25.00);

コードは同じに見えますが、ここでの違いは、実行されるSQLがになることSELECT * FROM Products WHERE Cost >= 25です。

開発者としてのPOVから、これは同じように見えます。ただし、パフォーマンスの観点からは、20,000の代わりに、ネットワーク全体で2つのレコードのみを返すことができます。


9
「GetQueryableProducts();」の定義はどこにありますか?
Pankaj 2011

12
戻っていることを任意の方法であることを意図しています@StackOverflowUser IQueryable<Product>など、あなたのORMまたはリポジトリに固有のだろう-
リードCopsey

正しい。しかし、この関数呼び出しの後にwhere句を記述しました。したがって、システムはまだフィルターを認識していません。つまり、製品のすべてのレコードを取得します。正しい?
Pankaj

@StackOverflowUserいいえ-これはIQueryable <T>の優れた点です - 結果を取得するときに評価するように設定できます。つまり、事後に使用されるWhere句は、サーバーで実行されるSQLステートメントに変換されます。必要な要素のみをワイヤーに引っ張る...
リードコプシー、

40
@Testing実際にはまだDBにアクセスしていません。結果を実際に列挙する(つまりforeach、を使用する、またはを呼び出すToList())までは、実際にはDBにアクセスしません。
リードコプシー、2012年

188

本質的に、その仕事はIEnumerable<T>-クエリ可能なデータソースを表す- に似ていますが、さまざまなLINQメソッド(Queryable)がより具体的であり、Expressionデリゲートではなくツリーを使用してクエリを構築します(これが何をEnumerable使用するか)。

式ツリーは、選択したLINQプロバイダーによって検査され、実際のクエリに変換されます。

これは本当にダウンにありElementTypeExpressionそしてProvider-しかし、現実には、あなたはめったにとしてこれを気にする必要はありませんユーザー。残酷な詳細を知る必要があるのはLINQ 実装者だけです。


コメントについて; 例として何が必要かはよくわかりませんが、LINQ-to-SQLを検討してください。ここで中心的なオブジェクトはでありDataContext、これはデータベースラッパーを表します。これには通常、テーブルごとのプロパティ(たとえばCustomers)があり、テーブルはを実装しIQueryable<Customer>ます。しかし、それほど多くは直接使用しません。考慮してください:

using(var ctx = new MyDataContext()) {
    var qry = from cust in ctx.Customers
              where cust.Region == "North"
              select new { cust.Id, cust.Name };
    foreach(var row in qry) {
        Console.WriteLine("{0}: {1}", row.Id, row.Name);
    }
}

これは(C#コンパイラによって)次のようになります。

var qry = ctx.Customers.Where(cust => cust.Region == "North")
                .Select(cust => new { cust.Id, cust.Name });

これも(C#コンパイラによって)次のように解釈されます。

var qry = Queryable.Select(
              Queryable.Where(
                  ctx.Customers,
                  cust => cust.Region == "North"),
              cust => new { cust.Id, cust.Name });

重要なのは、静的メソッドはQueryable式ツリーを取得します。これは、通常のILではなく、オブジェクトモデルにコンパイルされます。たとえば、「どこ」を見るだけで、これは次のようなものになります。

var cust = Expression.Parameter(typeof(Customer), "cust");
var lambda = Expression.Lambda<Func<Customer,bool>>(
                  Expression.Equal(
                      Expression.Property(cust, "Region"),
                      Expression.Constant("North")
                  ), cust);

... Queryable.Where(ctx.Customers, lambda) ...

コンパイラーは私たちのために多くのことをしませんでしたか?このオブジェクトモデルを分解して意味を調べ、TSQLジェネレーターで再び元に戻すことができます。

 SELECT c.Id, c.Name
 FROM [dbo].[Customer] c
 WHERE c.Region = 'North'

(文字列はパラメータとして終わる可能性があります。思い出せません)

デリゲートを使用しただけでは、これは不可能です。そして、これがポイントですQueryable/ IQueryable<T>:それは式ツリーを使用するためのエントリポイントを提供します。

これらはすべて非常に複雑であるため、コンパイラーを使用して簡単に実行できるようにするのは良い仕事です。

詳細については、「C#in Depth」または「LINQ in Action」を参照してください。どちらもこれらのトピックをカバーしています。


2
よろしければ、簡単なわかりやすい例(時間がある場合)で更新してください。
user190560 2009年

「GetQueryableProducts()の定義はどこにありますか?」「?」リード・コプシー氏の返信
Pankaj

表現をクエリに変換する一連の行を楽しんだのは、「ブラックアート自体」...それに対する多くの真実
afreeland

デリゲートを使用した場合、なぜそれが不可能になるのですか?
デビッド・クレンプナー、

1
@Backwards_Daveは、デリゲートが(本質的に)ILを指し、ILはSQLを構築するためにインテントを十分に分解しようとすることを合理的にするほど十分に表現力がないためです。ILは、あまりにも多くのことを許可します。つまり、ILで表現できるほとんどのことは、SQLのようなものに変換するのが妥当な限られた構文では表現できませんでした
マークグラベル

17

リードCopseyマルクGravellはすでにについて説明したIQueryable(ともIEnumerable)十分な、MIは上の小さな例を提供することで、もう少しここに追加したいIQueryableIEnumerable多くのユーザーがそれを求めて

:データベースに2つのテーブルを作成しました

   CREATE TABLE [dbo].[Employee]([PersonId] [int] NOT NULL PRIMARY KEY,[Gender] [nchar](1) NOT NULL)
   CREATE TABLE [dbo].[Person]([PersonId] [int] NOT NULL PRIMARY KEY,[FirstName] [nvarchar](50) NOT NULL,[LastName] [nvarchar](50) NOT NULL)

PersonIdテーブルの主キー()もテーブルEmployeeの偽造キー(personid)ですPerson

次に、アプリケーションにado.netエンティティモデルを追加し、その下にサービスクラスを作成します

public class SomeServiceClass
{   
    public IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable(IEnumerable<int> employeesToCollect)
    {
        DemoIQueryableEntities db = new DemoIQueryableEntities();
        var allDetails = from Employee e in db.Employees
                         join Person p in db.People on e.PersonId equals p.PersonId
                         where employeesToCollect.Contains(e.PersonId)
                         select e;
        return allDetails;
    }

    public IEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable(IEnumerable<int> employeesToCollect)
    {
        DemoIQueryableEntities db = new DemoIQueryableEntities();
        var allDetails = from Employee e in db.Employees
                         join Person p in db.People on e.PersonId equals p.PersonId
                         where employeesToCollect.Contains(e.PersonId)
                         select e;
        return allDetails;
    }
}

同じlinqが含まれています。program.cs以下で定義されているように呼び出されました

class Program
{
    static void Main(string[] args)
    {
        SomeServiceClass s= new SomeServiceClass(); 

        var employeesToCollect= new []{0,1,2,3};

        //IQueryable execution part
        var IQueryableList = s.GetEmployeeAndPersonDetailIQueryable(employeesToCollect).Where(i => i.Gender=="M");            
        foreach (var emp in IQueryableList)
        {
            System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
        }
        System.Console.WriteLine("IQueryable contain {0} row in result set", IQueryableList.Count());

        //IEnumerable execution part
        var IEnumerableList = s.GetEmployeeAndPersonDetailIEnumerable(employeesToCollect).Where(i => i.Gender == "M");
        foreach (var emp in IEnumerableList)
        {
           System.Console.WriteLine("ID:{0}, EName:{1},Gender:{2}", emp.PersonId, emp.Person.FirstName, emp.Gender);
        }
        System.Console.WriteLine("IEnumerable contain {0} row in result set", IEnumerableList.Count());

        Console.ReadKey();
    }
}

両方の出力は明らかに同じです

ID:1, EName:Ken,Gender:M  
ID:3, EName:Roberto,Gender:M  
IQueryable contain 2 row in result set  
ID:1, EName:Ken,Gender:M  
ID:3, EName:Roberto,Gender:M  
IEnumerable contain 2 row in result set

だから問題は何が/どこに違いがあるのですか?違いはないようですね?本当に!!

これらの期間中にエンティティフレームワーク5によって生成および実行されたSQLクエリを見てみましょう

IQueryable実行部分

--IQueryableQuery1 
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])

--IQueryableQuery2
SELECT 
[GroupBy1].[A1] AS [C1]
FROM ( SELECT 
    COUNT(1) AS [A1]
    FROM [dbo].[Employee] AS [Extent1]
    WHERE ([Extent1].[PersonId] IN (0,1,2,3)) AND (N'M' = [Extent1].[Gender])
)  AS [GroupBy1]

IEnumerable実行部分

--IEnumerableQuery1
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)

--IEnumerableQuery2
SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[Gender] AS [Gender]
FROM [dbo].[Employee] AS [Extent1]
WHERE [Extent1].[PersonId] IN (0,1,2,3)

両方の実行部分に共通のスクリプト

/* these two query will execute for both IQueryable or IEnumerable to get details from Person table
   Ignore these two queries here because it has nothing to do with IQueryable vs IEnumerable
--ICommonQuery1 
exec sp_executesql N'SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=1

--ICommonQuery2
exec sp_executesql N'SELECT 
[Extent1].[PersonId] AS [PersonId], 
[Extent1].[FirstName] AS [FirstName], 
[Extent1].[LastName] AS [LastName]
FROM [dbo].[Person] AS [Extent1]
WHERE [Extent1].[PersonId] = @EntityKeyValue1',N'@EntityKeyValue1 int',@EntityKeyValue1=3
*/

さて、あなたは今いくつかの質問を持っています、私がそれらを推測してそれらに答えようとしましょう

同じ結果に対して異なるスクリプトが生成されるのはなぜですか?

ここでいくつかのポイントを見つけましょう

すべてのクエリには1つの共通部分があります

WHERE [Extent1].[PersonId] IN (0,1,2,3)

どうして?関数IQueryable<Employee> GetEmployeeAndPersonDetailIQueryableIEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerableの両方SomeServiceClassがlinqクエリの1つの共通行を含むため

where employeesToCollect.Contains(e.PersonId)

なぜ実行部分にAND (N'M' = [Extent1].[Gender])欠落しているのか IEnumerable、どちらの関数呼び出しでもWhere(i => i.Gender == "M") inprogram.cs を使用したのか

今、私たちは違いが間に来た時点であるIQueryableIEnumerable

IQueryableメソッドが呼び出されたときにエンティティフレームワークが行うことは、メソッド内に記述されたlinqステートメントを取得し、結果セットにさらにlinq式が定義されているかどうかを調べ、結果がより適切なsqlをフェッチして構築するまで、定義されているすべてのlinqクエリを収集します実行するクエリ。

それはのような多くの利点を提供します、

  • linqクエリの実行全体で有効である可能性がある、SQLサーバーによって入力された行のみ
  • 不要な行を選択しないことでSQLサーバーのパフォーマンスを向上
  • ネットワークコストが削減されます

ここの例のように、SQLサーバーはIQueryableの実行後に2行だけをアプリケーションに返しましたが、IEnumerableクエリに対して3行を返しました。なぜですか?

IEnumerableメソッドの場合 、エンティティフレームワークはメソッド内に記述されたlinqステートメントを取得し、結果をフェッチする必要があるときにSQLクエリを構築します。SQLクエリを構築するためのREST LINQ部分は含まれていません。このように、SQLサーバーの列ではフィルタリングは行われませんgender

しかし、出力は同じですか?'IEnumerableは、SQLサーバーから結果を取得した後、アプリケーションレベルで結果をさらにフィルター処理するため

だから、誰かが何を選ぶべきですか?私は個人的に関数の結果を定義することを好みます。IQueryable<T>それは、それよりも多くの利点があるためIEnumerable、SQLサーバーに特定のスクリプトを生成する2つ以上のIQueryable関数を結合できるからです。

ここの例では、私の見解ではIQueryable Query(IQueryableQuery2)、より具体的なスクリプトが生成されていることがわかりIEnumerable query(IEnumerableQuery2)ます。


2

これにより、さらに後のクエリが可能になります。これがサービス境界を超えている場合、このIQueryableオブジェクトのユーザーは、それを使用してさらに多くのことを実行できます。

たとえば、nhibernateで遅延読み込みを使用していた場合、必要なときに/必要に応じてグラフが読み込まれる可能性があります。

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