当前位置: 首页 > 科技观察

C#表达式中的动态查询

时间:2023-03-13 16:12:18 科技观察

当你使用LINQ与数据库打交道时,这种体验是一种神奇的体验吧?您将数据库实体视为普通集合,并在Linq中像Where、Select或Take一样使用它,这些简单的使用将使代码可用。但是让我们在这里考虑动态查询和表达式树是如何实现的:幕后发生了什么。您编写的LINQ查询被转换为SQL(或其他),并且该SQL查询被发送到数据库。然后将来自数据库的响应映射到C#对象。但是你如何转换成SQL呢?在本文中,您将了解实体框架和MongoDBC#驱动程序等框架如何使用表达式树进行转换。您将看到如何使用表达式树自己构建动态查询。这些是您无法在编译时创建的查询,因为您只能在运行时知道查询的外观。揭开可查询树和表达式树的神秘面纱考虑使用EntityFramework6的以下C#代码:SQL:SELECT[Extent1].[StudentID]AS[StudentID],[Extent1].[StudentName]AS[StudentName],[Extent1].[DateOfBirth]AS[DateOfBirth],FROM[dbo].[Students]AS[Extent1]WHEREN'Billie'=[Extent1].[StudentName]注意WHERESQL查询中有一个操作。这并不明显。如果SQL中不包含WHERE,则从数据库中取出所有学生,并在.NET进程中进行过滤。事实上,下面的代码可以解决这个问题://BAD:DbSetstudents=context.Students;Funcpredicate=s=>s.StudentName=="Billie";varx=students.Where(谓词).ToList();在最后一个示例中,SQL查询将所有学生带入流程并将他们映射到常规集合。不同之处在于,在第一段代码中,lambda是一个表达式,而第二段代码在性能、内存和网络方面都很糟糕。我们从网络中获取许多对象,而不是从数据库中获取一个项目。然后我们使用CPU将它们序列化为C#对象。并用完内存将它们存储在进程的堆中。那么让我们回到第一段代码。awaitstudents.Where(s=>s.StudentName=="Billie").ToListAsync()如何生成包含WHEREN'Billie'=[Extent1].[StudentName]的SQL查询?答案是表达式树。代码s=>s.StudentName=="Billie"实际上是一个结构化查询,可以通过编程将其分解为节点树。在此示例中,有6个节点。最顶层的节点是lambda表达式。左边是lambda参数。它的右边是相等表示表达式的lambda主体。EntityFramework具有遍历这些表达式树和构造SQL查询的算法。其他数据源提供程序(如MongoDBC#驱动程序)也会发生同样的事情,只是它构造了一个MongoDBjson查询。C#表达式树在第一段代码中,类型s=>s.StudentName=="Billie"是ExpressionOK,但我该如何利用它呢?在大多数情况下,使用表达式树的人都是在构建WorldEntityFramework的人。但在某些特定情况下它会变得非常有用。这是我们最近在Ozcode[1](我的日常工作)中遇到的一个用例:我们想在名为Error的数据库实体上创建动态服务器端过滤。该实体有许多我们希望允许用户过滤的属性。因此,应该允许基于用户名、国家/地区、版本或任何其他属性进行过滤。这是我们需要实现的API:IQueryable_errors;publicIEnumerableGetErrors(stringpropertyToFilter,stringvalue){/*..*/}在这种情况下,propertyToFilter是Error的属性。使用常规LINQ,唯一的方法是使用巨大的switch/case语句。有点像这样:IQueryable_errors;publicIEnumerableGetErrors(stringpropertyToFilter,stringvalue){switch(propertyToFilter){case"用户名":returnawait_errors.Where(e=>e.Username==value).ToListAsync();案例“国家”:returnawait_errors.Where(e=>e.Country==value).ToListAsync();case"Version":returnawait_errors.Where(e=>e.Version==value).ToListAsync();//...}}您可能同意这并不理想。除了必须编写所有这些东西之外,它也很容易出错。如果添加属性怎么办?如果改名了呢?整个事情一团糟。通过动态查询和表达式树实现该功能的方法如下:propertyToFilter);varexprRight=Expression.Constant(value);varequalExpr=Expression.Equal(memberAccess,exprRight);Expression>lambda=Expression.Lambda>(equalExpr,error);returnawait_errors.Where(lambda).ToListAsync();}这里的每一行代码代表表达式树中的一个节点。它们一起构成了最高节点——lambda。然后可以在LINQ中使用动态表达式并生成服务器端SQL查询。我觉得还好。解决此问题的另一种方法是构建自定义SQL查询字符串。在Ozcode中,我们使用的是MongoDB,因此SQL不适合使用,但我们可以创建自定义的MongoDBJSON查询字符串。这也不太难,但我认为表达式树方法更灵活、更可靠。一方面,您可以将它放在LINQ中,并与其他LINQ运算符结合使用。此外,当有经过测试的框架(如EntityFramework)可以为您完成查询时,为什么还要编写自己的查询。总结回顾。以下是本文的一些要点:常规函数/委托和表达式之间的区别在于表达式可以用结构化树表示。可以很容易地分析这棵树以创建诸如数据库查询之类的东西。支持表达式的数据源实现IQueryable接口。如果您不能使用表达式(以及使用常规方法或委托),查询将在服务器端而不是数据库端进行,这对性能来说会很糟糕。当使用lambda(没有主体)时,表达式是无缝创建的,所以这些年来您可能一直在这样做。您可以自己使用表达式树来创建动态查询。这在无法在编译时构建查询而只能在运行时构建查询的情况下很有用。参考文献[1]Ozcode:https://oz-code.com[2]:https://www.mediavine.com/