前言

LINQ大家都知道,用起来也还不错,但有一个问题,当你用Linq进行搜索的时候,你是这样写的

  1. var query = from user in db.Set<User>()
  2.                         where user.Username == "xxxx"
  3.                         select user;

OK,看起来很好,不过····如果你要进行动态搜索的话··呵呵!其实方法还是挺多,只不过绕大弯

动态搜索是什么?顺便介绍下,假如你做了一个表格页面,有用户名、注册时间、等级三列,你希望实现动态组合搜索,只有当用户指定了要以用户名搜索的时候才把用户名这一列加入搜索条件,传统的sql是这么干的

  1. string sql="select * from user ";
  2. if(userName!="")
  3. {
  4.     sql+=" Where Username="+userName
  5. }

声明:只做示例,能不能运行不重要!(上面的代码一般情况下必须是有问题的

那你用linq能不能这么干呢?呃····向下面这样

  1. var query = from user in db.Set<User>()
  2.                         select user;
  3.             var userName = string.Empty;
  4.             if(!string.IsNullOrWhiteSpace(userName))
  5.             {
  6.                 query = query.Where(x => x.Username == userName);
  7.             }

对,是可以这样,但在实际项目中一般是不会直接返回IQueryable接口的,也就没办法这么做。

所以,动态拼接linq就应运而生了,而linq的实质是表达式树,大家可以看IQueryable的Where扩展方法的签名,是以Expression开头的。

组件介绍及使用

封装的最初目的,也是给自己用。我是懒人,所以我会把组件搞得越简单越好,下面是一个完整的使用示例

  1. TestDataContext db = new TestDataContext();
  2.            var builder = new ExpressionBuilder<User>();//实例化组件,User是什么下面说
  3.            var filters = new List<SqlFilter>();
  4.            filters.Add(SqlFilter.Create("Id", Operation.Equal, 1)); //添加User的Id属性值等于1的搜索条件
  5.            filters.Add(SqlFilter.Create("LastLoginDate", Operation.GreaterThan, DateTime.Now));//添加User的LastLoginDate属性值大于现在的搜索条件
  6.            filters.Add(SqlFilter.Create("Username", Operation.Like, "aaaa"));//添加User的Username属性值like "aaaaa"的搜索条件
  7.            filters.Add(SqlFilter.Create("Id", Operation.In, new int[] { 1, 2, 3 }));//添加User的Id属性值在1、2、3之中的搜索条件,当Operation为In的时候,最后一个参数必须为集合
  8.            filters.Add(SqlFilter.Create(""));
  9.            filters.Add(SqlFilter.Create("Status", Operation.In, new int[] { 1 }));
  10.            var where = builder.Build(filters, new Dictionary<string, string>());//根据上面的条件,拼接出表达式树
  11.            var results = db.Set<User>().Where(where).ToList();//地球人都知道

上面的代码拼接出来表达式树是这样的

  1. var ids=new int[]{1,2,3};
  2.             var status=new int[]{1};
  3.             db.Set<User>().Where(x => x.Id == 1 && x.LastLoginDate > DateTime.Now && ids.Contains(x.Id) && x.Username.Contains("" && status.Contains(x.Status));

注释已经说得比较清楚了,但第2行和第10行需要特别解释一下,第10行的Build方法原型如下

  1. public Expression<Func<TParameter, bool>> Build(IList<SqlFilter> filters, Dictionary<string, string> filterNameMap)

返回值是一个Expression<Func<TParameter,bool>>类型的,其中有一个泛型参数,这个参数就是第2行实例化的时候传的User,在使用的时候,必须保证添加的每一个搜索条件的属性在User类里面有。

为什么要这样设计呢?这个感觉一下子说不清楚,等下讲原理的时候说

另外还可以看到有个filterNameMap的参数,这个主要是用来进行属性名的转换的,一般用于外键。简单说一下我当时的设计意图。

例如,有一个列表,展示的是权限系统中的角色信息,有角色名、描述、拥有的功能三列,其中第三列内容是来自功能表中的,其他是来自角色表的。

一般情况下可能会将第三列的属性名设置为Privileges,然后类型是string型,但如果说用户要按角色拥有的功能进行搜索,你不可能按字符串过滤吧?一般是按照功能Id也就是PrivilegeId过滤。

因为我用的是ExtJs(没用过这个的直接跳过这一段吧),所以问题来了,extjs传给我的参数名是Privileges,值是一个集合,因为我的Model类属性名是这个,但我后台用于过滤的真正属性是PrivilegeId,所以我需要将Privileges映射到PrivilegeId,告诉ExpressionBuilder,如果遇到了Privileges属性名的搜索条件,就将属性名换成PrivilegeId进行拼接

为了方便理解,下面是SqlFilter的源码,很简单

  1. public class SqlFilter
  2.     {
  3.         public static SqlFilter Create(string propertyName, Operation operation, object value)
  4.         {
  5.             return new SqlFilter()
  6.             {
  7.                 Name = propertyName,
  8.                 Operation = operation,
  9.                 Value = value
  10.             };
  11.         }
  12.  
  13.         /// <summary>
  14.         /// 字段名
  15.         /// </summary>
  16.         public string Name { get; set; }
  17.  
  18.         /// <summary>
  19.         /// 搜索操作,大于小于等于
  20.         /// </summary>
  21.         public Xinchen.DbUtils.Operation Operation { get; set; }
  22.  
  23.         /// <summary>
  24.         /// 搜索参数值
  25.         /// </summary>
  26.         public object Value { get; set; }
  27.     }

下面是Operation的

  1. public enum Operation
  2.     {
  3.         GreaterThan,
  4.         LessThan,
  5.         GreaterThanOrEqual,
  6.         LessThanOrEqual,
  7.         NotEqual,
  8.         Equal,
  9.         Like,
  10.         In
  11.     }

组件原理

还真没把握能说清楚···

其实就是拼接表达式树的原理

  1. //假如我们要拼接x=>x.Id==1,假如x的类型为User
  2.             var parameterExp = Expression.Parameter(typeof(User), "x");
  3.             //结果是这样:x=>,x是变量名
  4.             var propertyExp = Expression.Property(parameterExp, "Id");
  5.             //结果是这样:x=>x.Id,这句是为了构建访问属性的表达式
  6.             //上面这句第一个参数是你要取属性的对象表达式。我们要拼的表达式是x=>x.Id==1,==1这块先不管,其实就是x=>x.Id,那么其实我们就是对x进行取属性值,而x是parameterExp,所以第一个参数是parameterExp,第二个参数好说,就是属性名
  7.             var constExp = Expression.Constant(1);
  8.             //结果是··没有结果,构建一个常量表达式,值为1(LINQ的世界,一切皆表达式树)
  9.             //马上就是关键的一步了
  10.             var body = Expression.Equal(propertyExp, constExp);
  11.             //结果是:x=>x.Id==1,这个··还需要解释么,很简单,不是么。创建一个相等的表达式,然后传入左边和右边的表达式
  12.             //当然到这儿还不能用,还需要继续
  13.             var lambda = Expression.Lambda<Func<User, bool>>(body, parameterExp);
  14.             //这句和第二句是我学的时候最难理解的两个地方。这句是将我们的成果封装成能用的,第一个参数就是我们的成果,第二个参数是实现这个成果所需要的参数,那当然是parameterExp,然后泛型参数Func<User,bool>就是我们想把这个表达式封装成什么样的东西,此时,lambda的类型就是Expression<Fun<User,bool>>,这个时候就能用了

这是一个相当简单的表达式树,注释写得很清楚,下面来个复杂的

  1. //假如我们要拼接x=>x.Username.Contains("aaa"),假如x的类型为User
  2.             var parameterExp = Expression.Parameter(typeof(User), "x");
  3.             var propertyExp = Expression.Property(parameterExp, "Username");
  4.             //上面两句不再介绍
  5.             var containsMethod = typeof(string).GetMethod("Contains", new Type[] { typeof(string) });
  6.             //因为我们要拼接的表达式中调用了string类型的Username的Contains方法,所以反射获取string类型的Contains方法
  7.             var constExp = Expression.Constant("aaa");
  8.             //不再解释
  9.             var containsExp = Expression.Call(propertyExp, containsMethod, constExp);
  10.             //结果是:x=>x.Username.Contains("aaa"),第一个参数,是要调用哪个实例的方法,这里是propertyExp,第二个是调用哪个方法,第三个是参数,理解了上一个示例,这个应该不难理解
  11.             var lambda = Expression.Lambda<Func<User, bool>>(containsExp, parameterExp);
  12.             //不再解释

可以看到,第一句都是取了User的类型,所以我在设计ExpressionBuilder的使用了泛型,以供传入这个参数

分享动态拼接Expression表达式组件及原理的更多相关文章

  1. .NetCore 使用 Linq 动态拼接Expression表达式条件来实现 对EF、EF Core 扩展查询排序操作

    相信在使用EF的时候对查询条件或者排序上的处理令人心烦,下面我们就来动态拼接表达式解决这一问题 当我们在查询中使用Where的时候可以看到如下参数 下面我们就来扩展 Expression<Fun ...

  2. 动态拼接lambda表达式树

    前言 最近在优化同事写的代码(我们的框架用的是dapperLambda),其中有一个这样很普通的场景——界面上提供了一些查询条件框供用户来进行过滤数据.由于dapperLambda按条件查询时是传入表 ...

  3. Asp.net Core C#进行筛选、过滤、使用PredicateBuilder进行动态拼接lamdba表达式树并用作条件精准查询,模糊查询

    在asp.net core.asp.net 中做where条件过滤筛选的时候写的长而繁琐不利于维护,用PredicateBuilder进行筛选.过滤.LInq配合Ef.core进行动态拼接lamdba ...

  4. Lambda表达式动态拼接(备忘)

    EntityFramework动态组合Lambda表达式作为数据筛选条件,代替拼接SQL语句 分类: C# Lambda/Linq Entity Framework 2013-05-24 06:58 ...

  5. 表达式树动态拼接lambda

    动态拼接lambda表达式树   前言 最近在优化同事写的代码(我们的框架用的是dapperLambda),其中有一个这样很普通的场景——界面上提供了一些查询条件框供用户来进行过滤数据.由于dappe ...

  6. 【转】EntityFramework动态组合Lambda表达式作为数据筛选条件,代替拼接SQL语句

    传统的操作数据库方式,筛选数据需要用StringBuilder拼接一大堆的WHERE子句. 在Entity Framework中,代码稍有不慎就会造成巨大性能消耗,如: using(var db=ne ...

  7. 动态拼接表达式——Expression

    我们在项目中会遇到以下查询需求吗? 比如需要查询出满足以下条件的会员: 条件组一:30-40岁的男性会员 条件组二:20-30岁的女性会员 条件组三:60-80岁性别未知的会员 条件组内是并且关系,但 ...

  8. Expression表达式目录树动态拼接 反射获取泛型方法

    class TestOne { public String[] arr = { "1", "2", "3" }; public class ...

  9. 关于Expression表达式树的拼接

    最近在做项目中遇到一个问题,需求是这样的: 我要对已经存在的用户进行检索,可以根据用户的id 或者用户名其中的一部分字符来检索出来,这样就出现了三种情况 只有id,只有用户名中一部字符,或者全部都有. ...

随机推荐

  1. 实验五:分析system_call中断处理过程

    原创作品转载请注明出处<Linux内核分析>MOOC课程http://mooc.study.163.com/course/USTC-1000029000 如果我写的不好或者有误的地方请留言 ...

  2. cocos2d-x把json数据解析到数组或字典中(libjson库)

    以前在cocos2d-x项目中用到json解析,集成了libjson库后发现网上提供的解析方法大多是在解析过程中取得值,并没有将解析结果有效的保存起来,于是摸索一番,把解析结果根据数据格式存到数组或字 ...

  3. 浅析hashCode方法

    一.问题引入 谈到hashCode就不得不说equals方法,二者均在Object类里,由于Object类是所有类的基类,所以一切类里都可以重写这两个方法. 要想较清晰的理解,需要先知道容器Colle ...

  4. Coupons

    uva10288:http://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&category=24&am ...

  5. Delphi COM编程学习笔记(1)

    释放接口对象,既不是调用MyObj.Free,也不是MyObj.Release;破坏对象的正确方法是将它们设置为nil:MyInterface := nil; 一个接口不能离开实现它的对象而独立存活. ...

  6. java Future 模式

    考慮這樣一個情況,使用者可能快速翻頁瀏覽文件中,而圖片檔案很大,如此在瀏覽到有圖片的頁數時,就會導致圖片的載入,因而造成使用者瀏覽文件時會有停頓 的現象,所以我們希望在文件開啟之後,仍有一個背景作業持 ...

  7. HBase Split

    Region Split请求是在Region MemStore Flush之后被触发的: boolean shouldCompact = region.flushcache(); // We just ...

  8. SQL 2008 如何配置远程连接

    初次接触sql2008 相比05 改观还是挺大的 在配置方面 如何打开"远程连接" 成了最棘手的   到网上找了大半天资料    依然云里雾里 参考网上的众多资料 结合本人的实际经 ...

  9. Scala 编程(一)Scala 编程总览

    Scala 简介 Scala 属于“可伸展语言”,源于它可以随使用者的需求而改变和成长.Scala 可以应用在很大范围的编程任务上,小到脚本大到建立系统均可以. Scala 跑在标准 Java 平台上 ...

  10. wxPython学习笔记(二)

    如何创建和使用一个应用程序对象? 任何wxPython应用程序都需要一个应用程序对象.这个应用程序对象必须是类wx.App或其定制的子类的一个实例.应用程序对象的主要目的是管理幕后的主事件循环. 父类 ...