产品说,我只需要一个有亿点复杂的查询界面

有的时候,你需要动态构建一个比较复杂的查询条件,传入数据库中进行查询。而条件本身可能来自前端请求或者配置文件。那么这个时候,表达式树,就可以帮助到你。本文我们将通过几个简短的示例来了解如何完成这些操作。

你也可能接到过这些需求

c43054f5cb2773e2ee0d552b705f3c56.png
从模型进行查询
c9702c6ba27ee6dc325b23b8e30a18dc.png
基于配置查询

今天我们看看表达式树如何实现这些需求。

一切都还要从盘古开天开始说起

以下是一个简单的单元测试用例。接下来,我们将这个测试用例改的面目全非。

[Test]
public void Normal()
{var re = Enumerable.Range(0, 10).AsQueryable() // 0-9.Where(x => x >= 1 && x < 5).ToList(); // 1 2 3 4var expectation = Enumerable.Range(1, 4); // 1 2 3 4re.Should().BeEquivalentTo(expectation);
}

很久很久以前天和地还没有分开

由于是 Queryable 的关系,所以Where当中的其实是一个表达式,那么我们把它单独定义出来,顺便水一下文章的长度。

[Test]
public void Expression00()
{Expression<Func<int, bool>> filter = x => x >= 1 && x < 5;var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);
}

有个叫盘古的巨人在这混沌之中

Expression 右侧是一个 Lambda ,所以可以捕获上下文中的变量。

这样你便可以把 minValue 和 maxValue 单独定义出来。

于是乎你可以从其他地方来获取 minValue 和 maxValue 来改变 filter。

[Test]
public void Expression01()
{var minValue = 1;var maxValue = 5;Expression<Func<int, bool>> filter = x => x >= minValue && x < maxValue;var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);
}

他睡了一万八千年也都不用上班

那既然这样,我们也可以使用一个方法来创建 Expression。

这个方法,实际上就可以认为是这个 Expression 的工厂方法。

[Test]
public void Expression02()
{var filter = CreateFilter(1, 5);var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Expression<Func<int, bool>> CreateFilter(int minValue, int maxValue){return x => x >= minValue && x < maxValue;}
}

有一天盘古突然醒了但天还没亮

那可以使用 minValue 和 maxValue 作为参数来制作工厂方法,那么用委托当然也可以。

于是,我们可以把左边和右边分别定义成两个 Func,从而由外部来决定左右具体的比较方式。

[Test]
public void Expression03()
{var filter = CreateFilter(x => x >= 1, x => x < 5);var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Expression<Func<int, bool>> CreateFilter(Func<int, bool> leftFunc, Func<int, bool> rightFunc){return x => leftFunc.Invoke(x) && rightFunc.Invoke(x);}
}

他就抡起大斧头朝前方猛劈过去

实际上,左右两个不仅仅是两个Func,其实也可以直接是两个表达式。

不过稍微有点不同的是,表达式的合并需要用 Expression 类型中的相关方法创建。

我们可以发现,调用的地方这次其实没有任何改变,因为 Lambda 既可以隐式转换为 Func 也可以隐式转换为 Expression。

每个方法的意思可以从注释中看出。

