web-dev-qa-db-ja.com

LinqでIQueryableを使う

LINQの文脈でのIQueryableの使用は何ですか?

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

238
user190560

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


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

たとえば、多くのORMシステムでリモートデータベースを操作している場合、IEnumerable<T>を返すものとIQueryable<T>を返すものの2つの方法でテーブルからデータを取得することができます。たとえば、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つのレコードのみをネットワーク経由で返すことができます。

471
Reed Copsey

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

式ツリーは、選択したLINQプロバイダーによって検査され、actualquery - )に変換されます。ただし、それ自体はブラックアートです。

これは本当にElementTypeExpression、およびProviderにかかっています - しかし実際にはあなたはまれに{これをユーザーとして気にする必要はありません。) LINQ implementsゴーリーの詳細を知る必要があります。


コメントを再;私はあなたが例として欲しいものがよくわからないが、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ではなくオブジェクトモデルにコンパイルされます。たとえば、「Where」を見ると、これは次のようなものになります。

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'

(文字列はパラメータとして終わるかもしれません。覚えていません)

デリゲートを使用したばかりでは、これは不可能です。そしてthisQueryable/IQueryable<T>のポイントです:それは式ツリーを使うためのエントリーポイントを提供します。

これはすべて非常に複雑なので、コンパイラーがそれを実現するのは良い仕事です。

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

176
Marc Gravell

Reed Copsey および Marc GravellIQueryablename__(およびIEnumerablename__)についてすでに説明していますが、mIQueryablename__およびIEnumerablename__の小さな例を示すことで、ここでもう少し説明を加えます。それを求めた

:データベースに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)

テーブルPersonIdname__の主キー(Employeename__)も、テーブルpersonidname__の偽造キー(Personname__)です。

次に、私は自分のアプリケーションに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)

どうして? SomeServiceClassname__の関数IQueryable<Employee> GetEmployeeAndPersonDetailIQueryableIEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerableの両方に、linqクエリに共通の1行が含まれているため

where employeesToCollect.Contains(e.PersonId)

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

IQueryablename__とIEnumerablename__の間に違いが生じたところです。

IQueryablename__メソッドが呼び出されたときにエンティティフレームワークが行うことは、メソッド内にlinqステートメントが書かれているかどうかを調べ、結果セットにもっと多くのlinq式が定義されているかどうかを調べることです。実行するSQLクエリ。

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

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

この例のように、SQL ServerはIQueryableの実行後に2行だけアプリケーションに返しましたが、IEnumerableクエリでは3行を返しました。

IEnumerablename__メソッドの場合、エンティティフレームワークはメソッド内に書かれたlinqステートメントを受け取り、結果を取得する必要があるときにSQLクエリを構築します。 SQLクエリを構築するためのrest linq部分は含まれません。このように、SQL Serverのgendername__列ではフィルタリングは行われません。

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

SO、誰かが選ぶべきものは? IEnumerablename__に比べて多くの利点があるので、私は個人的にはIQueryable<T>として関数の結果を定義することを好みます、あなたがSQLサーバーにもっと特定のスクリプトを生成する2つ以上のIQueryable関数を結合することができます。

この例では、IQueryable Query(IQueryableQuery2)IEnumerable query(IEnumerableQuery2)よりも具体的なスクリプトを生成することがわかります。

13
Moumit

それはさらに下の行でさらに問い合わせをすることを可能にします。これがサービス境界を超えていると言うならば、このIQueryableオブジェクトのユーザはそれを使ってより多くのことをすることが許されるでしょう。

例えば、もしあなたが遅延ローディングをnhibernateと共に使用していたならば、これは必要な時に/必要ならばグラフがロードされる結果になるかもしれません。

1
dove