通常我们在做一些数据过滤的操作的时候,经常需要做一些判断再进行是否要对其进行条件过滤。

普通做法

最原始的做法我们是先通过If()判断是否需要进行数据过滤,然后再对数据源使用Where来过滤数据。

示例如下:

if(!string.IsNullOrWhiteSpace(str))
{
query = query.Where(a => a == str);
}

封装WhereIf做法

进阶一些的就把普通做法的代码封装成一个扩展方法,WhereIf指代一个名称,也可以有其他名称,本质是一样的。

示例如下:

public static IQueryable<T> WhereIf<T>([NotNull] this IQueryable<T> query, bool condition, Expression<Func<T, int, bool>> predicate)
{
return condition
? query.Where(predicate)
: query;
}

使用方式:

query.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str);

封装WhereIf做法相比普通做法,已经可以减少我们代码的很多If块了,看起来也优雅一些。

但是如果查询条件增多的话,我们依旧需要写很多WhereIf,就会有这种现象:

query
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str)
.WhereIf(!string.IsNullOrWhiteSpace(str), a => a == str);

条件一但增多很多的话,这样一来代码看起来就又不够优雅了~

这时候就想,如果只用一个Where传进去一个对象,自动解析条件进行数据过滤,是不是就很棒呢~

WhereObj做法

想法来了,那就动手实现一下。

首先我们需要考虑如何对对象的属性进行标记来获取我们作为条件过滤的对应属性。那就得加一个Attribute,这里实现一个CompareAttribute,用于对对象的属性进行标记。

[AttributeUsage(AttributeTargets.Property)]
public class CompareAttribute : Attribute
{
public CompareAttribute(CompareType compareType)
{
CompareType = compareType;
} public CompareAttribute(CompareType compareType, string compareProperty) : this(compareType)
{
CompareProperty = compareProperty;
} public CompareType CompareType { get; set; } public CompareSite CompareSite { get; set; } = CompareSite.LEFT; public string? CompareProperty { get; set; }
} public enum CompareType
{
Equal,
NotEqual,
GreaterThan,
GreaterThanOrEqual,
LessThan,
LessThanOrEqual,
Contains,
StartsWith,
EndsWith,
IsNull,
IsNotNull
} public enum CompareSite
{
RIGHT,
LEFT
}

这里CompareType表示要进行比较的操作,很简单,一目了然。

CompareSite则表示在进行比较的时候比较的数据处于比较符左边还是右边,在CompareAttribute给与默认值在左边,表示比较的源数据处于左边。

CompareProperty则表示比较的属性名称,空的话则直接使用对象名称,如果有值则优先使用。

Attribute搞定了,接下来则实现我们的WhereObj

这里由于需要动态的拼接表达式,这里使用了DynamicExpresso.Core库来进行动态表达式生成。

先上代码:

namespace System.Linq;

public static class WhereExtensions
{
public static IQueryable<T> WhereObj<T>(this IQueryable<T> queryable, object parameterObject)
{
var interpreter = new Interpreter();
interpreter = interpreter.SetVariable("o", parameterObject);
var properties = parameterObject.GetType().GetProperties().Where(p => p.CustomAttributes.Any(a=>a.AttributeType == typeof(CompareAttribute)));
var whereExpression = new StringBuilder();
foreach (var property in properties)
{
if(property.GetValue(parameterObject) == null)
{
continue;
} var compareAttribute = property.GetCustomAttribute<CompareAttribute>(); var propertyName = compareAttribute!.CompareProperty ?? property.Name; if (typeof(T).GetProperty(propertyName) == null)
{
continue;
} if (whereExpression.Length > 0)
{
whereExpression.Append(" && ");
} whereExpression.Append(BuildCompareExpression(propertyName, property, compareAttribute.CompareType, compareAttribute.CompareSite));
} if(whereExpression.Length > 0)
{
return queryable.Where(interpreter.ParseAsExpression<Func<T, bool>>(whereExpression.ToString(), "q"));
}
return queryable;
}
public static IEnumerable<T> WhereObj<T>(this IEnumerable<T> enumerable, object parameterObject)
{
var interpreter = new Interpreter();
interpreter = interpreter.SetVariable("o", parameterObject);
var properties = parameterObject.GetType().GetProperties().Where(p => p.CustomAttributes.Any(a=>a.AttributeType == typeof(CompareAttribute)));
var whereExpression = new StringBuilder();
foreach (var property in properties)
{
if(property.GetValue(parameterObject) == null)
{
continue;
} var compareAttribute = property.GetCustomAttribute<CompareAttribute>(); var propertyName = compareAttribute!.CompareProperty ?? property.Name; if (typeof(T).GetProperty(propertyName) == null)
{
continue;
} if (whereExpression.Length > 0)
{
whereExpression.Append(" && ");
} whereExpression.Append(BuildCompareExpression(propertyName, property, compareAttribute.CompareType, compareAttribute.CompareSite));
} if(whereExpression.Length > 0)
{
return enumerable.Where(interpreter.ParseAsExpression<Func<T, bool>>(whereExpression.ToString(), "q").Compile());
}
return enumerable;
} private static string BuildCompareExpression(string propertyName, PropertyInfo propertyInfo, CompareType compareType, CompareSite compareSite)
{
var source = $"q.{propertyName}";
var target = $"o.{propertyInfo.Name}";
return compareType switch
{
CompareType.Equal => compareSite == CompareSite.LEFT ? $"{source} == {target}" : $"{target} == {source}",
CompareType.NotEqual => compareSite == CompareSite.LEFT ? $"{source} != {target}" : $"{target} != {source}",
CompareType.GreaterThan => compareSite == CompareSite.LEFT ? $"{source} < {target}" : $"{target} > {source}",
CompareType.GreaterThanOrEqual => compareSite == CompareSite.LEFT ? $"{source} <= {target}" : $"{target} >= {source}",
CompareType.LessThan => compareSite == CompareSite.LEFT ? $"{source} > {target}" : $"{target} < {source}",
CompareType.LessThanOrEqual => compareSite == CompareSite.LEFT ? $"{source} >= {target}" : $"{target} <= {source}",
CompareType.Contains => compareSite == CompareSite.LEFT ? $"{source}.Contains({target})" : $"{target}.Contains({source})",
CompareType.StartsWith => compareSite == CompareSite.LEFT ? $"{source}.StartsWith({target})" : $"{target}.StartsWith({source})",
CompareType.EndsWith => compareSite == CompareSite.LEFT ? $"{source}.EndsWith({target})" : $"{target}.EndsWith({source})",
CompareType.IsNull => $"{source} == null",
CompareType.IsNotNull => $"{source} != null",
_ => throw new NotSupportedException()
};
}
}

