2009-08-03 18 views
36

Abbiamo riscontrato che lo compiling our Linq queries è molto, molto più veloce di quello che devono compilare ogni volta, quindi vorremmo iniziare a utilizzare le query compilate. Il problema è che rende il codice più difficile da leggere, perché la sintassi effettiva della query è disattivata in qualche altro file, lontano da dove viene utilizzata.Compila automaticamente query Linq

Mi è venuto in mente che potrebbe essere possibile scrivere un metodo (o un metodo di estensione) che utilizza la riflessione per determinare quali query vengono passate e memorizzare automaticamente le versioni compilate per l'uso in futuro.

var foo = (from f in db.Foo where f.ix == bar select f).Cached(); 

Cached() sarebbe da riflettere l'oggetto query passata e determinare il tavolo (s) selezionato sulla ei tipi di parametri per l'interrogazione. Ovviamente, la riflessione è un po 'lenta, quindi potrebbe essere meglio usare i nomi per l'oggetto cache (ma dovresti comunque usare la reflection la prima volta per compilare la query).

var foo = (from f in db.Foo where f.ix == bar select f).Cached("Foo.ix"); 

Qualcuno ha qualche esperienza con questo o sa se è possibile?

UPDATE: Per coloro che non hanno visto, è possibile compilare LINQ interroga to SQL con il seguente codice:

public static class MyCompiledQueries 
{ 
    public static Func<DataContext, int, IQueryable<Foo>> getFoo = 
     CompiledQuery.Compile(
      (DataContext db, int ixFoo) => (from f in db.Foo 
              where f.ix == ixFoo 
              select f) 
     ); 
} 

Quello che sto cercando di fare è avere una cache di questi Func<> oggetti che posso chiamare dopo aver compilato automaticamente la query la prima volta.

+1

Questa è una domanda confusa perché sembra che tu stia confondendo LINQ e LINQ con SQL (che in aggiunta genera, compila e memorizza nella cache i piani di esecuzione dietro le quinte ogni volta che viene eseguita una query). Se stai chiedendo dei piani di esecuzione compilati di SQL Server, non c'è modo (che io sappia) di compilarli e tenerli in cache diversi da quelli in esecuzione. – 48klocs

+0

Questo non ha nulla a che fare con SQL Server. LINQ to SQL compila query - che possono richiedere un po 'di tempo - da entrambe le sintassi LINQ (concatenamento o stile SQL) a SQL ogni volta che vengono eseguite tali query. Leggi il link in alto per saperne di più. – tghw

+0

Un problema che ho riscontrato con l'utilizzo di query compilate con L2S in un'app web è che per compilarlo è necessario passare l'istanza di DataContext - per un'applicazione Web ciò significa che è necessario un DataContext condiviso per l'intero sito, che in cambio mi hanno causato alcuni problemi con molti thread multipli quando il sito ha iniziato ad avere un grosso carico. Non mi piace davvero come si debba passare l'istanza di datacontext quando si compila la query ... – kastermester

risposta

18

Non è possibile richiamare metodi di estensione su espressioni lambda anonime, quindi è consigliabile utilizzare una classe cache. Per poter archiviare correttamente una query è necessario anche "sollevare" qualsiasi parametro (incluso il DataContext) in parametri per l'espressione lambda.Ciò si traduce in un uso molto dettagliata come:

var results = QueryCache.Cache((MyModelDataContext db) => 
    from x in db.Foo where !x.IsDisabled select x); 

Per pulire che fino, siamo in grado di creare un'istanza di un QueryCache su una base per-contesto se facciamo non statico:

public class FooRepository 
{ 
    readonly QueryCache<MyModelDataContext> q = 
     new QueryCache<MyModelDataContext>(new MyModelDataContext()); 
} 

Poi siamo in grado di scrivere un metodo di cache, che ci permetterà di scrivere la seguente: dovrà anche essere sollevato

var results = q.Cache(db => from x in db.Foo where !x.IsDisabled select x); 

Qualsiasi argomento nella query:

012.
var results = q.Cache((db, bar) => 
    from x in db.Foo where x.id != bar select x, localBarValue); 

Ecco l'implementazione QueryCache ho preso in giro fino:

public class QueryCache<TContext> where TContext : DataContext 
{ 
    private readonly TContext db; 
    public QueryCache(TContext db) 
    { 
     this.db = db; 
    } 

    private static readonly Dictionary<string, Delegate> cache = new Dictionary<string, Delegate>(); 

    public IQueryable<T> Cache<T>(Expression<Func<TContext, IQueryable<T>>> q) 
    { 
     string key = q.ToString(); 
     Delegate result; 
     lock (cache) if (!cache.TryGetValue(key, out result)) 
     { 
      result = cache[key] = CompiledQuery.Compile(q); 
     } 
     return ((Func<TContext, IQueryable<T>>)result)(db); 
    } 

    public IQueryable<T> Cache<T, TArg1>(Expression<Func<TContext, TArg1, IQueryable<T>>> q, TArg1 param1) 
    { 
     string key = q.ToString(); 
     Delegate result; 
     lock (cache) if (!cache.TryGetValue(key, out result)) 
     { 
      result = cache[key] = CompiledQuery.Compile(q); 
     } 
     return ((Func<TContext, TArg1, IQueryable<T>>)result)(db, param1); 
    } 

