使用Expression实现数据的任意字段过滤(1)
在项目常常要和数据表格打交道。 现在BS的通常做法都是前端用一个js的Grid控件, 然后通过ajax的方式从后台加载数据, 然后将数据和Grid绑定。 数据往往不是一页可以显示完的, 所以要加分页;然后就是根据关键字段做排序, 做筛选过滤。 作为后端人员, 要考虑的是如何优雅的实现分页、排序、筛选的功能。
本文先谈谈筛选。 因为分页、排序、筛选这3个动作, 一定是先处理筛选的——筛选后的结果再去排序, 然后再做分页 , 才有意义。
筛选首先要考虑如下两个问题:
1) 字段类型
2) 比较方式
以下面的模拟数据为例( 该数据为服务器的性能监控, 包括处理器、内存的监控结果和时间)。
ServerName |
ProcessorMaxValue |
ProcessorMinValue |
ProcessorAvgValue |
MemoryMaxValue |
MemoryMinValue |
MemoryAvgValue |
DateTime |
Server1 |
8 |
3 |
3.29 |
82.18 |
82.11 |
82.14 |
2016/10/1 |
Server1 |
10 |
3 |
3.29 |
82.23 |
82.12 |
82.17 |
2016/10/2 |
Server1 |
11 |
3 |
3.32 |
82.21 |
82.15 |
82.18 |
2016/10/3 |
Server1 |
10 |
3 |
3.29 |
82.21 |
82.10 |
82.16 |
2016/10/4 |
Server1 |
10 |
3 |
3.42 |
82.20 |
82.12 |
82.15 |
2016/10/5 |
Server2 |
10 |
3 |
3.40 |
82.20 |
82.12 |
82.16 |
2016/10/6 |
Server2 |
9 |
3 |
4.08 |
82.22 |
82.11 |
82.15 |
2016/10/7 |
Server2 |
10 |
3 |
3.69 |
82.20 |
82.12 |
82.16 |
2016/10/8 |
Server3 |
11 |
3 |
4.13 |
82.21 |
82.14 |
82.16 |
2016/10/9 |
Server3 |
11 |
3 |
4.03 |
82.20 |
82.15 |
82.17 |
2016/10/10 |
对于用户来讲, 可能会使用所有的字段来做过滤。比如 "ServerName like 'Server'", "ProcessorMaxValue>10 ", "DateTime < '2016/10/9'"。
小结下, 比较常见的字段类型有字符串、数值、日期,以及boolean值。为什么要强调字段类型, 因为一样的值在不同的字段类型要求下, 比较结果是不同的, 比如说数字11>2 , 但字符串”11”<”2”。
其次考虑比较方式, 比较常见的有“大于、大于等于、等于、小于等于、小于、不等于”, 其次还有 “in (…set)”; 字符串类型可能有”包含”, “开头匹配”, “结尾匹配”等。
如果需求比较固定,直接在代码中依次处理有限的若干字段的筛选完全不是事。可是实际的项目中,这种情况很少。 更多的是, 客户一会要加这条件, 一会要加那条件。 如果都老老实实的一个一个加, 代码就很容易臃肿,甚至失控了。
本文中推荐的是使用Expression方案。 由于Expression是 对集合进行操作, 所以不使用于自己拼SQL然后使用SQLCommand的场景。比较适用于:
1) 使用EntityFramework作为ORM框架的
2) 直接对全集合处理的
先感受下代码
1 public class CriteriaCollectionHandler : ICollectionHandler 2 { 3 /* By Harvey Hu. @2016 */ 4 5 protected string PropertyName { get; set; } 6 7 protected ComparerEnum Comparer { get; set; } 8 9 protected object Target { get; set; } // 10 11 public CriteriaCollectionHandler(string propertyName, object target, ComparerEnum comparer) 12 { 13 this.PropertyName = propertyName; 14 this.Comparer = comparer; 15 this.Target = target; 16 } 17 18 private IQueryable<T> Filter<T>(IQueryable<T> source, string propertyName, ComparerEnum comparer, object target) 19 { 20 var type = typeof(T); 21 var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); 22 23 24 25 var parameter = Expression.Parameter(type, "p"); 26 Expression propertyAccess = Expression.MakeMemberAccess(parameter, property); 27 if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) 28 { 29 var getValueOrDefault = property.PropertyType.GetMethods().First(p => p.Name == "GetValueOrDefault"); 30 propertyAccess = Expression.Call(propertyAccess, getValueOrDefault); 31 } 32 var constExpression = Expression.Constant(ConvertTo(target, property.PropertyType)); // 转换为target的类型,以作比较 33 Expression comparisionExpression; 34 switch (comparer) 35 { 36 case ComparerEnum.Eq: 37 comparisionExpression = Expression.Equal(propertyAccess, constExpression); 38 break; 39 case ComparerEnum.Ne: 40 comparisionExpression = Expression.NotEqual(propertyAccess, constExpression); 41 break; 42 case ComparerEnum.Lt: 43 comparisionExpression = Expression.LessThan(propertyAccess, constExpression); 44 break; 45 case ComparerEnum.Gt: 46 comparisionExpression = Expression.GreaterThan(propertyAccess, constExpression); 47 break; 48 case ComparerEnum.Le: 49 comparisionExpression = Expression.LessThanOrEqual(propertyAccess, constExpression); 50 break; 51 case ComparerEnum.Ge: 52 comparisionExpression = Expression.GreaterThanOrEqual(propertyAccess, constExpression); 53 break; 54 case ComparerEnum.StringLike: 55 if (property.PropertyType != typeof(string)) 56 { 57 throw new NotSupportedException("StringLike is only suitable for string type property!"); 58 } 59 60 61 var stringContainsMethod = typeof(CriteriaCollectionHandler).GetMethod("StringContains"); 62 63 comparisionExpression = Expression.Call(stringContainsMethod, propertyAccess, constExpression); 64 65 break; 66 default: 67 comparisionExpression = Expression.Equal(propertyAccess, constExpression); 68 break; 69 } 70 71 72 var compareExp = Expression.Lambda(comparisionExpression, parameter); 73 var typeArguments = new Type[] { type }; 74 var methodName = "Where"; //sortOrder == SortDirection.Ascending ? "OrderBy" : "OrderByDescending"; 75 var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(compareExp)); 76 77 return source.Provider.CreateQuery<T>(resultExp); 78 } 79 80 public static bool StringContains(string value, string subValue) 81 { 82 if (value == null) 83 { 84 return false; 85 } 86 87 return value.Contains(subValue); 88 } 89 90 91 protected object ConvertTo(object convertibleValue, Type targetType) 92 { 93 if (null == convertibleValue) 94 { 95 return null; 96 } 97 98 if (!targetType.IsGenericType) 99 { 100 return Convert.ChangeType(convertibleValue, targetType); 101 } 102 else 103 { 104 Type genericTypeDefinition = targetType.GetGenericTypeDefinition(); 105 if (genericTypeDefinition == typeof(Nullable<>)) 106 { 107 var temp = Convert.ChangeType(convertibleValue, Nullable.GetUnderlyingType(targetType)); 108 var result = Activator.CreateInstance(targetType, temp); 109 return result; 110 } 111 } 112 throw new InvalidCastException(string.Format("Invalid cast from type \"{0}\" to type \"{1}\".", convertibleValue.GetType().FullName, targetType.FullName)); 113 } 114 115 116 public virtual ICollection<T> Execute<T>(ICollection<T> values) 117 { 118 var result = Filter(values.AsQueryable(), this.PropertyName, this.Comparer, this.Target).ToList(); 119 return result; 120 } 121 122 }
使用示例(伪码):
1 var criteria1 = New CriteriaCollectionHandler(“ServerName”, “server”, ComparerEnum.StringLike); // serverName like 'server'” 2 var criteria2 = New CriteriaCollectionHandler(“ProcessorMaxValue”, 10, ComparerEnum.Gt); 3 var criteria3 = New CriteriaCollectionHandler(“Datetime”, Datetime.Parse("2016/12/9"), ComparerEnum.lt); 4 ICollection<T> result = criteria3.Execute( 5 criteria2.Execute( 6 criteria1.Execute(YourDataCollection)));
核心是Filter()方法 ——IQueryable<T> Filter<T>(IQueryable<T> source, string propertyName, ComparerEnum comparer, object target)。
ICollectionHandler是用来处理集合Collection的对象接口,前面提到的分页、排序和筛选处理, 都可以适用于这个接口。这个接口的Execute方法处理一个集合,并返回一个集合。筛选也是这个逻辑,所以适用这个接口。
在Filter()方法中, 通过Expression构建了一个Lamda表达式, 如p=>p.Property == target。这个表达式有几个问题需要注意下:
1) 如何取到p.Property? 通过类型反射获取。
2) 如何取到判断操作? 根据比较符comparer枚举。如果是常规比较, 则直接调用Expression的相关方法生成, 比如Expression.Equal(); 如果是特殊, 则通过Expression.Call调用自定义的方法生成, 比如StringLike
3) 比较值的类型用什么?获取p.Property类型,并将target强制转换为该类型;参考ConvertTo()方法。
4) 是否支持Nullable类型?支持。但这个是个比较坑的事情。因为Nullable<T>实际上不支持和T的直接比较,所以不能将target转换为Nullable<T>类型,只能是T类型,因此lamda表达式只能用p=>p.Property.GetValueOrDefault() == target 规格来处理 。所以在ConvertTo()方法中, 对nullable<T>类型也做了判断处理。
Lamda表达式构造好了, 就可以通过Linq的Where方法来实现筛选了。这同样适用Expression的Call方法构造出来。最后通过IQuaryable的IQueryProvider
的CreateQuery()方法完成调用。
5) 是否支持其他比较操作? 通过适当的扩展,我想应该可以实现的。 比如StringLike就是我们自己扩展的比较方法, 当然这个不是EntityFramework提供的,所以不支持EF的Queryable。
代码实现分析到此暂告段落。 在实际使用中, 将每个条件都分别封装成CriteriaCollectionHandler对象, 然后依次调用即可完成”逻辑与”的操作。参考上面的实现示例。
如果要实现”逻辑或”怎么办?目前的考虑结果是将两个集合intersect()处理。如果各位有什么更好的办法, 欢迎回复讨论。
下一篇《使用Expression实现数据的任意字段过滤(2)》, 我将讨论下一些特殊字段的情况, 比如非Public的Property过滤。
注: 使用Expression过程也参考了博客园的其他朋友的文章。在此贡献出来, 也希望能帮助一些朋友。
转载于:https://www.cnblogs.com/huwz/p/ExpressionFilter1.html
使用Expression实现数据的任意字段过滤(1)相关推荐
- Debezium系列之:实现不同表中的数据始终发往对应的kafka topic分区,支持根据表中任意字段分发数据到Kafka topic多个分区
Debezium系列之:实现不同表中的数据始终发往对应的kafka topic分区,实现支持根据表中任意字段分发数据到Kafka topic多个分区 一.需求背景 二.ComputePartition ...
- Python黑客编程基础3网络数据监听和过滤
Python黑客编程3网络数据监听和过滤 课程的实验环境如下: • 操作系统:kali Linux 2.0 • 编程工具:Wing IDE • Python版本:2.7. ...
- 强大的DataGrid组件[13]_字段过滤(Filter)——Silverlight学习笔记[21]
在DataGrid中使用字段过滤可以用来进行数据的筛选,查找出符合条件的信息.本文将为大家介绍如何对DataGrid执行字段过滤. 需要了解的知识 1)PagedCollectionView 它代表了 ...
- 通用数据级别权限的框架设计与实现(3)-数据列表的权限过滤
查看上篇文章通用数据级别权限的框架设计与实现(2)-数据权限的准备工作,我们开始数据列表的权限过滤. 原理:我们在做过滤列表时,根据用户权限自动注入到相关SQL中,实现相关过滤,如果拥有全部权限,则不 ...
- SCREEN屏幕编程时候必须保证SCREN中词典的字段格式必须和数据表中字段的类型长度一致!...
此时任意操作都会出现如下问题 /h调试 回车调试被激活任意操作 执行到第23行时候报错"请输入一个数值",检查数据表中字段参考数据元素以及对应的域均是char类型,此时检查scre ...
- Tips--利用shell脚本批量提取txt文件中任意字段
利用shell脚本批量提取txt文件中任意字段 前言 0. 一个例子 1. cat命令 2. '|'符号与'>'符号 3. grep命令 4. awk命令 前言 对于测试中出现的log,我们经常 ...
- mysql 如何对表排序_学习MySQL:对表中的数据进行排序和过滤
mysql 如何对表排序 In this article, we will learn how we can sort and filter data using the WHERE clause a ...
- 宝剑赠英雄 - 任意字段\条件等效查询, 探探PostgreSQL多列展开式B树
标签 PostgreSQL , 多列索引 , btree , gin , gist , brin , btree_gist , btree_gin , 复合索引 , composite index , ...
- access表格怎么取消冻结字段_如何在excel表格中冻结任意字段?谢谢/excel表格制作...
在excel表格中 如果冻结某一行 怎么设置? 点击你需要冻结的单元格下一行,点击窗口-冻结窗口 如何把excel表格冻结 [视图]下面有对应的按钮,点鼠标就可以,按钮位置如下图: 注意,点之前单元格 ...
- R语言使用subset函数基于组合逻辑筛选dataframe符合条件的数据行(select observations)、并指定需要保留的dataframe数据列或者字段
R语言使用subset函数基于组合逻辑筛选dataframe符合条件的数据行(select observations).并指定需要保留的dataframe数据列或者字段 目录
最新文章
- promise实现多个请求并行串行执行
- SQL server数据库系统部分常用的存储过程及说明
- 如何防止话筒拾音的声学相位抵消?
- vue前端上传文件夹的插件_基于vue-simple-uploader封装文件分片上传、秒传及断点续传的全局上传插件...
- Vsftpd服务的部署及优化
- mysql galera 安装_MySQL Galera 集群的安装过程
- CSS 实现 0.5px 边框线
- Spring Boot 集成 WebSocket通信信息推送!
- ASP.NET Core中HTTP管道和中间件的二三事
- H3C三层交换机划分VLAN示例
- 个人支付源码_[5G时代投资风口源码修复版] 投资区块链+订制UI完美版+对接免签支付+自带发圈推广任务奖励+视频教程...
- OpenGL调用GPU(七)
- VS Code 调试 Angular 和 TypeScript 的配置
- 用java语言写网上购物的语句_用java代码写一个简单的网上购物车程序
- C-Free 5.0注册码分享
- BA无标度网络的仿真实现
- C语言常见问题(10):Sections of code should not be commented out
- [Render] Arm Graphics Analyzer 用户指南 [4] - 分析你的捕获
- 统计学习导论(1)------------一般线性模型介绍
- odoo12 物流 自动计算运费 ,采购销售使用不同计量单位自动换算
热门文章
- netframework 4.5官网下载路径
- android实现下载的核心代码
- lecture2-NN结构的主要类型的概述和感知机
- [文摘]Java正则表达式详解
- Error accessing PRODUCT_USER_PROFILE?
- ERP知识普及连载(21)
- C# Explicit 和 Implicit
- python中的特殊函数__call__
- 英语笔记-some words about description of girl
- hdu 1718 Rank