使用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) 直接对全集合处理的
先感受下代码
public class CriteriaCollectionHandler : ICollectionHandler
{
/* By Harvey Hu. @2016 */ protected string PropertyName { get; set; } protected ComparerEnum Comparer { get; set; } protected object Target { get; set; } // public CriteriaCollectionHandler(string propertyName, object target, ComparerEnum comparer)
{
this.PropertyName = propertyName;
this.Comparer = comparer;
this.Target = target;
} private IQueryable<T> Filter<T>(IQueryable<T> source, string propertyName, ComparerEnum comparer, object target)
{
var type = typeof(T);
var property = type.GetProperty(propertyName, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase); var parameter = Expression.Parameter(type, "p");
Expression propertyAccess = Expression.MakeMemberAccess(parameter, property);
if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
var getValueOrDefault = property.PropertyType.GetMethods().First(p => p.Name == "GetValueOrDefault");
propertyAccess = Expression.Call(propertyAccess, getValueOrDefault);
}
var constExpression = Expression.Constant(ConvertTo(target, property.PropertyType)); // 转换为target的类型,以作比较
Expression comparisionExpression;
switch (comparer)
{
case ComparerEnum.Eq:
comparisionExpression = Expression.Equal(propertyAccess, constExpression);
break;
case ComparerEnum.Ne:
comparisionExpression = Expression.NotEqual(propertyAccess, constExpression);
break;
case ComparerEnum.Lt:
comparisionExpression = Expression.LessThan(propertyAccess, constExpression);
break;
case ComparerEnum.Gt:
comparisionExpression = Expression.GreaterThan(propertyAccess, constExpression);
break;
case ComparerEnum.Le:
comparisionExpression = Expression.LessThanOrEqual(propertyAccess, constExpression);
break;
case ComparerEnum.Ge:
comparisionExpression = Expression.GreaterThanOrEqual(propertyAccess, constExpression);
break;
case ComparerEnum.StringLike:
if (property.PropertyType != typeof(string))
{
throw new NotSupportedException("StringLike is only suitable for string type property!");
} var stringContainsMethod = typeof(CriteriaCollectionHandler).GetMethod("StringContains"); comparisionExpression = Expression.Call(stringContainsMethod, propertyAccess, constExpression); break;
default:
comparisionExpression = Expression.Equal(propertyAccess, constExpression);
break;
} var compareExp = Expression.Lambda(comparisionExpression, parameter);
var typeArguments = new Type[] { type };
var methodName = "Where"; //sortOrder == SortDirection.Ascending ? "OrderBy" : "OrderByDescending";
var resultExp = Expression.Call(typeof(Queryable), methodName, typeArguments, source.Expression, Expression.Quote(compareExp)); return source.Provider.CreateQuery<T>(resultExp);
} public static bool StringContains(string value, string subValue)
{
if (value == null)
{
return false;
} return value.Contains(subValue);
} protected object ConvertTo(object convertibleValue, Type targetType)
{
if (null == convertibleValue)
{
return null;
} if (!targetType.IsGenericType)
{
return Convert.ChangeType(convertibleValue, targetType);
}
else
{
Type genericTypeDefinition = targetType.GetGenericTypeDefinition();
if (genericTypeDefinition == typeof(Nullable<>))
{
var temp = Convert.ChangeType(convertibleValue, Nullable.GetUnderlyingType(targetType));
var result = Activator.CreateInstance(targetType, temp);
return result;
}
}
throw new InvalidCastException(string.Format("Invalid cast from type \"{0}\" to type \"{1}\".", convertibleValue.GetType().FullName, targetType.FullName));
} public virtual ICollection<T> Execute<T>(ICollection<T> values)
{
var result = Filter(values.AsQueryable(), this.PropertyName, this.Comparer, this.Target).ToList();
return result;
} }
使用示例(伪码):
var criteria1 = New CriteriaCollectionHandler(“ServerName”, “server”, ComparerEnum.StringLike); // serverName like 'server'”
var criteria2 = New CriteriaCollectionHandler(“ProcessorMaxValue”, , ComparerEnum.Gt);
var criteria3 = New CriteriaCollectionHandler(“Datetime”, Datetime.Parse("2016/12/9"), ComparerEnum.lt);
ICollection<T> result = criteria3.Execute(
criteria2.Execute(
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过程也参考了博客园的其他朋友的文章。在此贡献出来, 也希望能帮助一些朋友。
使用Expression实现数据的任意字段过滤(1)的更多相关文章
- 使用Expression实现数据的任意字段过滤(2)
上一篇<使用Expression实现数据的任意字段过滤(1)>, 我们实现了通过CriteriaCollectionHandler对象来处理集合数据过滤.通过适当的扩展, 应该可以满足一般 ...
- 齐博x1 万能fun 调用任意数据表 任意字段就是这么任性调用
列举了几个常用的查询进行简单封装,虽然系统也有内置的但是很多人不大会就二次封装简化了一下. 这里只封装了一个条件 多个条件的自己再封装或者用标签解决比较好 这里只是说fun可以万能调用 1获取任意表的 ...
- 写出java8实现对List<User>中的username字段过滤出不等于张三的数据
写出java8实现对List<User>中的username字段过滤出不等于张三的数据... 对...这个是一道面试题.当时没有看过java8的新特性...所以有点懵. 看完之后感觉 真. ...
- Python黑客编程基础3网络数据监听和过滤
网络数据监听和过滤 课程的实验环境如下: • 操作系统:kali Linux 2.0 • 编程工具:Wing IDE • Python版本:2.7.9 • 涉及 ...
- 处理json数据的空数据为任意字符
处理json数据的空数据为任意字符 有时候从后台返回来的数据需要处理一下,根据实际开发需求,不能在页面上直接显示空字符,需要显示为"无内容"或者其他字段,而有些json数据结构比较 ...
- ASP.NET实现二维码 ASP.Net上传文件 SQL基础语法 C# 动态创建数据库三(MySQL) Net Core 实现谷歌翻译ApI 免费版 C#发布和调试WebService ajax调用WebService实现数据库操作 C# 实体类转json数据过滤掉字段为null的字段
ASP.NET实现二维码 using System;using System.Collections.Generic;using System.Drawing;using System.Linq;us ...
- MVC中构建Linq条件、排序、Selector字段过滤
代码: System.Linq.Expressions.Expression<Func<Domain.S_ROLE, bool>> expressWhere1 = (c =&g ...
- 如何在K3 WISE BOS集成开发工具中自定义字段过滤条件
1.结论 对于输入过滤条件后BOS报“列名不正确”的过滤条件,要在列名前增加x2标识 无效的过滤 FNumber ,,,,,) 正确的过滤 x2.FNumber ,,,,,) 2.完全可以不看的探索过 ...
- SQL批量更新数据库中所有用户数据表中字段类型为tinyint为int
--SQL批量更新数据库中所有用户数据表中字段类型为tinyint为int --关键说明:--1.从系统表syscolumns中的查询所有xtype='48'的记录得到类型为[tinyint]的字段- ...
随机推荐
- 避免重复造轮子的UI自动化测试框架开发
一懒起来就好久没更新文章了,其实懒也还是因为忙,今年上半年的加班赶上了去年一年的加班,加班不息啊,好了吐槽完就写写一直打算继续的自动化开发 目前各种UI测试框架层出不穷,但是万变不离其宗,驱动PC浏览 ...
- 学点HTTP知识
不学无术 又一次感觉到不学无术,被人一问Http知识尽然一点也没答上来,丢人丢到家了啊.平时也看许多的技术文章,为什么到了关键时刻就答不上来呢? 确实发现一个问题,光看是没有用的,需要实践.看别人说的 ...
- 消息队列 Kafka 的基本知识及 .NET Core 客户端
前言 最新项目中要用到消息队列来做消息的传输,之所以选着 Kafka 是因为要配合其他 java 项目中,所以就对 Kafka 了解了一下,也算是做个笔记吧. 本篇不谈论 Kafka 和其他的一些消息 ...
- Gradle配置APK自动签名完整流程
转载请注明出处:http://www.cnblogs.com/LT5505/p/6256683.html 一.生成签名 1.命令行生成签名,输入命令keytool -genkey -v -keysto ...
- SuperMap iClient for JavaScript 新手入门
地理信息系统(英语:Geographic Information System,缩写:GIS)是一门综合性学科,结合地理学与地图学,已经广泛的应用在不同的领域,是用于输入.存储.查询.分析和显示地理数 ...
- [WCF]缺少一行代码引发的血案
这是今天作项目支持的发现的一个关于WCF的问题,虽然最终我只是添加了一行代码就解决了这个问题,但是整个纠错过程是痛苦的,甚至最终发现这个问题都具有偶然性.具体来说,这是一个关于如何自动为服务接口(契约 ...
- [C#] C# 知识回顾 - 表达式树 Expression Trees
C# 知识回顾 - 表达式树 Expression Trees 目录 简介 Lambda 表达式创建表达式树 API 创建表达式树 解析表达式树 表达式树的永久性 编译表达式树 执行表达式树 修改表达 ...
- JDBC Tutorials: Commit or Rollback transaction in finally block
http://skeletoncoder.blogspot.com/2006/10/jdbc-tutorials-commit-or-rollback.html JDBC Tutorials: Com ...
- android_m2repository_rxx.zip下载地址以及MD5
地址 MD5 https://dl-ssl.google.com/android/repository/android_m2repository_r08.zip 8C8EC4C731B7F55E646 ...
- 如何区别数据库删除语句drop与delete与truncate?
1.delete:删除数据表中的行(可以删除某一行,也可以在不删除数据表的情况下删除所有行) 删除某一行:delete from 数据表名称 where 列名称=值: 删除所有行:delete*fro ...