我们发现compiling our Linq queries比每次都要编译要快得多,因此我们想开始使用编译查询。问题是它使代码更难阅读,因为查询的实际语法在其他文件中偏离了使用位置。
在我看来,有可能编写一种使用反射来确定要传递的查询的方法(或扩展方法),并自动缓存已编译的版本以备将来使用。
var foo = (from f in db.Foo where f.ix == bar select f).Cached();
Cached()
必须反射(reflect)传入的查询对象,并确定在其上选择的表和查询的参数类型。显然,反射有点慢,因此对缓存对象使用名称可能会更好(但是您仍然必须在第一次使用反射来编译查询)。var foo = (from f in db.Foo where f.ix == bar select f).Cached("Foo.ix");
有没有人有这样做的经验,或者知道是否有可能?
更新:对于尚未看过的人,可以使用以下代码将LINQ查询编译为SQL:
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)
);
}
我想做的是拥有这些
Func<>
对象的缓存,我可以在第一次自动编译查询后调用这些缓存。 最佳答案
您不能在匿名lambda表达式上调用扩展方法,因此您将要使用Cache类。为了正确地缓存查询,您还需要将所有参数(包括DataContext)“提升”到lambda表达式的参数中。这会导致非常冗长的用法,例如:
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方法本身,就可以得到lambda表达式的隐式类型。
编辑:请注意,您不能将新的运算符应用于已编译的查询。特别是,您不能执行以下操作:
var allresults = q.Cache(db => from f in db.Foo select f);
var page = allresults.Skip(currentPage * pageSize).Take(pageSize);
因此,如果计划分页查询,则需要在编译操作中执行此操作,而不是稍后再执行。这不仅是为了避免发生异常,而且与跳过/执行的整个要点一致(避免从数据库返回所有行)都是必要的。此模式将起作用:
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);