2009-08-03 10 views
36

Hemos encontrado que compiling our Linq queries es mucho, mucho más rápido que tener que compilar cada vez, por lo que nos gustaría comenzar a utilizar consultas compiladas. El problema es que hace que el código sea más difícil de leer, porque la sintaxis real de la consulta está desactivada en algún otro archivo, lejos de donde se está utilizando.Compilar automáticamente consultas de Linq

Se me ocurrió que podría ser posible escribir un método (o método de extensión) que utilizara la reflexión para determinar qué consultas se pasan y almacenar en caché las versiones compiladas automáticamente para usarlas en el futuro.

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

Cached() tendría que reflejar el objeto de consulta aprobada en y determinar la tabla (s) seleccionado en y los tipos de parámetros de la consulta. Obviamente, la reflexión es un poco lenta, por lo que podría ser mejor usar nombres para el objeto de caché (pero aún tendría que usar la reflexión la primera vez para compilar la consulta).

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

¿Alguien tiene alguna experiencia con hacer esto, o sabe si es posible?

ACTUALIZACIÓN: Para aquellos que no lo han visto, se puede compilar LINQ consulta a SQL con el siguiente código:

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) 
     ); 
} 

Lo que estoy tratando de hacer es tener una memoria caché de éstos Func<> objetos a los que puedo llamar después de compilar automáticamente la consulta la primera vez.

+1

Esta es una pregunta confusa porque parece que está combinando LINQ y LINQ con SQL (que además genera, compila y almacena en caché los planes de ejecución detrás de escena cada vez que se ejecuta una consulta). Si está preguntando sobre los planes de ejecución compilados de SQL Server, no hay forma (que yo sepa) de compilarlos y mantenerlos en la memoria caché que no sean ejecutarlos. – 48klocs

+0

Esto no tiene nada que ver con SQL Server. LINQ to SQL compila consultas (que pueden llevar bastante tiempo) desde las dos sintaxis de LINQ (encadenamiento o estilo SQL) hasta SQL cada vez que se ejecutan esas consultas. Lea el enlace en la parte superior para obtener más información. – tghw

+0

Un problema que he encontrado con el uso de consultas compiladas con L2S en una aplicación web es que para compilarlo debes pasar la instancia de DataContext; para una aplicación web, esto significa que necesitas un DataContext compartido para todo el sitio, que a cambio, me causó algunos problemas principales de múltiples hilos cuando el sitio comenzó a tener una gran carga. Realmente no me gusta cómo debe pasar la instancia del contexto de datos al compilar la consulta ... – kastermester

Respuesta

18

No se pueden invocar métodos de extensión en expresiones lambda anónimas, por lo que querrá usar una clase Cache. Para cachear correctamente una consulta, también tendrá que "levantar" cualquier parámetro (incluido su DataContext) en parámetros para su expresión lambda.Esto se traduce en un uso muy prolijo como:

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

Con el fin de limpiar eso, podemos crear instancias de un QueryCache en función de cada contexto si lo hacemos no estático:

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

Luego puede escribir un método de caché que nos permitirá escribir la siguiente: también tendrán que ser levantado

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

Cualquier argumento en su consulta:

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

Aquí está la aplicación QueryCache me burlé de arriba:

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); 
    } 
} 

Esto se puede ampliar para que admita más argumentos. Lo mejor de todo es que al pasar los valores de los parámetros al método Cache mismo, se obtiene el tipeo implícito para la expresión lambda.

EDIT: Tenga en cuenta que no se puede aplicar nuevos operadores a las consultas compiladas .. En concreto no se puede hacer algo como esto:

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

Así que si usted planea en la paginación de una consulta, es necesario hacerlo en el compilar la operación en lugar de hacerlo más tarde. Esto es necesario no solo para evitar una excepción, sino también para cumplir con el objetivo de Saltar/Tomar (para evitar devolver todas las filas de la base de datos). Este patrón funciona:

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); 
} 

Otro enfoque para paginación habría que devolver 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); 
} 

Este patrón se utiliza como:

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

Esto es casi exactamente lo mismo que comencé a trabajar. El único problema que veo es que llamar a q.ToString() provocaría que la consulta se compilara de todos modos, ya que ToString() genera el SQL parametrizado. ¿Me estoy perdiendo de algo? – tghw

+1

tenga cuidado con .ToString() si cambia los nombres de las variables pero la expresión LINQ es la misma, se cambiará el ToString y, por lo tanto, la clave será diferente. Por lo tanto, compilará una nueva consulta. –

+2

@tghw: .ToString() no es un problema; stringifica la expresión lambda y no el SQL resultante, es decir, "db => db.Foo.Where (x =>! x.IsDisabled)". Lo verifiqué localmente en un proyecto de MVC. @Stan: eso no es una preocupación real, ya que es probable que tenga N consultas literales en el código frente a M * N veces que se invocan esas consultas. – Jason

2

Como nadie lo intenta, le daré una oportunidad. Tal vez ambos podamos resolver esto de alguna manera. Aquí está mi intento de esto.

Configuré esto usando un diccionario, tampoco estoy usando DataContext aunque esto es trivial, creo.

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

ahora esto me permite hacer esta

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); 

ganas de cierta discusión sobre esto, para desarrollar aún más esta idea.

+0

predeterminado (IEnumerable ); == nulo; en todos los casos. Estás incumpliendo una interfaz. – Dykam

+0

oops..cienme error.Tenía un código diferente allí y simplemente copié y pegué mi código y no lo revisé. Gracias por notarlo. –

+0

Esto no hace lo que crees que hace. Si tiene una 'Expresión' en un' IEnumerable', no hay diferencia entre expression.Compile() y el código 'Func' IL real que se habría generado si no estuviera pidiendo una' Expresión'. De hecho, exp.Compile() probablemente sea más lento ya que te faltan muchas optimizaciones del compilador. – Jason

1

Para el futuro la posteridad: .NET Framework 4.5 hará esto por defecto (de acuerdo con una diapositiva en una presentación que acabo de ver).

+0

¿Podría proporcionar una fuente apropiada para esta declaración? –

+0

Lo siento, no estoy seguro de por qué no proporcioné una fuente en ese momento; supongo que fue algo que había visto recientemente y que no tenía el enlace a mano –

+0

Ok, ¿es cierto lo que dijiste en el ¿de momento? ¿Se compilan todas las consultas LINQ en .NET 4.5? –

Cuestiones relacionadas