Вы не можете вызывать методы расширения для анонимных лямбда-выражений, поэтому вы захотите использовать класс Cache. Чтобы правильно кэшировать запрос, вам также необходимо «поднять» любые параметры (включая ваш DataContext) в параметры для вашего лямбда-выражения. Это приводит к очень многословному использованию, как:
var results = QueryCache.Cache((MyModelDataContext db) =>
from x in db.Foo where !x.IsDisabled select x);
Чтобы очистить это, мы можем создать экземпляр QueryCache для каждого контекста, если сделаем его нестатичным:
public class FooRepository
{
readonly QueryCache<MyModelDataContext> q =
new QueryCache<MyModelDataContext>(new MyModelDataContext());
}
Затем мы можем написать метод Cache, который позволит нам написать следующее:
var results = q.Cache(db => from x in db.Foo where !x.IsDisabled select x);
Любые аргументы в вашем запросе также должны быть отменены:
var results = q.Cache((db, bar) =>
from x in db.Foo where x.id != bar select x, localBarValue);
Вот реализация QueryCache, которую я смоделировал:
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);
}
}
Это может быть расширено для поддержки большего количества аргументов. Самое замечательное в том, что, передавая значения параметров в сам метод Cache, вы получаете неявную типизацию лямбда-выражения.
РЕДАКТИРОВАТЬ: обратите внимание, что вы не можете применять новые операторы к скомпилированным запросам. В частности, вы не можете делать что-то вроде этого:
var allresults = q.Cache(db => from f in db.Foo select f);
var page = allresults.Skip(currentPage * pageSize).Take(pageSize);
Так что, если вы планируете разбивать запрос на страницы, вам нужно сделать это в операции компиляции, а не делать это позже. Это необходимо не только для того, чтобы избежать исключения, но и в соответствии со всем смыслом Skip / Take (чтобы избежать возврата всех строк из базы данных). Этот шаблон будет работать:
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);
}
Другим подходом к подкачке будет возвращение 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);
}
Этот шаблон используется как:
var results = GetPageableFoo()(currentPage, pageSize);