2009-08-20 10 views
8

私はIQueryable<T>の式を定義し、その定義をカプセル化して保存して再利用したり、後で大きなクエリに埋め込んだりしたいとします。例:LINQの遅延実行を維持する方法は?

IQueryable<Foo> myQuery = 
    from foo in blah.Foos 
    where foo.Bar == bar 
    select foo; 

ここで、myQueryオブジェクトをそのまま使用して、説明したように使用できます。それをパラメータ化する

  1. 最善の方法:しかし、いくつかのものは私はわからないんだけど?最初にメソッドでこれを定義し、メソッドの結果としてIQueryable<T>を返しました。この方法では、メソッドの引数としてblahbarを定義することができ、毎回新しいIQueryable<T>を作成すると思います。これはIQueryable<T>のロジックをカプセル化する最良の方法ですか?他の方法はありますか?

  2. クエリがIQueryableではなくスカラに変換されるとどうなりますか?たとえば、このクエリを正確に表示するには、.Any()を追加して、一致する結果があるかどうかを教えてください。 (...).Any()を追加すると、結果はboolとなり、直ちに実行されます。これらのQueryable演算子(AnySindleOrDefaultなど)をすぐに実行することなく利用する方法はありますか? LINQ-to-SQLはこれをどのように処理しますか?

編集:パート2はIQueryable<T>.Where(Expression<Func<T, bool>>)IQueryable<T>.Any(Expression<Func<T, bool>>)間の制限の違いが何であるかを理解しようとについて本当に多くのです。後者は、実行が遅れる大きなクエリを作成するときに柔軟性がないように見えます。 Where()を追加して、後で他の構造を追加してから最後に実行することができます。 Any()はスカラー値を返すので、クエリの残りの部分が構築される前にすぐに実行されるように聞こえます。

答えて

5
  1. あなたは、コンテキスト一度あなたがもうそののIQueryable上で実行することができません配置されますので、あなたはDataContextのを使用しているときIQueryablesの周りに渡すことについては本当に注意する必要があります。コンテキストを使用していない場合は、大丈夫かもしれませんが、それに注意してください。

  2. .Any()および.FirstOrDefault()は、ではなく、が遅延しています。あなたがそれらを呼び出すと、になり、実行が行われます。しかし、これはあなたが思っていることをしないかもしれません。たとえば、LINQ to SQLでは、IQueryableで.Any()を実行すると、基本的にIF EXISTS(SQL HERE)として機能します。あなたはこのように沿ってチェーンのIQueryableさん、あなたがしたいことができるかどう

:のIQueryableオブジェクトをキャッシュより

var firstQuery = from f in context.Foos 
        where f.Bar == bar 
        select f; 

var secondQuery = from f in firstQuery 
        where f.Bar == anotherBar 
        orderby f.SomeDate 
        select f; 

if (secondQuery.Any()) //immediately executes IF EXISTS(second query in SQL) 
{ 
    //causes execution on second query 
    //and allows you to enumerate through the results 
    foreach (var foo in secondQuery) 
    { 
     //do something 
    } 

    //or 

    //immediately executes second query in SQL with a TOP 1 
    //or something like that 
    var foo = secondQuery.FirstOrDefault(); 
} 
+0

#1のように、新しいIQueryableを基本的に構築する方法は、たぶん*良いことです*ので、私は廃棄の問題に遭遇しません。 #2では、LINQ-to-SQLが「Any」演算子をどのように変換できるのか混乱していますが、延期することはできません。より大きなクエリの中で 'Any'演算子を使用するのであれば、すぐにそこで実行されるのでしょうか、それともより大きなクエリの実行の一部ですか? – mckamey

+0

OK私はほとんどそこにいると思う。 '.Any()'を 'where'節に埋め込むと、それはループ内で実行されませんでしたか?適切なSQL式にコンパイルし、それを送信します。したがって実際には、どのように使用されているかのように、遅延実行を防ぐ '.Any()'ではありません。基本的には、*全体*クエリの結果がスカラーの場合、コンパイラは 'IQueryable 'を構築するのではなく、今結果を必要としていると判断します。 – mckamey

