【翻译】C#表达式中的动态查询

当您使用LINQ来处理数据库时,这种体验是一种神奇的体验,对吗?你把数据库实体像一个普通的收集,使用Linq中像WhereSelect或者 Take,这些简单的使用就能让代码可用了。

但是,让我们考虑一下这里是如何通过动态查询和表达式树实现此功能的:幕后发生的事情。您编写的LINQ查询将转换为SQL(或其他方式),并将该SQL查询发送到数据库。然后将数据库的响应映射到C#对象。但是,如何完全转换为SQL?

在本文中,您将看到诸如Entity Framework和MongoDB C#驱动程序之类的框架如何使用表达式树进行转换。您将看到如何亲自使用表达式树来构建动态查询。这些查询是您无法在编译时创建的查询,因为您将知道该查询仅在运行时的外观。

对可查询树和表达式树进行揭秘

考虑以下使用Entity Framework 6的C#代码:

DbSet<Student> students = context.Students;
var billie = await students.Where(s => s.StudentName == "Billie").ToListAsync();

执行时,实体框架会产生以下SQL查询:

SQL:SELECT[Extent1].[StudentID] AS [StudentID],[Extent1].[StudentName] AS [StudentName],[Extent1].[DateOfBirth] AS [DateOfBirth],FROM [dbo].[Students] AS [Extent1]WHERE N'Billie' = [Extent1].[StudentName]

请注意,WHERESQL查询中有一个操作。那不是很明显。如果SQL不包含WHERE,则所有学生都将从数据库中带走,并且筛选将在.NET进程中执行。实际上,以下代码可以做到这一点:

//BAD:
DbSet<Student> students = context.Students;
Func<Student, bool> predicate = s => s.StudentName == "Billie";
var x = students.Where(predicate).ToList();

在最后一个示例中,SQL查询使所有学生进入流程,并将其映射到常规集合。不同之处在于,在第一段代码中,lambda是一个Expression<Func<Student, bool>>,它允许实体框架将其添加到SQL查询中。在第二段代码中,lambda是a Func<Student, bool>,因此将Where执行操作符之前的所有操作并将其转换为常规IEnumerable集合,然后执行其余的查询。

第二段代码在性能,内存和网络方面很糟糕。我们从网络中获取了许多对象,而不是仅从数据库中获取一个项目。然后,我们使用CPU将它们序列化为C#对象。并用完内存将它们存储在进程的堆中。

因此,让我们回到第一段代码。如何await students.Where(s => s.StudentName == "Billie").ToListAsync()产生一个包含的SQL查询WHERE N'Billie' = [Extent1].[StudentName]