代码对IEnumerable和IQueryable都进行了扩展,总共行数100行。

在WhereObj中,我们传入一个parameterObject,然后获取对象的所有加了CompareAttribute的属性。

然后进行循环拼接条件。在循环中我们先判断属性是否有值,有值才会添加表达式。所以建议条件属性都为可空类型。

if(property.GetValue(parameterObject) == null)
{
continue;
}

然后获取属性的CompareAttribute, 先指定条件属性名称,在判断属性是否在源对象存在,如果不存在则不处理。

if (typeof(T).GetProperty(propertyName) == null)
{
continue;
}

最后就是根据CompareType来动态生成拼接的表达式了。

BuildCompareExpression方法根据CompareType和CompareSite动态拼接表达式字符串,然后使用Interpreter.ParseAsExpression<Func<T, bool>>转换成我们的表达式类型。就完成啦。

测试效果

搞一个Customer类和CustomerFilter,再搞一个数据。

namespace Test
{
public class Customer
{
public string Name { get; set; }
public int Age { get; set; }
public char Gender { get; set; }
}
public class CustomerFilter
{
[Compare(CompareType.StartsWith)]
public string? Name { get; set; }
[Compare(CompareType.Contains, "Name", CompareSite = CompareSite.RIGHT)]
public List<string>? Names { get; set; }
[Compare(CompareType.GreaterThan)]
public int? Age { get; set; }
[Compare(CompareType.Equal)]
public char? Gender { get; set; }
} public class T
{
public static IEnumerable<Customer> customers = (new List<Customer> {
new Customer() { Name = "David", Age = 31, Gender = 'M' },
new Customer() { Name = "Mary", Age = 29, Gender = 'F' },
new Customer() { Name = "Jack", Age = 2, Gender = 'M' },
new Customer() { Name = "Marta", Age = 1, Gender = 'F' },
new Customer() { Name = "Moses", Age = 120, Gender = 'M' },
}).AsEnumerable();
} }

测试代码

T.customers.WhereObj(new CustomerFilter()
{
//Name = "M",
Names = ["Mary", "Jack"],
//Age = 20,
//Gender = 'M'
})
.ToList().ForEach(c => Console.WriteLine(c.Name));



可以看到正常执行。

这样我们在应对条件很多的数据过滤的时候,就可以只用一个WhereObj就可以代替很多个WhereIf的拼接了。同时,在添加新条件的时候我们也无需修改其他业务代码。

