LINQの文脈でのIQueryable
の使用は何ですか?
それは拡張メソッドの開発やその他の目的に使用されますか?
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つのレコードのみをネットワーク経由で返すことができます。
本質的には、その仕事はIEnumerable<T>
- クエリ可能なデータソースを表す - に非常に似ています - 違いは、Queryable
ツリーを使ってクエリを構築するための(Expression
の)さまざまなLINQメソッドがより具体的ですデリゲートよりも(これがEnumerable
が使用するものです)。
式ツリーは、選択したLINQプロバイダーによって検査され、actualquery - )に変換されます。ただし、それ自体はブラックアートです。
これは本当にElementType
、Expression
、および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'
(文字列はパラメータとして終わるかもしれません。覚えていません)
デリゲートを使用したばかりでは、これは不可能です。そしてthisはQueryable
/IQueryable<T>
のポイントです:それは式ツリーを使うためのエントリーポイントを提供します。
これはすべて非常に複雑なので、コンパイラーがそれを実現するのは良い仕事です。
詳細については、「 C#の深さ 」または「 LINQ in Action 」を参照してください。どちらもこれらのトピックを網羅しています。
Reed Copsey および Marc Gravell はIQueryable
name__(およびIEnumerable
name__)についてすでに説明していますが、mIQueryable
name__およびIEnumerable
name__の小さな例を示すことで、ここでもう少し説明を加えます。それを求めた
例:データベースに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
name__の主キー(Employee
name__)も、テーブルpersonid
name__の偽造キー(Person
name__)です。
次に、私は自分のアプリケーションに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)
どうして? SomeServiceClass
name__の関数IQueryable<Employee> GetEmployeeAndPersonDetailIQueryable
とIEnumerable<Employee> GetEmployeeAndPersonDetailIEnumerable
の両方に、linqクエリに共通の1行が含まれているため
where employeesToCollect.Contains(e.PersonId)
なぜAND (N'M' = [Extent1].[Gender])
部分がIEnumerable
name__実行部分に欠けているのですが、どちらの関数呼び出しでもWhere(i => i.Gender == "M") in
program.csを使いました。
IQueryable
name__とIEnumerable
name__の間に違いが生じたところです。
IQueryable
name__メソッドが呼び出されたときにエンティティフレームワークが行うことは、メソッド内にlinqステートメントが書かれているかどうかを調べ、結果セットにもっと多くのlinq式が定義されているかどうかを調べることです。実行するSQLクエリ。
それはのような多くの利点を提供します、
この例のように、SQL ServerはIQueryableの実行後に2行だけアプリケーションに返しましたが、IEnumerableクエリでは3行を返しました。
IEnumerable
name__メソッドの場合、エンティティフレームワークはメソッド内に書かれたlinqステートメントを受け取り、結果を取得する必要があるときにSQLクエリを構築します。 SQLクエリを構築するためのrest linq部分は含まれません。このように、SQL Serverのgender
name__列ではフィルタリングは行われません。
しかし、出力は同じですか? SQL Serverから結果を取得した後、 'IEnumerableは結果をアプリケーションレベルでさらにフィルタリングするため
SO、誰かが選ぶべきものは? IEnumerable
name__に比べて多くの利点があるので、私は個人的にはIQueryable<T>
として関数の結果を定義することを好みます、あなたがSQLサーバーにもっと特定のスクリプトを生成する2つ以上のIQueryable関数を結合することができます。
この例では、IQueryable Query(IQueryableQuery2)
がIEnumerable query(IEnumerableQuery2)
よりも具体的なスクリプトを生成することがわかります。
それはさらに下の行でさらに問い合わせをすることを可能にします。これがサービス境界を超えていると言うならば、このIQueryableオブジェクトのユーザはそれを使ってより多くのことをすることが許されるでしょう。
例えば、もしあなたが遅延ローディングをnhibernateと共に使用していたならば、これは必要な時に/必要ならばグラフがロードされる結果になるかもしれません。