自动编译Linq查询

我们发现编译我们的Linq查询要比每次编译要快得多,所以我们想开始使用编译查询。 问题在于它使得代码更难阅读,因为查询的实际语法在其他文件中是closures的,远离它被使用的地方。

我想到编写一个方法(或扩展方法)可能会使用reflection来确定传入的查询,并自动caching编译后的版本以供将来使用。

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

Cached()将不得不反映传入的查询对象,并确定所选的表以及查询的参数types。 显然,reflection速度有点慢,所以最好使用caching对象的名字(但是你仍然必须首次使用reflection来编译查询)。

 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<>对象的caching,我可以在第一次自动编译查询后调用。

您不能在匿名lambdaexpression式上调用扩展方法,因此您需要使用Cache类。 为了正确caching查询,您还需要将任何参数(包括您的DataContext)“提升”到您的lambdaexpression式的参数中。 这导致非常详细的用法,如:

 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方法本身,您可以为lambdaexpression式进行隐式types化。

编辑:请注意,您不能应用新的操作员编译查询..具体而言,你不能做这样的事情:

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

因此,如果您计划分页查询,则需要在编译操作中完成,而不是稍后再执行。 这不仅是为了避免一个exception,而且还要保持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); 

既然没有人在尝试,我会试一试。 也许我们可以以某种方式解决这个问题。 这是我的尝试。

我使用字典设置了这个,我也没有使用DataContext,虽然这是微不足道的,我相信。

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

现在这允许我这样做

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

期待对此进行一些讨论,进一步发展这个想法。

对于未来的后代:.NET Framework 4.5会默认执行此操作(根据我刚刚观看的演示文稿中的幻灯片)。