Vous ne pouvez pas avoir des méthodes d'extension appelée sur anonyme lambda expressions, de sorte que vous aurez envie d'utiliser une classe de Cache. Afin de bien mettre en cache une requête, vous aurez également besoin d "ascenseur' tous les paramètres (y compris votre DataContext) dans les paramètres de votre expression lambda. Il en résulte très détaillé de l'utilisation, comme:
var results = QueryCache.Cache((MyModelDataContext db) =>
from x in db.Foo where !x.IsDisabled select x);
Afin de nettoyer tout ça, on peut instancier un QueryCache par le contexte, si nous le rendre non-statique:
public class FooRepository
{
readonly QueryCache<MyModelDataContext> q =
new QueryCache<MyModelDataContext>(new MyModelDataContext());
}
Ensuite, nous pouvons écrire un Cache méthode qui va nous permettre d'écrire ce qui suit:
var results = q.Cache(db => from x in db.Foo where !x.IsDisabled select x);
Tous les arguments dans votre requête devra également être levé:
var results = q.Cache((db, bar) =>
from x in db.Foo where x.id != bar select x, localBarValue);
Voici la QueryCache mise en œuvre j'ai fait la maquette:
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);
}
}
Cela peut être étendu pour prendre en charge plus d'arguments. Le grand bit est qu'en transmettant les valeurs de paramètres dans la mémoire Cache de la méthode elle-même, vous obtenez de typage implicite de l'expression lambda.
EDIT: Notez que vous ne pouvez pas appliquer de nouveaux opérateurs pour les requêtes compilées.. Spécifiquement vous ne pouvez pas faire quelque chose comme ceci:
var allresults = q.Cache(db => from f in db.Foo select f);
var page = allresults.Skip(currentPage * pageSize).Take(pageSize);
Donc, si vous envisagez sur la pagination d'une requête, vous devez le faire dans l'opération de la compilation au lieu de le faire plus tard. Ceci est nécessaire non seulement pour éviter une exception, mais également en conformité avec le point de l'ensemble de Skip/Prendre (pour éviter de se retrouver toutes les lignes de la base de données). Ce modèle devrait fonctionner:
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);
}
Une autre approche de la pagination serait de revenir à 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);
}
Ce modèle est utilisé comme:
var results = GetPageableFoo()(currentPage, pageSize);