从上一篇NHibernate与Entity Framework性能比较中可以看出来LINQ预编译查询可以大大提高性能。本文对预编译查询做简单介绍。
首先了解什么是
匿名方法(anonymous function)和Func<>
1. 命名方法,匿名方法和Lambda表达式
在C#2.0之前,对委托赋值的唯一方式是通过的命名方法。C#2.0引入匿名方法以内联(in-line)方式使之变得直观和方便,而且匿名方法可以省略参数列表,可以将匿名方法转换为不同签名的委托。
C#3.0中Lambda表达式的语法跟匿名方法的非常相似,可用于构建委托或者LINQ表达式(Expression)。后面的例子中都会使用Lambda表达式对委托赋值。
class Test
{
delegate void TestDelegate(string s);
static void M(string s)
{
Console.WriteLine(s);
}
static void Main(string[] args)
{
// Original delegate syntax required initialization with a named method.
TestDelegate testDelA = new TestDelegate(M);
// C# 2.0: A delegate can be initialized with inline code, called an "anonymous method." This
// method takes a string as an input parameter.
TestDelegate testDelB = delegate(string s) { Console.WriteLine(s); };
// C# 3.0. A delegate can be initialized with a lambda expression. The lambda also takes a string
// as an input parameter (x). The type of x is inferred by the compiler.
TestDelegate testDelC = (x) => { Console.WriteLine(x); };
// Invoke the delegates.
testDelA("Hello. My name is M and I write lines.");
testDelB("That's nothing. I'm anonymous and ");
testDelC("I'm a famous author.");
}
}
2. Linq.Expression 和 Func<>
Linq.Expression就是LINQ表达式,继承Linq.Expressions.LambdaExpression
Func<>是一种委托,在LINQ中Func<>代表对应LINQ表达式的委托。
Linq表达式要变成可执行的代码(不是指SQL,而是指C#代码), 需要将Expression编译Func<>。我们通过一下代码来测试
//此时sqrExpression只是一个Expression,并不能执行
Expression<Func<double, double>> sqrExpression = (x => x * x);
//Compile成可执行的Func<>
Func<double, double> sqr = sqrExpression.Compile();
Console.Write(sqr(3));
3. 预编译LINQ
Compile是一步耗时的操作,在执行普通Linq查询的时候,每执行一次都自动Compile,即使同样的查询(参数可能不一样)已经被执行过多次。
EF对此有缓存机制,在同一个context内会将compile后的结果缓存起来,但是在一般场景下使用的都是短生命周期(short-life)的context,此缓存的作用是杯水车薪。
//普通Linq查询
public IList<Product> SearchByName(string name)
{
using (NorthWindEntities ctx = new NorthWindEntities())
{
//每次执行是都会自动compile
return ctx.Products.Where(o => o.ProductName.Contains(name.Trim())).ToList();
}
}
对此我们可以考虑将编译后的结果(Func<>)保存起来以重用
private static Func<NorthWindEntities, string, IQueryable<Product>> searchByNameQuery =
CompiledQuery.Compile<NorthWindEntities, string, IQueryable<Product>>
((NorthWindEntities ctx, string name) => ctx.Products.Where(o => o.ProductName.Contains(name.Trim())));
public IList<Product> SearchByNameCompiled(string name)
{
using (NorthWindEntities ctx = new NorthWindEntities())
{
return searchByNameQuery(ctx, name).ToList();
}
}
上面的例子我们把编译后的Func<>放到一个static变量中,这样可以保证全局范围内只需要compile一次。当然也可以在此基础上再做改进,比如Lazy-load Singleton。
为了提高程序性能,你几乎可以在所用地方都使用预编译LINQ,当然查询语句越复杂的时候效果就越明显。