答案是表达树。该代码s => s.StudentName == "Billie"实际上是一个结构化查询,可以通过编程将其分解为节点树。在此示例中,有6个节点。最顶层的节点是lambda表达式。左侧是lambda参数。在它的右边是Equal表示表达式的lambda主体。实体框架具有遍历这些表达式树并构造SQL查询的算法。其他数据源提供程序(如Mongo DB C#驱动程序)也会发生同样的事情,除了它会构造一个MongoDB json查询。

9e29038719e39b6780f53f784d09ce7e.png

C#表达式树

在第一段代码中,类型s => s.StudentName == "Billie"Expression<Func<Student, bool>>。这表示生成树的表达式树Func<Student, bool>。该Where子句接受这种类型的参数,因为aDbSet<TEntity>实现了IQueryable<T>接口,这要求它与表达式树配合使用。相反,常规集合(如数组或a List<T>IEnumerable意味着它将lambdas => s.StudentName == "Billie"用作常规函数。

好的,但是我该如何利用它呢?

在大多数情况下,使用表达树的人们就是在构建世界实体框架的人们。但是在某些特定情况下,它变得非常有用。这是我们最近在Ozcode[1](我的日常工作)中遇到的一个用例:

我们想在名为Error的数据库实体上创建动态服务器端过滤。该实体具有许多属性,我们希望允许用户对其进行过滤。因此过滤应根据被允许UsernameCountryVersion,或任何其他财产。这是我们需要实现的API:

IQueryable<Error> _errors; 
public IEnumerable<Error> GetErrors(string propertyToFilter, string value){ /*..*/}

在这种情况下,propertyToFilter是的属性Error。使用常规的LINQ,唯一的方法就是使用巨大的switch / case语句。有点像这样:

IQueryable<Error> _errors;public IEnumerable<Error> GetErrors(string propertyToFilter, string value)
{switch (propertyToFilter){case "Username":return await _errors.Where(e=> e.Username == value).ToListAsync();case "Country":return await _errors.Where(e=> e.Country == value).ToListAsync();case "Version":return await _errors.Where(e=> e.Version == value).ToListAsync();// ...        }
}

您可能会同意这不是理想的选择。除了必须编写所有这些东西之外,它还非常容易出现错误。如果添加了属性怎么办?如果重命名怎么办?整个事情一团糟。

通过动态查询和表达式树可以实现此功能的方法如下:

private async static Task<IEnumerable<Error>> GetErrors(string propertyToFilter, string value)
{var error = Expression.Parameter(typeof(Error));var memberAccess = Expression.PropertyOrField(error, propertyToFilter);var exprRight = Expression.Constant(value);var equalExpr = Expression.Equal(memberAccess, exprRight);Expression<Func<Error, bool>> lambda = Expression.Lambda<Func<Error, bool>>(equalExpr, error);return await _errors.Where(lambda).ToListAsync();
}

这里的每一行代码代表表达式树中的一个节点。它们共同构成了最高节点-lambda。然后,可以在LINQ中使用动态表达式,并生成服务器端SQL查询。我认为很好。

解决此问题的另一种方法是构建自定义SQL查询字符串。在Ozcode中,我们使用的是MongoDB,因此SQL不适合使用,但我们可以创建一个自定义的MongoDB JSON查询字符串。也不是太难,但是我认为表达式树方法更加灵活和可靠。一方面,您可以将其放在LINQ中并与其他LINQ运算符组合。此外,当有诸如Entity Framework之类的经过测试的框架可以为您执行此操作时,为什么还要编写自己的查询。

概要

回顾一下。这是本文中的一些关键点:

•常规函数/委托与表达式之间的区别在于,表达式可以用结构化树表示。可以轻松地分析该树以创建诸如数据库查询之类的东西。•支持表达式的数据源实现该IQueryable接口。•如果您无法使用表达式(以及使用常规方法或委托),则查询将在服务器端而不在数据库端,这对于性能而言将是可怕的。•使用lambda(不带主体)时,表达式是无缝创建的,因此这些年来您可能一直都在这样做。•您可以自己使用表达式树来创建动态查询。这在无法在编译时仅在运行时构建查询的情况下很有用。

References

[1] Ozcode: https://oz-code.com

[2]: https://www.mediavine.com/

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

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

相关文章

SVD++:推荐系统的基于矩阵分解的协同过滤算法的提高

1.背景知识 在讲SVD之前&#xff0c;我还是想先回到基于物品相似的协同过滤算法。这个算法基本思想是找出一个用户有过正反馈的物品的相似的物品来给其作为推荐。其公式为&#xff1a; 其中 rui 表示预测用户u对物品i的喜爱程度。wij 是物品i&#xff0c;j之间的相似度&#xf…

资源文件(.RES)的应用

资源档有什麽用处呢&#xff1f;最重要的有两个地方1.国际发行&#xff1a;我们将Application中所有的文字从Resource用读取&#xff0c;那麽&#xff0c;只要更动 Resource档的内容&#xff0c;就可以用不同语言的方式来显示。2.管理资源&#xff1a;例如说&#x…

Linux下Tomcat设置自动启动

在linux系统下&#xff0c;设置某个服务自启动的话&#xff0c;需要在/etc/rcX.d下挂载&#xff0c;还要在/etc/init.d/下写启动脚本的。 1、我们在/etc/init.d/下新建一个文件tomcat&#xff08;需要在root权限下操作&#xff09; vi /etc/init.d/tomcat#!/bin/sh # chkconfig…

Android插件化开发之动态加载的类型

https://segmentfault.com/a/1190000005113493 基本信息 Author&#xff1a;kaedea GitHub&#xff1a;android-dynamical-loading 现在网络上有许多关于动态加载的介绍的文章&#xff0c;谈及的关键词汇有动态加载、插件化、热部署、热修复等&#xff0c;对于一些刚接触这方…

c# 爬虫 -ChromeDriver+HtmlAgilityPack爬取比赛实时比分

背景最近NBA总决赛吸引了不少球迷&#xff0c;但是因为时差的关系&#xff0c;人家在比赛&#xff0c;我们在上班&#xff0c;有时候上班又不好意思光明正大的看比赛&#xff0c;那有什么办法 可以光明正大的看又不被发现呢。有&#xff0c;自己动手丰衣足食&#xff0c;Chrome…

Codevs2157 配对

题目描述 Description给出2个序列A{a[1]&#xff0c;a[2]&#xff0c;…&#xff0c;a[n]}&#xff0c;B{b[1]&#xff0c;b[2]&#xff0c;…&#xff0c;b[n]}&#xff0c;从A、B中各选出n个元素进行一一配对&#xff08;可以不按照原来在序列中的顺序&#xff09;&#xff0…

UITableView的优化原理

2019独角兽企业重金招聘Python工程师标准>>> 当我们下啦一个 UITableView时&#xff0c;如果没有做优化&#xff0c;只是简单的实现功能代码如下&#xff0c;这样当我们有上百条tableviewcell的时候&#xff0c;我们滑动的非常快时会非常费内存&#xff0c;当然苹果…

深入浅出Mybatis系列(一)---Mybatis入门[转]

最近两年 springmvc mybatis 的在这种搭配还是蛮火的&#xff0c;楼主我呢&#xff0c;也从来没真正去接触过mybatis, 趁近日得闲&#xff0c; 就去学习一下mybatis吧。 本次拟根据自己的学习进度&#xff0c;做一次关于mybatis 的一系列教程&#xff0c; 记录自己的学习历程&…

字符串之括号的有效性

题目: 给定一个字符串str,判断是不是整体有效的括号字符串 举例: str = "()" return true; stre = "()()" return true; str = "())" return false; str = "()a()" return false; 代码: package com.chenyu.string.cn;public class…

python为text添加滚动条_在Tkinter中向一组小部件添加滚动条

概述您只能将滚动条与一些小部件关联起来&#xff0c;根小部件和Frame不是那组小部件的一部分。最常见的解决方案是创建一个画布小部件&#xff0c;并将滚动条与该小部件关联起来。然后&#xff0c;将包含标签小部件的框架嵌入到画布中。确定框架的宽度/高度&#xff0c;并将其…

C# 图像模板匹配并标注

01—需求这个是粉丝在我的技术群提的一个需求1、 模板匹配 &#xff1a;功能&#xff1a;&#xff08;1&#xff09;在一张大图像中&#xff0c;选取一小块区域作为模板&#xff08;2&#xff09;可在大图像中匹配到模板图像和位置。模板匹配是图像处理中最基本、最常用的匹配方…

深入浅出Mybatis系列(八)---mapper映射文件配置之select、resultMap[转]

上篇《深入浅出Mybatis系列&#xff08;七&#xff09;---mapper映射文件配置之insert、update、delete》介绍了insert、update、delete的用法&#xff0c;本篇将介绍select、resultMap的用法。select无疑是我们最常用&#xff0c;也是最复杂的&#xff0c;mybatis通过resultMa…

李洪强经典面试题146-网络

李洪强经典面试题146-网络 网络 http请求方式&#xff1f; 通常&#xff0c;HTTP的请求方式有3种&#xff0c;分别是&#xff1a;POST、GET、HEAD。POST和GET方法是用于数据发送的。 POST&#xff1a;它将要发送的数据单独放在一个流中进行发送&#xff0c;而不是附加在URL地址…

北大保送、硕博连读!《西游记》红孩儿扮演者现成中科院博士!

全世界只有3.14 % 的人关注了爆炸吧知识本文转自&#xff1a;募格学术86版《西游记》可以说是很多人的记忆&#xff0c;男女老幼几乎都看过这个版本&#xff0c;虽然已经过去三十多年&#xff0c;但如今依旧是经典无法超越之作。看过86版《西游记》的小伙伴应该都还记得里面牛魔…

Android插件化开发之运行未安装apk的activity

1、介绍 我们知道PathClassLoader是一个应用的默认加载器(而且他只能加载data/app/xxx.apk的文件)&#xff0c;但是我们加载插件一般使用DexClassLoader加载器&#xff0c;所以这里就有问题了&#xff0c;其实如果对于开始的时候&#xff0c;每个人都会认为很简单&#xff0c;…

理解UI线程——SWT, Android, 和Swing的UI机理

2019独角兽企业重金招聘Python工程师标准>>> 在做GUI的时候, 无论是SWT, AWT, Swing 还是Android, 都需要面对UI线程的问题, UI线程往往会被单独的提出来单独对待, 试着问自己, 当GUI启动的时候, 后台会运行几个线程? 比如 1. SWT 从Main函数启动 2. Swing 从Ma…

python自动填写小程序表单_新年好!教大家用Python写一个自动回复拜年信息的小程序!...

原标题&#xff1a;新年好&#xff01;教大家用Python写一个自动回复拜年信息的小程序&#xff01;过年期间&#xff0c;想必大家都收到很多拜年信息吧&#xff01;有没有也被拜年短信(大部分是群发)搞得很焦虑&#xff1f;不回复似乎显得很没有礼貌&#xff0c;一一回复又累心…

C#多线程开发-并发集合中的ConcurrentQueue

前言大家好&#xff0c;我是阿辉。上一篇博文简单介绍了C#中支持并发的数据字典&#xff0c;简单举例说明比较了常规集合与ConcurrentDictionary的读写速度。下来简单介绍其中一个线程安全队列ConcurrentQueue;ConcurrentQueue队列我们不陌生&#xff0c;在数据结构这门课中就有…