还在拼冗长的WhereIf吗?100行代码解放这个操作的更多相关文章

  1. 100行代码实现现代版Router

      原文:http://www.html-js.com/article/JavaScript-version-100-lines-of-code-to-achieve-a-modern-version ...

  2. 100行代码实现最简单的基于FFMPEG+SDL的视频播放器(SDL1.x)【转】

    转自:http://blog.csdn.net/leixiaohua1020/article/details/8652605 版权声明:本文为博主原创文章,未经博主允许不得转载.   目录(?)[-] ...

  3. 【编程教室】PONG - 100行代码写一个弹球游戏

    大家好,欢迎来到 Crossin的编程教室 ! 今天跟大家讲一讲:如何做游戏 游戏的主题是弹球游戏<PONG>,它是史上第一款街机游戏.因此选它作为我这个游戏开发系列的第一期主题. 游戏引 ...

  4. 100行代码实现HarmonyOS“画图”应用,eTS开发走起!

    本期我们给大家带来的是"画图"应用开发者Rick的分享,希望能给你的HarmonyOS开发之旅带来启发~ 介绍 2021年的华为开发者大会(HDC2021)上,HarmonyOS ...

  5. 【转】100行代码实现最简单的基于FFMPEG+SDL的视频播放器

    FFMPEG工程浩大,可以参考的书籍又不是很多,因此很多刚学习FFMPEG的人常常感觉到无从下手.我刚接触FFMPEG的时候也感觉不知从何学起. 因此我把自己做项目过程中实现的一个非常简单的视频播放器 ...

  6. 仅100行的JavaScript DOM操作类库

    如果你构建过Web引用程序,你可能处理过很多DOM操作.访问和操作DOM元素几乎是每一个Web应用程序的通用需求.我们我们经常从不同的控件收集信息,我们需要设置value值,修改div或span标签的 ...

  7. 用JavaCV改写“100行代码实现最简单的基于FFMPEG+SDL的视频播放器 ”

    FFMPEG的文档少,JavaCV的文档就更少了.从网上找到这篇100行代码实现最简单的基于FFMPEG+SDL的视频播放器.地址是http://blog.csdn.net/leixiaohua102 ...

  8. 100行代码让您学会JavaScript原生的Proxy设计模式

    面向对象设计里的设计模式之Proxy(代理)模式,相信很多朋友已经很熟悉了.比如我之前写过代理模式在Java中实现的两篇文章: Java代理设计模式(Proxy)的四种具体实现:静态代理和动态代理 J ...

  9. GuiLite 1.2 发布(希望通过这100+行代码来揭示:GuiLite的初始化,界面元素Layout,及消息映射的过程)

    经过开发群的长期验证,我们发现:即使代码只有5千多行,也不意味着能够轻松弄懂代码意图.痛定思痛,我们发现:虽然每个函数都很简单(平均长度约为30行),可以逐个击破:但各个函数之间如何协作,却很难说明清 ...

  10. 100行代码搞定抖音短视频App,终于可以和美女合唱了。

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由视频咖 发表于云+社区专栏 本文作者,shengcui,腾讯云高级开发工程师,负责移动客户端开发 最近抖音最近又带了一波合唱的节奏,老 ...

随机推荐

  1. 基于阿里云GPU云服务器的AIACC助力UC搜索业务性能提效380%,每年节省数千万成本

    简介: 用阿里云GPU计算实例来满足UC极致性价比需求 文丨阿里云神龙计算平台AI加速团队 & UC搜索架构部推理引擎团队 导语:作为国产行列里占有率排名第一的移动浏览器,UC浏览器自身承载着 ...

  2. 消息队列 RabbitMQ 遇上可观测--业务链路可视化

    简介: 本篇文章主要介绍阿里云消息队列 RabbitMQ 版的可观测功能.RabbitMQ 的可观测能力相对开源有了全面的加强,为业务链路保驾护航. 作者:文婷.不周 本篇文章主要介绍阿里云消息队列 ...

  3. 揭秘阿里云神龙团队拿下TPCx-BB排名第一的背后技术

    ​简介:阿里云自主研发的神龙大数据加速引擎获得了TPCx-BB SF3000世界排名第一的成绩. ​ 一  背景介绍 近日,TPC Benchmark Express-BigBench(简称TPCx- ...

  4. 双11特刊|十年磨一剑,云原生多模数据库Lindorm 2021双11总结

    ​前言 2021 年,转眼 Lindorm 已经在阿里发展了十年的时间,从基于 HBase 深度改造的 Lindorm 1.0 版本,到全面重构,架构大幅升级的 Lindorm 2.0 版本:从单一的 ...

  5. Go语言入门分享

    ​简介: Go语言出自Ken Thompson.Rob Pike和Robert Griesemer之手,起源于2007年,并在2009年正式对外发布.Go的主要目标是"兼具Python等动态 ...

  6. [GPT] 如何让 vue-router 打开新窗口

      在Vue Router中打开新窗口可以通过使用 <router-link> 组件的 target 属性来实现.将 target 属性设置为`"_blank"`将会在 ...

  7. 2018-8-10-使用-Resharper-快速做适配器

    title author date CreateTime categories 使用 Resharper 快速做适配器 lindexi 2018-08-10 19:16:51 +0800 2018-2 ...

  8. Django之ajax简介

    1.MTV与MVC 框架类型:MVC: M:models V:views C:controller Django用的框架就是MTV MTV: M:models T:templates V:views ...

  9. 《Effective C++》第三版-1. 让自己习惯C++(Accustoming Yourself to C++)

    目录 条款01:视C++为一个语言联邦(View C++ as a federation of languages) 条款02:尽量以const.enum.inline替换#define(Prefer ...

  10. Solution Set - CDQ分治&整体二分

    A[洛谷P2163].给定平面上若干个点,多次询问给定矩形内的点数. B[洛谷P3810].给定若干个三元组,对所有\(k\),求这样三元组的个数:恰有\(k\)个三元组,满足其每个分量都不超过它的相 ...