如何使用 Entity Framework 构造动态查询表达式

  一般的程序员做上几年以后, 或多或少的都有些代码的积累, 我也不例外. 作为微软技术程序员, 自从Linq和EF出来之后, 就基本上爱不释手了, 且不说执行效率的问题, 单单就开发效率和代码的可移植性上讲, 都是微软技术常规开发首选无二了. 于是终于丢掉了奴隶社会的.NET三层, 进入了EF的封建制了, 然后, 就发现持久化层的代码越写越少, 越写越精炼, 最后好像简直就是个万金油啊, 拿到哪里哪里可以用. 还有因为Lambda 表达式可以做为查询代码的参数, 就导致, 服务器端单单就数据访问层代码就浓缩到了一个文件中, 而且还不需要写过于复杂的代码就可以写出使用泛型, 适合各种增删改查的动态代码, 如下面的查询:

  public IQueryable<T> ExecuteQuery<T>(Fun<T, bool> where, Fun<T, string> orderby, int pageIndex, int pageSize, out int total, params string[] includes) where T : class {...}

  一度感觉心里美美的, 好像世界从此就清净了. 直到后来做Web开发的时候, 发现了一个这套机制难以解决的问题,  那就是如何将查询应用到客户端复杂的过滤条件上了?  

  那为什么不能了? 因为Lambda 表达式本身就是动态委托, 这种类型是不能从客户端(js)构造出来, 作为参数传递到服务器端去执行的, 那么怎么办了? 

  经过一番研究, 终于发现了可实现的方式, 原来在System.Linq.Expressions命名空间下的Expression类提供了大量方法, 用于构造动态语法, 于是从只需要定义一套查询表达式规则, 然后客户端去写表达式, 服务器端收到后把它解析成Lambda表达式就可以被EF执行了, 至此, 好像发现了新大陆一样又欣喜若狂了一番.

  现在, 随着.NET开源, Oracle和Mysql开始支持EF6, 直到Webapi开始支持oData, 貌似一场新的技术改革又要开始了. 我又投入到了新技术孜孜不倦的追求当中.  

  虽然oData是如此之好用, 以至于从此从客户端接受增删改查什么的都成了浮云,  但是许多程序员还在维护着很多旧代码, 许多新程序员需要技术指导和提高, 许多时间以后我自己或许也会忘记如何去写动态表达式, 所以把核心部分代码贴出来, 大家分享一下, 不喜勿喷哦.

  DbContext的扩展查询方法:

 1 public static IQueryable<T> ExecuteQuery<T>(this DbContext ctx, QueryExpression query) where T : class
 2         {
 3             try
 4             {
 5                 IQueryable<T> queryable = ctx.Set<T>();
 6                 if (query != null)
 7                 {
 8                     if (query.Include != null && query.Include.Count != 0)
 9                     {
10                         foreach (var x in query.Include)
11                         {
12                             queryable = queryable.Include<T>(query.Include[0]);
13                         }
14                     }
15                     ParameterExpression param = Expression.Parameter(typeof(T), CommonClass.Anonymous);
16                     if (query.Filter != null && query.Filter.Count != 0)
17                     {
18                         Expression filter = Expression.Constant(true);
19                         foreach (var x in query.Filter)
20                         {
21                             filter = filter.AddQueryExpression<T>(param, x);
22                         }
23                         Expression<Func<T, bool>> where = arg => true;
24                         where = where.Update(filter, new List<ParameterExpression> { param });
25                         queryable = queryable.Where(where);
26                     }
27                     if (query.OrderBy != null && query.OrderBy.Count != 0)
28                     {
29                         Expression<Func<T, string>> orderBy = arg => CommonClass.StateField;
30                         IOrderedQueryable<T> orderByQueryable = queryable.OrderBy(orderBy);
31                         foreach (var x in query.OrderBy)
32                         {
33                             Expression exp = Expression.Property(param, x.Field);
34                             orderBy = orderBy.Update(exp, new List<ParameterExpression> { param });
35                             orderByQueryable = x.Asc ? orderByQueryable.ThenBy(orderBy) : orderByQueryable.ThenByDescending(orderBy);
36                         }
37                         queryable = orderByQueryable.AsQueryable<T>();
38                     }
39                     if (query.Pager != null)
40                     {
41                         query.Pager.Total = queryable.Count();
42                         queryable = queryable.Skip((query.Pager.PageIndex - 1) * query.Pager.PageSize).Take(query.Pager.PageSize);
43                     }
44                 }
45                 return queryable;
46             }
47             catch (Exception ex)
48             {
49                 throw ex;
50             }
51         }

 1 public static Expression AddQueryExpression<T>(this Expression exp, ParameterExpression param, DataFilter filter)
 2         {
 3             try
 4             {
 5                 if (param != null && filter != null)
 6                 {
 7                     Expression mexp = null;
 8                     BinaryExpression bexp = null;
 9                     PropertyInfo property = typeof(T).GetProperty(filter.Field);
10                     if (property != null)
11                     {
12                         Type type = property.PropertyType;
13                         object value = Convert.ChangeType(filter.Value, type);
14                         switch (filter.Operation)
15                         {
16                             //methods operation
17                             case DataOperation.Contains:
18                             case DataOperation.StartsWith:
19                             case DataOperation.EndsWith:
20                                 mexp = Expression.Call(Expression.Property(param, filter.Field), type.GetMethod(filter.Operation.ToString()), Expression.Constant(value));
21                                 exp = Expression.AndAlso(exp, mexp);
22                                 break;
23                             //other operation
24                             case DataOperation.Equal:
25                                 bexp = Expression.Equal(Expression.Property(param, filter.Field), Expression.Constant(value));
26                                 exp = Expression.AndAlso(exp, bexp);
27                                 break;
28                             case DataOperation.NotEqual:
29                                 bexp = Expression.NotEqual(Expression.Property(param, filter.Field), Expression.Constant(value));
30                                 exp = Expression.AndAlso(exp, bexp);
31                                 break;
32                             case DataOperation.GreaterThan:
33                                 bexp = Expression.GreaterThan(Expression.Property(param, filter.Field), Expression.Constant(value));
34                                 exp = Expression.AndAlso(exp, bexp);
35                                 break;
36                             case DataOperation.GreaterThanOrEqual:
37                                 bexp = Expression.GreaterThanOrEqual(Expression.Property(param, filter.Field), Expression.Constant(value));
38                                 exp = Expression.AndAlso(exp, bexp);
39                                 break;
40                             case DataOperation.LessThan:
41                                 bexp = Expression.LessThan(Expression.Property(param, filter.Field), Expression.Constant(value));
42                                 exp = Expression.AndAlso(exp, bexp);
43                                 break;
44                             case DataOperation.LessThanOrEqual:
45                                 bexp = Expression.LessThanOrEqual(Expression.Property(param, filter.Field), Expression.Constant(value));
46                                 exp = Expression.AndAlso(exp, bexp);
47                                 break;
48                             default:
49                                 break;
50                         }
51                     }
52                 }
53                 return exp;
54             }
55             catch (Exception ex)
56             {
57                 throw ex;
58             }
59         }

其中的QueryExpression是我自己定义的查询表达式对象, 可以从客户端传递.

打完收工!

 

posted @ 2015-01-10 13:55  七里听香  阅读(1861)  评论(0编辑  收藏  举报