2009-08-20 19 views
8

Załóżmy, że mam wyrażenie IQueryable<T>, że chciałbym hermetyzować definicję, przechowywania i ponownego użycia lub osadzić go w większe zapytanie później. Na przykład:Jak utrzymać odłożoną realizację LINQ?

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

Teraz wierzę, że mogę po prostu zatrzymać obiekt myQuery i używać go tak, jak to opisałem. Ale niektóre rzeczy nie jestem pewien o:

  1. Jak najlepiej go parametryzacji? Początkowo zdefiniowałem to w metodzie, a następnie zwróciłem IQueryable<T> jako wynik tej metody. W ten sposób mogę zdefiniować blah i bar jako argumenty metody i myślę, że po prostu tworzy nowe IQueryable<T> za każdym razem. Czy jest to najlepszy sposób na zamknięcie logiki z IQueryable<T>? Czy są inne sposoby?

  2. Co się stanie, jeśli moje zapytanie zostanie przetworzone na skalar, a nie na IQueryable? Na przykład, jeśli chcę, aby to zapytanie było dokładnie takie, jak pokazano, ale dołącz .Any(), aby dać mi znać, czy są jakieś wyniki, które pasują do siebie? Jeśli dodaję (...).Any(), wynikiem jest bool i natychmiast wykonywane, prawda? Czy istnieje sposób na wykorzystanie tych operatorów Queryable (Any, SindleOrDefault itp.) Bez natychmiastowego wykonania? W jaki sposób LINQ-SQL to obsługuje?

Edit: Część 2 jest naprawdę więcej o starając się zrozumieć, jakie są różnice między IQueryable<T>.Where(Expression<Func<T, bool>>) ograniczenia vs. IQueryable<T>.Any(Expression<Func<T, bool>>). Wydaje się, że ta ostatnia nie jest tak elastyczna przy tworzeniu większych zapytań, w których wykonanie ma być opóźnione. Where() można dołączyć, a następnie inne konstrukcje mogą być później dołączane, a następnie ostatecznie wykonane. Ponieważ Any() zwraca wartość skalarną, wydaje się, że zostanie ona natychmiast wykonana, zanim będzie można zbudować pozostałą część zapytania.

Odpowiedz

5
  1. Trzeba być bardzo ostrożnym, przechodząc wokół IQueryables gdy używasz DataContext, ponieważ raz kontekst get wyrzucać nie będzie w stanie wykonać na tym IQueryable więcej. Jeśli nie używasz kontekstu, możesz być w porządku, ale bądź tego świadomy.

  2. . Any() i .FirstOrDefault() są nie odroczone. Gdy je wywołasz, będą powodowały wykonanie. Może to jednak nie robić tego, co myślisz. Na przykład, w LINQ do SQL, jeśli wykonujesz .Any() na IQueryable, to zasadniczo działa jako IF EXISTS (SQL TUTAJ).

Można IQueryable łańcuchowe i wzdłuż tak, jeśli chcesz:

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

To brzmi jak w # 1, ponieważ metoda, która w zasadzie tworzy nowe 'IQueryable' za każdym razem jest * dobrą rzeczą *, ponieważ w ten sposób nie napotkasz problemów ze zbieraniem. W # 2, jestem zdezorientowany, jak LINQ-SQL może tłumaczyć operatora Any, ale nie mogę odroczyć. Jeśli miałbym użyć operatora Any w większym zapytaniu, czy został on tam natychmiastowo wykonany, czy jest to część większego wykonania zapytania? – mckamey

+0

OK Myślę, że już prawie jestem. Gdybym miał osadzić '.Any()' w klauzuli 'where', to nie wykonałoby tego w pętli, prawda? Kompiluje się do odpowiedniego wyrażenia SQL i wyśle ​​je w dół. W efekcie nie jest to '.Any()', który zapobiega odroczeniu wykonania, ponieważ jest to w jaki sposób jest używany. Zasadniczo, jeśli wynik kwerendy * całkowitej * jest skalarem, wówczas kompilator podaje wynik, a nie kontynuuje budowania 'IQueryable '. – mckamey

+1

@McKAMEY poprawne, jak tylko użyjesz. Any() w kontekście, który nie zostanie odroczony, to zostanie wykonany. W przypadku .Where() szuka wyrażenia, które jest odraczane, więc wszystko w porządku. W przypadku var lub pętli foreach te przyczyny powodują, że nie są one odłożone. – Joseph

2

O wiele lepszym rozwiązaniem niż buforowanie IQueryable obiektów jest buforowanie wyrażenia drzew. Wszystkie obiekty IQueryable mają właściwość o nazwie Expression (jak sądzę), która reprezentuje bieżące drzewo wyrażeń dla tego zapytania.

W późniejszym czasie można ponownie utworzyć zapytanie, wywołując metodę queryable.Provider.CreateQuery (wyrażenie) lub bezpośrednio na jakimkolwiek dostawcy (w danym przypadku Kontekst danych Linq2Sql).

Parametryzowanie drzewek wyrażeń jest nieco trudniejsze, ponieważ używają one ConstantExpressions do budowania wartości. Aby sparametryzować te zapytania, będziesz musiał przebudować zapytanie za każdym razem, gdy chcesz mieć inne parametry.

+1

Powiedziałbym, że parametryzacja (lub, co ważniejsze, enkapsulacja pojedynczej jednostki logicznej) jest tutaj rzeczywistym celem, a nie buforowaniem. Biorąc pod uwagę, że kompilator C# już go przekonwertował, nie sądzę, aby odpowiednik środowiska wykonawczego miał wiele korzyści w zakresie wykorzystania/wydajności (jak to sugeruje buforowanie). – mckamey

+0

Czy możesz wyjaśnić, dlaczego ta opcja jest lepsza? O ile dobrze rozumiem, po prostu rozwijasz "Wyrażenie", aby później go zawinąć: dlaczego nie zachować opakowania tak, jak jest? – PPC

1

Any() używany w ten sposób jest odroczony.

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

Any() wykorzystywane w ten sposób nie jest odroczona, ale jest jeszcze przetłumaczony na SQL (cała tabela klienci nie są ładowane do pamięci).

bool result = dc.Customers.Any(); 

Jeśli chcesz odroczone Obojętnie(), zrób to w ten sposób:

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

który nazywa się tak:

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

Minusem jest to, że technika ta nie będzie pozwalają na sub-zapytania.

+0

Kiedy mówisz odłożone, brzmi to tak, jak masz na myśli, mogę zdefiniować wyrażenie lambda (lub delegować), ale nie od razu go wykonać. Myślę, że istnieje subtelność koncepcji LINQ "odroczonego wykonywania", która pozwala operacji stać się częścią większego drzewa wyrażenie, które może być interpretowane przez dostawcę, jak chce. Moje pytanie jest bardziej próbujące dowiedzieć się, jakie są ograniczenia różnic między 'IQueryable .Where (Wyrażenie >)' vs.'IQueryable . Any (Wyrażenie >)' ponieważ wydaje się, że ten ostatni nie jest tak elastyczny. – mckamey

+1

Ta nieelastyczność wynika z różnicy typów zwrotów. Typ o nazwie "bool" nie może mieć odroczonego zachowania. –

+0

ta sugestia wydaje mi się niebezpieczna dla wątków (proszę wziąć pod uwagę DataContext) –

0

Tworzenie częściowego stosowania zapytania wewnątrz wyrażenia

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

i wtedy można go używać przez ...

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

Zapytanie może być zamknięty w klasie, jeśli chcesz uczynić go bardziej przenośnym/wielokrotnego użytku.

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; 
    } 
} 
Powiązane problemy