    public IQueryable<T> Cache<T, TArg1, TArg2>(Expression<Func<TContext, TArg1, TArg2, IQueryable<T>>> q, TArg1 param1, TArg2 param2) 
    { 
     string key = q.ToString(); 
     Delegate result; 
     lock (cache) if (!cache.TryGetValue(key, out result)) 
     { 
      result = cache[key] = CompiledQuery.Compile(q); 
     } 
     return ((Func<TContext, TArg1, TArg2, IQueryable<T>>)result)(db, param1, param2); 
    } 
} 

Questo può essere esteso per supportare più argomenti. Il bello è che passando i valori dei parametri nel metodo Cache stesso, si ottiene una digitazione implicita per l'espressione lambda.

EDIT: Si noti che non è possibile applicare nuovi operatori alle domande compilate .. In particolare, non si può fare qualcosa di simile:

var allresults = q.Cache(db => from f in db.Foo select f); 
var page = allresults.Skip(currentPage * pageSize).Take(pageSize); 

Quindi, se si ha intenzione di paging una query, è necessario farlo nel compila operazione invece di farlo in seguito. Ciò è necessario non solo per evitare un'eccezione, ma anche per mantenere l'intero punto di Skip/Take (per evitare di restituire tutte le righe dal database). Questo modello potrebbe funzionare:

public IQueryable<Foo> GetFooPaged(int currentPage, int pageSize) 
{ 
    return q.Cache((db, cur, size) => (from f in db.Foo select f) 
     .Skip(cur*size).Take(size), currentPage, pageSize); 
} 

Un altro approccio per il paging sarebbe quella di restituire un Func:

public Func<int, int, IQueryable<Foo>> GetPageableFoo() 
{ 
    return (cur, size) => q.Cache((db, c, s) => (from f in db.foo select f) 
     .Skip(c*s).Take(s), c, s); 
} 

Questo modello è usato come:

var results = GetPageableFoo()(currentPage, pageSize); 
+0

Questo è quasi esattamente lo stesso di come ho iniziato a lavorare. L'unico problema che vedo è che chiamare q.ToString() farebbe sì che la query sia compilata comunque, poiché ToString() emette l'SQL parametrizzato. Mi sto perdendo qualcosa? – tghw

+1

fai attenzione con .ToString() se cambi i nomi delle variabili ma l'espressione LINQ è la stessa, cambierà ToString e quindi la chiave sarà diversa. Compilerà quindi una nuova query. –

+2

@tghw: .ToString() non è un problema; esso specifica l'espressione lambda e non l'SQL risultante, ad esempio "db => db.Foo.Where (x =>! x.IsDisabled)". Ho verificato questo in locale in un progetto MVC. @Stan: non è una vera preoccupazione dato che probabilmente avrai N query letterali nel tuo codice rispetto a M * N volte vengono chiamate quelle query. – Jason

2

Dal momento che nessuno sta tentando, farò un tentativo. Forse possiamo farlo entrambi in qualche modo. Ecco il mio tentativo in questo.

L'ho impostato utilizzando un dizionario, inoltre non sto utilizzando DataContext anche se questo è banale credo.

public static class CompiledExtensions 
    { 
     private static Dictionary<string, object> _dictionary = new Dictionary<string, object>(); 

     public static IEnumerable<TResult> Cache<TArg, TResult>(this IEnumerable<TArg> list, string name, Expression<Func<IEnumerable<TArg>, IEnumerable<TResult>>> expression) 
     { 
      Func<IEnumerable<TArg>,IEnumerable<TResult>> _pointer; 

      if (_dictionary.ContainsKey(name)) 
      { 
       _pointer = _dictionary[name] as Func<IEnumerable<TArg>, IEnumerable<TResult>>; 
      } 
      else 
      { 
       _pointer = expression.Compile(); 
       _dictionary.Add(name, _pointer as object); 
      } 

      IEnumerable<TResult> result; 
      result = _pointer(list); 

      return result; 
     } 
    } 

ora questo mi permette di fare questo

List<string> list = typeof(string).GetMethods().Select(x => x.Name).ToList(); 

    IEnumerable<string> results = list.Cache("To",x => x.Where(y => y.Contains("To"))); 
    IEnumerable<string> cachedResult = list.Cache("To", x => x.Where(y => y.Contains("To"))); 
    IEnumerable<string> anotherCachedResult = list.Cache("To", x => from item in x where item.Contains("To") select item); 

in attesa di qualche discussione su questo, per sviluppare ulteriormente questa idea.

+0

predefinito (IEnumerable ); == null; in tutti i casi. Stai impostando come predefinito un'interfaccia. – Dykam

+0

oops ... errore più probabile.Ho avuto un codice diverso lì e ho appena copiato e incollato il mio codice e non l'ho ricontrollato. grazie per averlo notato –

+0

Questo non fa quello che pensi che faccia. Se hai un 'Expression' su un' IEnumerable' non c'è differenza tra expression.Compile() e il vero codice IL 'Func' che sarebbe stato generato se non stavi chiedendo un' Expression'. Infatti, exp.Compile() molto probabilmente sarà più lento poiché mancano molte ottimizzazioni del compilatore. – Jason

1

Per futuro posteri: .NET Framework 4.5 lo farà di default (secondo una diapositiva in una presentazione che ho appena visto).

+0

Potresti fornire una fonte appropriata per questa affermazione? –

+0

Scusa se non sono sicuro del motivo per cui non ho fornito una fonte al momento - presumo che fosse qualcosa che avevo guardato di recente in quel momento e non avevo il link a portata di mano –

+0

Ok, è vero quello che hai detto al momento per ora? Tutte le query LINQ sono compilate in .NET 4.5? –