Ответ 1
У вас не может быть методов расширения, вызываемых на анонимных лямбда-выражениях, поэтому вы захотите использовать класс 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, вы получаете неявное типирование для выражения лямбда.
EDIT: Обратите внимание, что вы не можете применять новые операторы к скомпилированным запросам. В частности, вы не можете сделать что-то вроде этого:
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);