参考:https://www.cnblogs.com/wzxinchen/p/4611592.html,最后加了点额外的东西

前言

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

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

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

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

string sql="select * from user ";
if(userName!="")
{sql+=" Where Username="+userName
}

声明:只做示例,能不能运行不重要!(上面的代码一般情况下必须是有问题的
那你用linq能不能这么干呢?呃····向下面这样

var query = from user in db.Set<User>()select user;
var userName = string.Empty;
if(!string.IsNullOrWhiteSpace(userName))
{query = query.Where(x => x.Username == userName);
}

对,是可以这样,但在实际项目中一般是不会直接返回IQueryable接口的,也就没办法这么做。
所以,动态拼接linq就应运而生了,而linq的实质是表达式树,大家可以看IQueryable的Where扩展方法的签名,是以Expression开头的。

组件介绍及使用

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

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

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

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

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

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的源码,很简单

public class SqlFilter
{public static SqlFilter Create(string propertyName, Operation operation, object value){return new SqlFilter(){Name = propertyName,Operation = operation,Value = value};}/// <summary>/// 字段名/// </summary>public string Name { get; set; }/// <summary>/// 搜索操作,大于小于等于/// </summary>public Xinchen.DbUtils.Operation Operation { get; set; }/// <summary>/// 搜索参数值/// </summary>public object Value { get; set; }
}

下面是Operation的

public enum Operation
{GreaterThan,LessThan,GreaterThanOrEqual,LessThanOrEqual,NotEqual,Equal,Like,In
}

组件原理

还真没把握能说清楚···
其实就是拼接表达式树的原理

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

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

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

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

附件 LinqExtions扩展封装类:

public static class LinqExtions{public static IEnumerable<TSource> DistinctBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector){HashSet<TKey> seenKeys = new HashSet<TKey>();foreach (TSource element in source){if (seenKeys.Add(keySelector(element))){yield return element;}}}public static Expression Property(this Expression expression, string propertyName){return Expression.Property(expression, propertyName);}public static Expression AndAlso(this Expression left, Expression right){return Expression.AndAlso(left, right);}public static Expression Call(this Expression instance, string methodName, params Expression[] arguments){return Expression.Call(instance, instance.Type.GetMethod(methodName), arguments);}public static Expression GreaterThan(this Expression left, Expression right){return Expression.GreaterThan(left, right);}public static Expression<T> ToLambda<T>(this Expression body, params ParameterExpression[] parameters){return Expression.Lambda<T>(body, parameters);}public static Expression<Func<T, bool>> True<T>() { return param => true; }public static Expression<Func<T, bool>> False<T>() { return param => false; }/// <summary>/// 组合And/// </summary>/// <returns></returns>public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second){return first.Compose(second, Expression.AndAlso);}/// <summary>/// 组合Or/// </summary>/// <returns></returns>public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second){return first.Compose(second, Expression.OrElse);}/// <summary>/// Combines the first expression with the second using the specified merge function./// </summary>static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge){var map = first.Parameters.Select((f, i) => new { f, s = second.Parameters[i] }).ToDictionary(p => p.s, p => p.f);var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);}/// <summary>/// ParameterRebinder/// </summary>private class ParameterRebinder : ExpressionVisitor{/// <summary>/// The ParameterExpression map/// </summary>readonly Dictionary<ParameterExpression, ParameterExpression> map;/// <summary>/// Initializes a new instance of the <see cref="ParameterRebinder"/> class./// </summary>/// <param name="map">The map.</param>ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map){this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();}/// <summary>/// Replaces the parameters./// </summary>/// <param name="map">The map.</param>/// <param name="exp">The exp.</param>/// <returns>Expression</returns>public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp){return new ParameterRebinder(map).Visit(exp);}/// <summary>/// Visits the parameter./// </summary>/// <param name="p">The p.</param>/// <returns>Expression</returns>protected override Expression VisitParameter(ParameterExpression p){ParameterExpression replacement;if (map.TryGetValue(p, out replacement)){p = replacement;}return base.VisitParameter(p);}}}

使用例子:
1.

var expression = LinqExtensions.True<XXXEntity>();
if (!string.IsNullOrWhiteSpace(queryJson))
{var parmDic = Newtonsoft.Json.JsonConvert.DeserializeObject<Hashtable>(queryJson);if (!parmDic["aaa"].IsEmpty()){var value = (string)parmDic["aaa"];expression = expression.And(x =>x.bbb.Contains(value));}
}
var Rows = xxxDBContext.where(expression).ToList();

2.

var expression = LinqExtensions.True<XXXXEntity>();
expression = expression.And(x =>x.id== "aaa" &&x.code == "bbb");
var queryList = xxxDBContext.where(expression).ToList();

Expression 拼接组合表达式(附--封装代码)相关推荐

  1. 基于opencv的图像碎片拼接复原系统 附完整代码可作为毕设

    C++ 开发环境:Microsoft VisualStudio 2017 Python 开发环境: PyCharm C++ 开源计算机视觉库: Opencv4.1.0 Python 开发语言版本: P ...

  2. 实战31:基于opencv的图像碎片拼接复原系统 附完整代码可作为毕设

    C++ 开发环境:Microsoft VisualStudio 2017 Python 开发环境: PyCharm C++ 开源计算机视觉库: Opencv4.1.0 Python 开发语言版本: P ...

  3. 【组合导航】GNSS与惯性及多传感器组合导航附matlab代码

    ✅作者简介:热爱科研的Matlab仿真开发者,修心和技术同步精进,matlab项目合作可私信.

  4. 吴恩达机器学习python实现(6):SVM支持向量机(文末附完整代码)

    所有的数据来源:链接:https://pan.baidu.com/s/1vTaw1n77xPPfKk23KEKARA 提取码:5gl2 1 Support Vector Machines 1.1 Pr ...

  5. 广告点击率(CTR)预测经典模型 GBDT + LR 理解与实践(附数据 + 代码)

    CTR 系列文章: 广告点击率(CTR)预测经典模型 GBDT + LR 理解与实践(附数据 + 代码) CTR经典模型串讲:FM / FFM / 双线性 FFM 相关推导与理解 CTR深度学习模型之 ...

  6. 数学规划模型总结(附MatLab代码)

    数学规划模型总结(附MatLab代码) 概述 定义 一般形式 分类 线性规划(linear programming) 非线性规划(nonlinear programming) 整数规划(integer ...

  7. 北京房租大涨?6个维度,数万条数据帮你揭穿(附详情代码)

    作者|丁彦军 来源|恋习Python 昨天还幻想海边别墅的年轻人,今天可能开始对房租绝望了. 8月初,有网友在"水木论坛"发帖控诉长租公寓加价抢房引起关注.据说,一名业主打算出租自 ...

  8. Vue 知识点汇总(下)--附案例代码及项目地址

    文章目录 Vue 预备知识与后续知识及项目案例 一.简介 1.Vue (读音 /vjuː/,类似于 view)的简单认识 2.Vue.js安装 二.Vue知识量化 三.内容 1.Webpack 详解 ...

  9. Web前端:JavaScript最强总结 附详细代码 (带常用案例!)

    Web前端基础: Web前端:HTML最强总结 附详细代码 Web前端:CSS最强总结 附详细代码 Web前端:JavaScript最强总结 附详细代码 Web前端工具: Web前端: JQuery最 ...

最新文章

  1. 用C#操纵IIS(代码)
  2. Mybatis-Plus 支持分库分表了?-官方神器发布!
  3. 台式机dp接口_台式机成就3471和战99,哪个更好?对比分析
  4. 关于js里的document.compatmode
  5. 3月第4周全球域名商TOP15:万网第四 涨幅居亚
  6. Java环境变量CLASSPATH详解
  7. JNI之常用函数大全
  8. get buffer from CMSampleBufferRef
  9. 内部排序的方法实验报告_十大经典排序算法Python版实现(附动图演示)
  10. 【操作系统】为什么需要内核
  11. python3 爬虫https的坑 -- 已解决
  12. Project免费视频教程来了
  13. jquery 文件上传 触发两次_聊一聊jquery文件上传(支持多文件上传)
  14. word-单独设置某一页的页眉或页脚
  15. 计算机维修培训教材,计算机芯片级维中心(芯片级维修培训教材)b.doc
  16. vs2015不使用方向键移动光标快捷键
  17. PHP Laravel 队列技巧:Fail、Retry 或者 Delay
  18. 经验分布函数与格里纹科定理
  19. H5实现一个简易本地视频播放器
  20. Scancode到Keycode的映射

热门文章

  1. html数组删除指定元素,js怎么删除数组中指定元素?
  2. 热转印标签印刷机行业分析-热转印标签印刷机产能、产量、产能利用率及发展趋势
  3. Android系统发展历程:1.0到4.0及代表机型
  4. 免费节点2:使用捷径添加_盒子:找到性能瓶颈的捷径
  5. TCP 协议面试灵魂10问,建议收藏~
  6. 头歌php 表单语言进阶
  7. 妙用CANoe对LIN节点球形阀进行自动化PV验证
  8. Embedding Temporal Network via Neighborhood Formation
  9. python 解析labelme
  10. 为什么说中国的吃瓜群众战斗力超群?