当前位置 > 首页 > Asp.net

PredicateBuilder 对 ADO.Net Entity Framework 支持的改进

2012-9-7 10:23:00来源:Asp.net

曾几何时,网络上曾经大传 PredicateBuilder 用于拼接两个 Lambda 表达式树。在对内存数据的筛选上面,其简洁方便的操作大放异彩,但是对数据库操作的不支持,一直是其硬伤。PredicateBuilder 拼接表达式的过程中,产生的 Invoke 表达式无法翻译成 SQL 语句,这是其根本原因。另外,Invoke 表达式编译后,形成的委托调用委托的方式,也是对性能的一种损耗。

 

当然,也有很多人对其做过改造,不过给人的感觉,总不是那么完美。以前,因为种种原因,我们不得不忍受这种问题。前几天,由于工作中用到了 Entity Framework 和动态拼接 Lambda 表达式树,为了避免全表查询的尴尬,硬着头皮研究改造 PredicateBuilder —— 各种纠结。还好,了解透彻原理后就很简单了。期间,用到了下面这个测试类:

public class Person{    public int ID { get; set; }    public string Name { get; set; }}

 

这里先说一下要达到的目的:

两个表达式:

Expression<Func<Person, bool>> a = p => p.ID == 3;Expression<Func<Person, bool>> b = c => c.Name == "张三";

拼接之后,想要达成如下形式:

Expression<Func<Person, bool>> x = y => y.ID == 3 && y.Name == "张三";

原始 PredicateBuilder 用的是 Invoke 表达式,也就是说没有解析待拼接的表达式。我们可以解析一下,看看能否达到效果。

 

首先来看相同点:

表达式 a :根是一个相等判断的二元表达式,其左子树是属性的调用的表达式,右子树是一个数字常量表达式。

表达式 b :根是一个相等判断的二元表达式,其左子树是属性的调用的表达式,右子树是一个数字常量表达式。

下面看不同:

表达式 a 参数名字是 p ,表达式 b 参数名字是 c —— 两者参数的名称不同,但是类型是相同的。

 

对于目标表达式,这里我第一印象是把右边表达式的参数,改成左边的,然后把左右两边表达式的 Body 拼接,加上左边的参数,这就是目标表达式树。经过分析,这种做法是可行的,下面上代码,代码不解释:

/// <summary>/// 谓词表达式构建器/// </summary>public static class PredicateBuilder{    #region Expression Joiner    /// <summary>    /// 创建一个值恒为 <c>true</c> 的表达式。    /// </summary>    /// <typeparam name="T">表达式方法类型</typeparam>    /// <returns>一个值恒为 <c>true</c> 的表达式。</returns>    public static Expression<Func<T, bool>> True<T>() { return p => true; }    /// <summary>    /// 创建一个值恒为 <c>false</c> 的表达式。    /// </summary>    /// <typeparam name="T">表达式方法类型</typeparam>    /// <returns>一个值恒为 <c>false</c> 的表达式。</returns>    public static Expression<Func<T, bool>> False<T>() { return f => false; }    /// <summary>    /// 使用 Expression.OrElse 的方式拼接两个 System.Linq.Expression。    /// </summary>    /// <typeparam name="T">