+1

@McKAMEYは正しいです。延期できないコンテキストで.Any()を使用すると、すぐに実行されます。 .Where()の場合、それは延期可能な式を探しているので、大丈夫です。 varまたはforeachループの場合は、遅延が発生しないため実行されます。 – Joseph

2

より良いオプションは、式ツリーをキャッシュすることです。すべてのIQueryableオブジェクトには、そのクエリの現在の式ツリーを表すExpression(I believe)というプロパティがあります。

後で、queryable.Provider.CreateQuery(式)を呼び出すか、プロバイダが何であれ(Linq2Sqlデータコンテキスト)を直接呼び出して、クエリを再作成することができます。

しかし、これらの式ツリーをパラメータ化することは、ConstantExpressionsを使用して値を構築するため、やや難しくなります。これらのクエリをパラメータ化するには、異なるパラメータが必要なたびにクエリを再構築する必要があります。

+1

私は、パラメータ化(より重要なのは、ロジックの単一のユニットをカプセル化すること)が、キャッシュではなく実際の目標であると言います。 C#コンパイラがすでにそれを変換していることを考慮すると、ランタイムの同等性は、(キャッシングが暗示しているように)多くの使用/パフォーマンスの利点であるとは思わない。 – mckamey

+0

このオプションが優れている理由を説明できますか?私が理解する限り、 'Expression'をアンラップして後でそれを再ラップすることができます:なぜラッパーをそのままにしないのですか? – PPC

1

Any()このように使用されます。

var q = dc.Customers.Where(c => c.Orders.Any()); 

Any()この方法を使用し延期されていないが、それでも(全顧客テーブルがメモリにロードされていません)SQLに変換されます。

bool result = dc.Customers.Any(); 

あなたが延期どれを(必要な場合)は、それをこのように実行します。欠点は、この技術はないということです

Func<bool> f = dc.Customers.DeferredAny(); 
bool result = f(); 

public static class QueryableExtensions 
{ 
    public static Func<bool> DeferredAny<T>(this IQueryable<T> source) 
    { 
    return() => source.Any(); 
    } 
} 

は、このように呼ばれていますサブクエリを許可します。

+0

遅延を言うと、ラムダ式(またはデリゲート)を定義することはできますが、すぐには実行できないということになります。私はLINQの "遅延実行"という概念に微妙な違いがあると考えています。これは、操作がより大きな表現ツリーの一部になることを可能にします。 私の質問は、 'IQueryable .Where(Expression >)と後者は柔軟ではないように見えるので、 'IQueryable .Any(Expression >) – mckamey

+1

その柔軟性は戻り値の型の違いに由来します。 'bool'という名前の型は、遅延動作を持たないことがあります。 –

+0

この提案は私にとってはスレッドセーフではないようです(DataContextを考慮してください) –

0

あなたがしたい場合は、式の中

Func[Bar,IQueryable[Blah],IQueryable[Foo]] queryMaker = 
(criteria, queryable) => from foo in queryable.Foos 
     where foo.Bar == criteria 
     select foo; 

をクエリの一部のアプリケーションを作成し、あなたがすることによって、それを使用することができます...

IQueryable[Blah] blah = context.Blah; 
Bar someCriteria = new Bar(); 
IQueryable[Foo] someFoosQuery = queryMaker(blah, someCriteria); 

クエリがクラス内にカプセル化することができよりポータブルで再利用可能にする。

public class FooBarQuery 
{ 
    public Bar Criteria { get; set; } 

    public IQueryable[Foo] GetQuery(IQueryable[Blah] queryable) 
    { 
    return from foo in queryable.Foos 
     where foo.Bar == Criteria 
     select foo; 
    } 
} 
関連する問題