2009-08-20 22 views
8

Supponiamo di avere un'espressione IQueryable<T> che mi piacerebbe incapsulare la definizione di, archiviarla e riutilizzarla o incorporarla in una query più ampia in seguito. Ad esempio:Come mantenere LINQ esecuzione differita?

Ora credo di poter mantenere l'oggetto myQuery e utilizzarlo come descritto. Ma alcune cose che non sono sicuro:

  1. Il modo migliore per parametrizzare esso? Inizialmente l'ho definito in un metodo e poi ho restituito IQueryable<T> come risultato del metodo. In questo modo posso definire blah e bar come argomenti del metodo e immagino che crei appena un nuovo IQueryable<T> ogni volta. È questo il modo migliore per incapsulare la logica di un IQueryable<T>? Ci sono altri modi?

  2. Cosa succede se la mia query si risolve in uno scalare anziché in IQueryable? Ad esempio, cosa succede se voglio che questa query sia esattamente come mostrato ma aggiungo .Any() per farmi sapere se ci sono risultati corrispondenti? Se aggiungo il (...).Any(), il risultato è bool e viene eseguito immediatamente, giusto? C'è un modo per utilizzare questi operatori Queryable (Any, SindleOrDefault, ecc.) Senza eseguire immediatamente? Come gestisce LINQ-to-SQL?

Edit: Part 2 è davvero più di cercare di capire quali sono le differenze tra di limitazione IQueryable<T>.Where(Expression<Func<T, bool>>) vs. IQueryable<T>.Any(Expression<Func<T, bool>>). Sembra che quest'ultimo non sia così flessibile quando si creano query più ampie in cui l'esecuzione deve essere ritardata. È possibile aggiungere Where() e quindi altri costrutti possono essere successivamente aggiunti e infine eseguiti. Poiché Any() restituisce un valore scalare, sembra che verrà eseguito immediatamente prima che sia possibile creare il resto della query.

risposta

5
  1. Bisogna essere veramente attenti a passare intorno IQueryables quando si utilizza un DataContext, perché una volta che il contesto get smaltiti non sarà in grado di eseguire su quel IQueryable più. Se non stai usando un contesto, potresti essere ok, ma sii consapevole di ciò.

  2. . Qualsiasi() e .FirstOrDefault() sono non differiti. Quando li chiami, lo causerà l'esecuzione. Tuttavia, questo non può fare ciò che pensi che faccia. Ad esempio, in LINQ to SQL se si esegue un .Any() su un IQueryable esso agisce come IF EXISTS (SQL HERE) in pratica.

È possibile IQueryable catena di lungo come questo se si vuole:

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

Sembra in # 1, avere un metodo che essenzialmente costruisce un nuovo 'IQueryable' ogni volta è una * cosa buona * poiché in questo modo non mi imbatterò in problemi con lo smaltimento. In # 2, sono confuso da come LINQ-to-SQL può tradurre l'operatore 'Any', ma non posso differire. Se dovessi usare un operatore 'Any' all'interno di una query più grande, viene eseguito immediatamente anche lì, o fa parte dell'esecuzione della query più grande? – mckamey

+0

OK Penso di esserci quasi. Se dovessi incorporare un '.Any()' in una clausola 'where' allora non lo eseguirà in un ciclo, corretto? Dovrebbe compilare l'espressione SQL appropriata e inviarlo. Quindi, in effetti, non è '. Any()' che impedisce l'esecuzione differita in quanto è come viene utilizzato. Fondamentalmente se il risultato di una query * whole * è uno scalare, allora le figure del compilatore ti richiedono il risultato ora piuttosto che continuare con la creazione di un 'IQueryable '. – mckamey

+1

@McKAMEY corretto, non appena si utilizza .Any() in un contesto che non è deferrabile, verrà eseguito. Nel caso di .Where() sta cercando un'espressione, che è deferrabile, quindi stai bene. Nel caso di var o del ciclo foreach, quelli causano l'esecuzione perché non sono differibili. – Joseph

2

una scelta molto migliore di quanto il caching degli oggetti IQueryable è quello di memorizzare nella cache alberi di espressione. Tutti gli oggetti IQueryable hanno una proprietà chiamata Expression (credo), che rappresenta l'albero di espressioni corrente per quella query.

In un secondo momento, è possibile ricreare la query chiamando queryable.Provider.CreateQuery (espressione) o direttamente su qualsiasi sia il provider (nel caso specifico, un contesto dati Linq2Sql).

La parametrizzazione di questi alberi di espressioni è leggermente più difficile, in quanto utilizzano ConstantExpressions per creare un valore. Per parametrizzare queste query, dovrai ricreare la query ogni volta che vuoi parametri diversi.

+1

Direi che la parametrizzazione (o, più importante, l'incapsulamento di una singola unità di logica) è il vero obiettivo qui piuttosto che il caching. Considerando che il compilatore C# lo ha già convertito, non penso che un runtime equivalente sarebbe di grande utilità/prestazioni (come è il significato di ciò che implica la memorizzazione nella cache). – mckamey

+0

Puoi spiegare perché questa opzione è migliore? Per quanto ho capito, basta scartare l'espressione 'Expression' per riavvolgerlo in un secondo momento: perché non mantenere il wrapper così com'è? – PPC

1

Any() utilizzato in questo modo è rinviato.

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

Any() utilizzato in questo modo non viene differita, ma è ancora tradotto a SQL (l'intera tabella clienti non è caricato in memoria).

bool result = dc.Customers.Any(); 

Se si desidera una differita Qualsiasi(), fare in questo modo:

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

che si chiama in questo modo:

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

Lo svantaggio è che questa tecnica non sarà consentire la sottocomponente.

+0

Quando dici differito, sembra che tu voglia dire che posso definire un'espressione lambda (o delegata) ma non eseguirla immediatamente. Penso che ci sia una sottigliezza al concetto di LINQ di "esecuzione differita" che consente all'operazione di diventare parte di un albero di espressioni più grande, che può essere interpretato dal provider come vuole. La mia domanda è di più cercando di capire quali sono le differenze di limite tra 'IQueryable .Where (Espressione >)' vs.'IQueryable . Qualsiasi (Espressione >)' come sembra che quest'ultimo non sia flessibile. – mckamey

+1

Questa inflessibilità deriva dalla differenza nei tipi di ritorno. Il tipo di nome "bool" non può avere un comportamento differito. –

+0

questo suggerimento mi sembra poco sicuro (considera DataContext) –

0

Creare un'applicazione parziale della query all'interno di un'espressione

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

e quindi si può utilizzare da ...

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

La query potrebbe essere incapsulata all'interno di una classe, se si desidera renderlo più portabile/riutilizzabile.

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; 
    } 
} 
Problemi correlati