社区编辑申请
注册/登录
C#表达式中的动态查询
开发 后端
当您使用LINQ来处理数据库时,这种体验是一种神奇的体验,对吗?你把数据库实体像一个普通的收集,使用Linq中像Where,Select或者 Take,这些简单的使用就能让代码可用了。

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

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

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

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

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

  1. SQL:SELECT 
  2.     [Extent1].[StudentID] AS [StudentID], 
  3.     [Extent1].[StudentName] AS [StudentName], 
  4.     [Extent1].[DateOfBirth] AS [DateOfBirth], 
  5.     FROM [dbo].[Students] AS [Extent1] 
  6.     WHERE N'Billie' = [Extent1].[StudentName] 

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

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

在最后一个示例中,SQL查询使所有学生进入流程,并将其映射到常规集合。不同之处在于,在第一段代码中,lambda是一个Expression

第二段代码在性能,内存和网络方面很糟糕。我们从网络中获取了许多对象,而不是仅从数据库中获取一个项目。然后,我们使用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查询。

C#表达式树

在第一段代码中,类型s => s.StudentName == "Billie"为Expression

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

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

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

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

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

  1. IQueryable<Error> _errors; 
  2.  
  3. public IEnumerable<Error> GetErrors(string propertyToFilter, string value) 
  4.     switch (propertyToFilter) 
  5.     { 
  6.         case "Username"
  7.             return await _errors.Where(e=> e.Username == value).ToListAsync(); 
  8.         case "Country"
  9.             return await _errors.Where(e=> e.Country == value).ToListAsync(); 
  10.         case "Version"
  11.             return await _errors.Where(e=> e.Version == value).ToListAsync(); 
  12.         // ...         
  13.     } 

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

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

  1. private async static Task<IEnumerable<Error>> GetErrors(string propertyToFilter, string value) 
  2.     var error = Expression.Parameter(typeof(Error)); 
  3.     var memberAccess = Expression.PropertyOrField(error, propertyToFilter); 
  4.     var exprRight = Expression.Constant(value); 
  5.     var equalExpr = Expression.Equal(memberAccess, exprRight); 
  6.     Expression<Func<Error, bool>> lambda = Expression.Lambda<Func<Error, bool>>(equalExpr, error); 
  7.  
  8.     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/

责任编辑:武晓燕 来源: DotNET技术圈
相关推荐

2022-05-17 16:56:33

开发工具前端

2022-02-12 12:26:45

2022-05-12 23:38:19

SQL数据库字符串

2022-04-01 15:02:56

前端工具开发

2022-04-04 10:28:49

C#项目WPF

2022-02-21 07:04:44

C#表达式运算符

2022-03-30 08:40:00

JavaScript控制台

2022-03-09 09:56:27

插件开发效率

2021-12-06 07:23:12

2022-02-09 07:52:36

2009-08-17 13:56:28

C#正则表达式入门

2022-05-20 08:50:26

JMeter正则表达式

2009-08-07 15:16:10

C#正则表达式

2009-08-25 17:59:49

C#入门

2022-01-17 10:12:47

2009-08-07 15:41:39

C#正规表达式

2009-08-20 14:43:03

C#正则表达式Rege

2011-11-23 11:04:41

BGPAS_PATH正则表达式

2009-08-20 14:57:00

C#正则表达式

2009-08-20 15:10:33

C#正则表达式

同话题下的热门内容

Python 字符串总结,建议收藏!这份Java日志格式规范,拿走不谢!Mybatis-Plus官方发布分库分表神器,一个依赖轻松搞定!后端思维篇:如何应用设计模式优化代码改变 Python 对象规则的黑魔法 Metaclass几种限流算法的Go语言实现JMeter关联之正则表达式提取器在 Go 中实现一个支持并发的 TCP 服务端

编辑推荐

使用Kotlin做开发一个月后的感想面试官问你什么是消息队列?把这篇甩给他!五大自动化测试的Python框架图文详解两种算法:深度优先遍历(DFS)和广度优先遍历(BFS)2018年最流行的十大编程语言,其中包括你用的语言吗?
我收藏的内容
点赞
收藏

51CTO技术栈公众号