[Test]
public void Expression04()
{var filter = CreateFilter(x => x >= 1, x => x < 5);var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Expression<Func<int, bool>> CreateFilter(Expression<Func<int, bool>> leftFunc,Expression<Func<int, bool>> rightFunc){// xvar pExp = Expression.Parameter(typeof(int), "x");// (a => leftFunc(a))(x)var leftExp = Expression.Invoke(leftFunc, pExp);// (a => rightFunc(a))(x)var rightExp = Expression.Invoke(rightFunc, pExp);// (a => leftFunc(a))(x) && (a => rightFunc(a))(x)var bodyExp = Expression.AndAlso(leftExp, rightExp);// x => (a => leftFunc(a))(x) && (a => rightFunc(a))(x)var resultExp = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);return resultExp;}
}

只听枯叉一声黑暗渐渐地就分开

但是,上面的方法,其实可以在优化一下。避免对左右表达式的直接调用。

使用一个叫做 Unwrap 的方法,可以将 Lambda Expression 解构成只包含 Body 部分的表达式。

这是一个自定义的扩展方法,你可以通过 ObjectVisitor[1] 来引入这个方法。

限于篇幅,我们此处不能展开谈 Unwrap 的实现。我们只需要关注和前一个示例中注释的不同即可。

[Test]
public void Expression05()
{var filter = CreateFilter(x => x >= 1, x => x < 5);var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Expression<Func<int, bool>> CreateFilter(Expression<Func<int, bool>> leftFunc,Expression<Func<int, bool>> rightFunc){// xvar pExp = Expression.Parameter(typeof(int), "x");// leftFunc(x)var leftExp = leftFunc.Unwrap(pExp);// rightFunc(x)var rightExp = rightFunc.Unwrap(pExp);// leftFunc(x) && rightFunc(x)var bodyExp = Expression.AndAlso(leftExp, rightExp);// x => leftFunc(x) && rightFunc(x)var resultExp = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);return resultExp;}
}

天和地分开后盘古怕它们还合并

我们可以再优化以下,把 CreateFilter 方法扩展为支持多个子表达式和可自定义子表达式的连接方式。

于是,我们就可以得到一个 JoinSubFilters 方法。

[Test]
public void Expression06()
{var filter = JoinSubFilters(Expression.AndAlso, x => x >= 1, x => x < 5);var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,params Expression<Func<int, bool>>[] subFilters){// xvar pExp = Expression.Parameter(typeof(int), "x");var result = subFilters[0];foreach (var sub in subFilters[1..]){var leftExp = result.Unwrap(pExp);var rightExp = sub.Unwrap(pExp);var bodyExp = expJoiner(leftExp, rightExp);result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);}return result;}
}

他就头顶着天脚蹬着地不知多久

有了前面的经验,我们知道。其实x => x >= 1这个表达式可以通过一个工厂方法来创建。

所以,我们使用一个 CreateMinValueFilter 来创建这个表达式。

[Test]
public void Expression07()
{var filter = JoinSubFilters(Expression.AndAlso,CreateMinValueFilter(1),x => x < 5);var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Expression<Func<int, bool>> CreateMinValueFilter(int minValue){return x => x >= minValue;}Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,params Expression<Func<int, bool>>[] subFilters){// xvar pExp = Expression.Parameter(typeof(int), "x");var result = subFilters[0];foreach (var sub in subFilters[1..]){var leftExp = result.Unwrap(pExp);var rightExp = sub.Unwrap(pExp);var bodyExp = expJoiner(leftExp, rightExp);result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);}return result;}
}

盘古也累得倒下来变成山石河流

当然,可以只使用 Expression 相关的方法来创建x => x >= 1

[Test]
public void Expression08()
{var filter = JoinSubFilters(Expression.AndAlso,CreateMinValueFilter(1),x => x < 5);var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Expression<Func<int, bool>> CreateMinValueFilter(int minValue){// xvar pExp = Expression.Parameter(typeof(int), "x");// minValuevar rightExp = Expression.Constant(minValue);// x >= minValuevar bodyExp = Expression.GreaterThanOrEqual(pExp, rightExp);var result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);return result;}Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,params Expression<Func<int, bool>>[] subFilters){// xvar pExp = Expression.Parameter(typeof(int), "x");var result = subFilters[0];foreach (var sub in subFilters[1..]){var leftExp = result.Unwrap(pExp);var rightExp = sub.Unwrap(pExp);var bodyExp = expJoiner(leftExp, rightExp);result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);}return result;}
}

那么看来盘古也吃不了上班的苦

那既然都用了 Expression 来创建子表达式了,那就干脆再做一点点改进,把x => x < 5也做成从工厂方法获取。

[Test]
public void Expression09()
{var filter = JoinSubFilters(Expression.AndAlso,CreateValueCompareFilter(Expression.GreaterThanOrEqual, 1),CreateValueCompareFilter(Expression.LessThan, 5));var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Expression<Func<int, bool>> CreateValueCompareFilter(Func<Expression, Expression, Expression> comparerFunc,int rightValue){var pExp = Expression.Parameter(typeof(int), "x");var rightExp = Expression.Constant(rightValue);var bodyExp = comparerFunc(pExp, rightExp);var result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);return result;}Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,params Expression<Func<int, bool>>[] subFilters){// xvar pExp = Expression.Parameter(typeof(int), "x");var result = subFilters[0];foreach (var sub in subFilters[1..]){var leftExp = result.Unwrap(pExp);var rightExp = sub.Unwrap(pExp);var bodyExp = expJoiner(leftExp, rightExp);result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);}return result;}
}

所以如果可以不做这需求就别搞

最后,我们在把子表达式的创建通过一点点小技巧。通过外部参数来决定。就基本完成了一个多 And 的值比较查询条件的动态构建。

[Test]
public void Expression10()
{var config = new Dictionary<string, int>{{ ">=", 1 },{ "<", 5 }};var subFilters = config.Select(x => CreateValueCompareFilter(MapConfig(x.Key), x.Value)).ToArray();var filter = JoinSubFilters(Expression.AndAlso, subFilters);var re = Enumerable.Range(0, 10).AsQueryable().Where(filter).ToList();var expectation = Enumerable.Range(1, 4);re.Should().BeEquivalentTo(expectation);Func<Expression, Expression, Expression> MapConfig(string op){return op switch{">=" => Expression.GreaterThanOrEqual,"<" => Expression.LessThan,_ => throw new ArgumentOutOfRangeException(nameof(op))};}Expression<Func<int, bool>> CreateValueCompareFilter(Func<Expression, Expression, Expression> comparerFunc,int rightValue){var pExp = Expression.Parameter(typeof(int), "x");var rightExp = Expression.Constant(rightValue);var bodyExp = comparerFunc(pExp, rightExp);var result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);return result;}Expression<Func<int, bool>> JoinSubFilters(Func<Expression, Expression, Expression> expJoiner,params Expression<Func<int, bool>>[] subFilters){// xvar pExp = Expression.Parameter(typeof(int), "x");var result = subFilters[0];foreach (var sub in subFilters[1..]){var leftExp = result.Unwrap(pExp);var rightExp = sub.Unwrap(pExp);var bodyExp = expJoiner(leftExp, rightExp);result = Expression.Lambda<Func<int, bool>>(bodyExp, pExp);}return result;}
}

还要更多

如果逻辑关系更复杂,有多层嵌套像树形一样,比较方法也很多花样,甚至包含方法,怎么办?

可以参考以下示例:

https://github.com/newbe36524/Newbe.Demo/tree/main/src/BlogDemos/Newbe.ExpressionsTests/Newbe.ExpressionsTests/FilterFactory

如果你对此内容感兴趣,还可以浏览我之前录制的视频进行进一步了解:

  • 戏精分享 C#表达式树,第一季[2]

  • 戏精分享 C#表达式树,第二季[3]

你也可以参阅之前一篇入门:

《只要十步,你就可以应用表达式树来优化动态调用》[4]

或者看MSDN文档,我觉得你也可以有所收获:

https://docs.microsoft.com/dotnet/csharp/programming-guide/concepts/expression-trees/?WT.mc_id=DX-MVP-5003606

这篇相关的代码,可以通过以下地址得到:

https://github.com/newbe36524/Newbe.Demo/blob/main/src/BlogDemos/Newbe.ExpressionsTests/Newbe.ExpressionsTests/Examples/Z01SingleWhereTest.cs

如果你觉得本文不错,记得收藏、点赞、评论、转发。告诉我还想知道点什么哟。

参考资料

[1]

Newbe.ObjectVisitor: https://github.com/newbe36524/Newbe.ObjectVisitor

[2]

戏精分享 C#表达式树,第一季: https://www.bilibili.com/video/BV15y4y1r7pK

[3]

戏精分享 C#表达式树,第二季: https://www.bilibili.com/video/BV1Mi4y1L7oR

[4]

只要十步,你就可以应用表达式树来优化动态调用: https://www.newbe.pro/Newbe.Claptrap/Using-Expression-Tree-To-Build-Delegate/index.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/296494.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

PostgreSQL忘记输入where条件update更新整张表的解决办法

2019独角兽企业重金招聘Python工程师标准>>> 虽然出现这个错误很挫&#xff0c;但有时候还是会被你或者你的同事碰到。为了避免这个错误&#xff0c;PostgreSQL数据库中可以通过触发器来解决&#xff0c;这里用的是plpgsql 。 1、修改postgresql.conf配置 增加&…

视觉开发需要什么程度的数学_角度的概念在视觉上非常直观,但其数学定义并不是那么简单...

角的概念是几何学中最基本的概念之一。当我们研究三角形的性质时&#xff0c;我们自然地建立了三角形的边和角之间的联系。这些联系是在三角学中系统地建立起来的。角是什么&#xff1f;我们如何测量它&#xff1f;虽然角度的概念在视觉上很直观&#xff0c;但它的数学定义却不…

别薅了别薅了!!!再薅就真的被薅秃了!!

▲ 点击查看大家好&#xff0c;超模全新的固定栏目「薅羊毛」上线了&#xff01;既然是薅羊毛&#xff0c;怎么能空着手来&#xff1f;毕竟好用的好吃的&#xff0c;啥都要花钱。与其为那些虚幻的包装价值买单&#xff0c;不如跟着超模君狠狠地薅一把羊毛&#xff0c;「花小钱赚…

GitHub Universe 2021|MS Reactor 邀你共聚年度盛会

关注我们GitHub Universe 2021 将于2021年10月27-28日&#xff08;PDT&#xff09;在线直播&#xff0c;MS Reactor 将与 CSDN 合作进行转播&#xff0c;与你一同观看这场全球开发者盛会。 关于 GitHub UniverseGitHub Universe 是 GitHub 面向全球开发者社区举办的年度重要盛会…

C#中的多线程 - 并行编程 z

原文&#xff1a;http://www.albahari.com/threading/part5.aspx 专题&#xff1a;C#中的多线程 1并行编程Permalink 在这一部分&#xff0c;我们讨论 Framework 4.0 加入的多线程 API&#xff0c;它们可以充分利用多核处理器。 并行 LINQ&#xff08;Parallel LINQ&#xff09…

java数组深拷贝和浅拷贝_java中的深拷贝与浅拷贝(值类型 vs 引用类型)

对象赋值赋值是日常编程过程中最常见的操作&#xff0c;最简单的比如&#xff1a;Student codeSheep new Student(); Student codePig codeSheep;严格来说&#xff0c;这种不能算是对象拷贝&#xff0c;因为拷贝的仅仅只是引用关系&#xff0c;并没有生成新的实际对象&#x…

各大厂抢招WPF,小米这回是下了血本啊...

九银十进入尾声&#xff0c;小米又爆出高薪岗位&#xff1a;35k左右&#xff0c;14薪&#xff0c;招5年左右.NET&#xff0c;要求WPF和自动化( 职位&#xff1a;https://app.mokahr.com/apply/xiaomi/287/#/job/523278c0-c504-4cdc-bb88-28c1b101ac76)。今年招WPF的大厂太多了&…

你永远都不知道你老公可以多幼稚......

1 爸爸带女儿&#xff01;两个幼稚鬼▼2 常州一小区提醒防疫四种语言切换无压力▼3 我知道了&#xff01;他的门牙肯定有条缝▼4 狗子OS&#xff1a;今天栏杆和木棍必须要断一个▼5 掀起你的假发来让我帮你擦擦汗▼6 干啥啥不行&#xff0c;吃饭第一名▼7 一看就是亲妈&…

没有女朋友,可能是因为你数学不好

全世界只有3.14 % 的人关注了爆炸吧知识孔子和耶稣曾说过&#xff1a;初恋无限好。回想起青涩的大学时光&#xff0c;告别了高中时代紧张的学习氛围和父母、老师的谆谆告诫&#xff0c;爱情也不再是伊甸园里的禁果。关于爱情的开展和维系&#xff0c;在Levinger&#xff08;198…

C#10,带来了Date和Time类型

C#10引入了日期DateOnly&#xff0c;时间TimeOnly&#xff1a;//从DateTime转换 Console.WriteLine(DateOnly.FromDateTime(DateTime.Now)); //从字会串转换 Console.WriteLine(DateOnly.Parse("2021-10-23")); //从0001-01-01到现在的天数 Console.WriteLine(DateOn…

数据可视化----我在寻找一款类似vfp或是access这样自带可视化风格的数据库或是键盘数据库...

我在寻找一款类似vfp或是access这样自带可视化风格的数据库或是键盘数据库影响redis,mongodb今后发展的我也认为是一些可视化工具的支持http://blog.xiqiao.info/tag/data-visualization

AgileConfig 1.5 发布 - 支持多环境配置

AgileConfig 从发布到现在&#xff0c;收到不少同学的 issue 说需要多环境的支持。也就是一个应用在不同的环境下可以配置不同的配置项。这是一个非常有用的功能&#xff0c;就跟我们开发的时候会设置多个 appsettings.json 文件一样&#xff0c;比如 appsettings.development.…

知乎高赞:哪些事坚持做3个月就会有巨大改变?

全世界只有3.14 % 的人关注了爆炸吧知识知乎上有个高赞问题&#xff1a;有哪些书看完后&#xff0c;会让人后悔没早看到&#xff1f;答案各有不一、包罗万象。但有一点&#xff0c;大家达成了共识&#xff1a;要多读书。人生漫长&#xff0c;而我们都不会是一成不变的&#xff…

clover 主题_Clover主题更换

小白前言今天,黑果小白来教大家更换自己clover引导的 主题 ,相信大家看了这么久的主题 ,应该也厌倦了吧,换一个主题,增加一下新鲜度,废话不多说,上教程!更换前的准备要想更换主题&#xff0c;你就必须得有主题可以更换&#xff0c;没有主题&#xff0c;神马都是浮云&#xff01…

IOS使用Auto Layout中的VFL适配

做登录页面,之前做都是用frame做,今天想着用Auto Layout中的VFL来做。觉得做的效果还是可以的(自恋一下下)。 首先看下效果图和标记图 自己在做的过程中也遇到了好多问题,不过也一个一个的自己解决了 1.子视图居中的问题 上一博客我也写了,由于指定了视图的宽度高度,想让视图居…

左手菲尔兹右手突破奖,这个中国女婿其实是英国贵族?拿到300万奖金后他这样说……...

全世界只有3.14 % 的人关注了爆炸吧知识如果说科学界里高大上的奖项&#xff0c;你还是只能想到诺贝尔&#xff0c;菲尔兹&#xff0c;那可能真的是out了。毕竟现在都0202年了。少年&#xff0c;突破奖了解一下&#xff1f;作为有马云&#xff0c;马化腾&#xff0c;还有俄罗斯…

理财平台架构分析

一个理财平台可以从不同的维度来看。对于一个消费者来说&#xff0c;最宏观的看法&#xff0c;P2P公司的理财平台相当于一个中介&#xff0c;一边用于对接用户&#xff0c;一边用于对接产品提供商。这个中介系统负责用户和产品提供者之间的交互。对于一个P2P理财公司来说&#…

mysql数据压缩存储_压缩文本,然后存储在mysql数据库中

I am developing a web application using php and MySQL. I am compressing the text using gzcompress() php function before storing in MySQL database / storing compressed form of text in database. My Question is that is this OK? to store compressed form? Or …

Abp VNext 集成sharding-core 分表分库

ShardingCore 易用、简单、高性能、普适性&#xff0c;是一款扩展针对efcore生态下的分表分库的扩展解决方案,支持efcore2的所有版本,支持efcore2的所有数据库、支持自定义路由、动态路由、高性能分页、读写分离的一款组件&#xff0c;如果你喜欢这组件或者这个组件对你有帮助请…

引热议!多所高校明文规定:不再给部分全日制研究生安排宿舍

全世界只有3.14 % 的人关注了爆炸吧知识本文转自募格学术。近日&#xff0c;高校宿舍问题频发。矛盾点基本都集中在宿舍加塞人&#xff0c;宿舍环境不好&#xff0c;为了迎接新生强制老生搬宿舍但宿舍环境一言难尽等...... 不过好歹也都算解决了研究生住宿的问题。但近日&#…