最近工作中遇到一个这样的需求:在某个列表查询功能中,可以选择某个数字列(如商品单价、当天销售额、当月销售额等),再选择 小于或等于大于或等于 ,再填写一个待比较的数值,对数据进行查询过滤。

如果只有一两个这样的数字列,那么使用 Entity Framework Core 可以这么写 LINQ 查询:

public Task<List<Product>> GetProductsAsync(string propertyToFilter, MathOperator mathOperator, decimal value)
{
var query = _context.Products.AsNoTracking(); query = propertyToFilter switch
{
"Amount1" when mathOperator == MathOperator.LessThanOrEqual => query.Where(x => x.Amount1 <= value),
"Amount1" when mathOperator == MathOperator.GreaterThanOrEqual => query.Where(x => x.Amount1 >= value), "Amount2" when mathOperator == MathOperator.LessThanOrEqual => query.Where(x => x.Amount2 <= value),
"Amount2" when mathOperator == MathOperator.GreaterThanOrEqual => query.Where(x => x.Amount2 >= value), _ => throw new ArgumentException($"不支持 {propertyToFilter} 列作为数字列查询", nameof(propertyToFilter))
}; return query.ToListAsync();
}

如果固定只有一两个数字列且将来也不会再扩展,这样写简单粗暴,也没什么问题。

但如果有几十个数字列,这样使用 swith 模式匹配的写法就太恐怖了,代码大量重复。很自然地,我们得想办法根据属性名动态创建 Where 方法的参数。它的参数类型是:Expression<Func<T, bool>>,是一个表达式参数。

要知道如何动态创建一个类似 Expression<Func<T, bool>> 类型的表达式实例,就要知道如何拆解表达式树。

对于本示例,以 x => x.Amount1 <= value 表达式实例为例,它的表达式树是这样的:

然后我们可以按照此表达式树结构来构建我们的 LINQ 表达式:

public Task<List<Product>> GetProductsAsyncV2(string propertyToFilter, MathOperator mathOperator, decimal value)
{
var query = _context.Products.AsNoTracking(); var paramExp = Expression.Parameter(typeof(Product));
var memberExp = Expression.PropertyOrField(paramExp, propertyToFilter);
var valueExp = Expression.Constant(value);
var compareExp = mathOperator == MathOperator.LessThanOrEqual ?
Expression.LessThanOrEqual(memberExp, valueExp) :
Expression.GreaterThanOrEqual(memberExp, valueExp);
var lambda = Expression.Lambda<Func<Product, bool>>(compareExp, paramExp); return query.Where(lambda).ToListAsync();
}

每个 Expression.XXX 静态方法返回的都是一个以 Expression 为基类的实例,代表一个表达式。不同的表达式又可以组成一个新的表达式,直到得到我们需要的 Lambda 表达式。这样就形成了一种树形结构,我们称为表达式树。知道如何把一个最终的查询表达式拆解成表达式树,我们就容易动态构建此查询表达式。

得到一个表达式后,我们还可以动态编译并调用该表达式,比如上面示例得到的 lambda 变量,是一个Expression<Func<Product, bool>> 类型,调用其 Compile 方法,可以得到 Func<Product, bool> 类型的委托。

...

var toTestProduct = new Product { Amount1 = 100, Amount2 = 200 };

Func<Product, bool> func = lambda.Compile();
var result = func(toTestProduct); Console.WriteLine($"The product's {propertyToFilter} is to {mathOperator} {value}."); // Output: The product's Amount1 is LessThanOrEqual to 150.

你可以通过研究 Expression 类来了解更多动态构建表达式的方法。

动态构建 LINQ 表达式对于不能在编译时建立查询,只能在运行时建立查询的场景很有用。但它的缺点也很明显,不易维护、不易阅读、不易调试。如果最终的表达式执行出错,很难通过调试来发现具体是构建中的那一步写错了,只能凭自己的理解和经验查找错误。所以,如非必须,一般不推荐动态构建 LINQ 查询表达式。

[C#.NET 拾遗补漏]13:动态构建LINQ查询表达式的更多相关文章

  1. 通过动态构建Expression Select表达式并创建动态类型来控制Property可见性

    通过动态构建Expression Select表达式并创建动态类型来控制Property可见性 项目中经常遇到的一个场景,根据当前登录用户权限,仅返回权限内可见的内容.参考了很多开源框架,更多的是在V ...

  2. LINQ查询表达式---------where子句

    LINQ查询表达式---------where子句 where 子句用在查询表达式中,用于指定将在查询表达式中返回数据源中的哪些元素. 它将一个布尔条件(“谓词”)应用于每个源元素(由范围变量引用), ...

  3. Linq专题之创建Linq查询表达式

    本节我们主要介绍一下如何创建查询集合类型,关系数据库类型,DataSet对象类型和XML类型的数据源的Linq查询表达式. 下面在实例代码ReadyCollectionData()函数创建了准备的数据 ...

  4. 2.3 LINQ查询表达式中 使用select子句 指定目标数据

    本篇讲解LINQ查询的三种形式: 查询对象 自定义查询对象某个属性 查询匿名类型结果 [1.查询结果返回集合元素] 在LINQ查询中,select子句和from子句都是必备子句.LINQ查询表达式必须 ...

  5. LINQ查询表达式---------let子句

    LINQ查询表达式---------let子句 let子句创建一个范围变量来存储结果,变量被创建后,不能修改或把其他表达式的结果重新赋值给它.此范围变量可以再后续的LINQ子句中使用. class P ...

  6. LINQ查询表达式---------join子句

    LINQ查询表达式---------join子句 join 子句接受两个源序列作为输入. 每个序列中的元素都必须是可以与另一个序列中的相应属性进行比较的属性,或者包含一个这样的属性. join子句使用 ...

  7. LINQ查询表达式---------orderby子句

    LINQ查询表达式---------orderby子句 LINQ可以按元素的一个或多个属性对元素进行排序. class Program { public class PerInfo { public ...

  8. LINQ查询表达式---------into

    LINQ查询表达式---------into into 上下文关键字创建一个临时标识符,以便将 group.join 或 select 子句的结果存储到新的标识符 class Program { pu ...

  9. LINQ查询表达式---------group子句

    LINQ查询表达式---------group子句 LINQ表达式必须以from子句开头,以select或group子句结束.使用guoup子句来返回元素分组后的结果.group 子句返回一个 IGr ...

随机推荐

  1. [String] intern()方法

    intern()方法设计的初衷,就是重用String对象,以节省内存消耗. JDK1.6以及以前版本中,常量池是放在 Perm 区(属于方法区)中的,熟悉JVM的话应该知道这是和堆区完全分开的. 使用 ...

  2. 10 张图打开 CPU 缓存一致性的大门

    前言 直接上,不多 BB 了. 正文 CPU Cache 的数据写入 随着时间的推移,CPU 和内存的访问性能相差越来越大,于是就在 CPU 内部嵌入了 CPU Cache(高速缓存),CPU Cac ...

  3. DOM4J API

    1.DOM4J简介 DOM4J是 dom4j.org 出品的一个开源 XML 解析包.DOM4J应用于 Java 平台,采用了 Java 集合框架并完全支持 DOM,SAX 和 JAXP. DOM4J ...

  4. freopen ()函数

    1.格式 FILE * freopen ( const char * filename, const char * mode, FILE * stream ); 2.参数说明 filename: 要打 ...

  5. DFA简介

    DFA(Detrend Fluctuation Analysis)与scale-free scale-free的本质特征是self-affine or self-similar.具体的,体现在几何上, ...

  6. 【洛谷】P1009 阶乘之和——高精度算法

    题目描述 用高精度计算出S = 1! + 2! + 3! + - + n!  ( n ≤  50 ) S = 1! + 2! + 3! + - + n! ( n ≤ 50 ) 其中"!&qu ...

  7. R语言factor类型转numeric

    R 语言中为了进行数据分析,比如回归分析,这时候对于数据表格中的factor类型的数据会带来弊端,比如对因子的每一个数据都进行一次回归,这样就显得很复杂,且违背了我们的初衷,需要把factor转换为n ...

  8. 8、Python语法之流程控制

    一 引子 流程控制即控制流程,具体指控制程序的执行流程,而程序的执行流程分为三种结构:顺序结构(之前我们写的代码都是顺序结构).分支结构(用到if判断).循环结构(用到while与for) 二 分支结 ...

  9. 7 apache和nginx的区别

    7 apache和nginx的区别 nginx 相对 apache 的优点: 轻量级,同样起web 服务,比apache 占用更少的内存及资源 抗并发,nginx 处理请求是异步非阻塞的,支持更多的并 ...

  10. 知识全聚集 .Net Core 技术突破 | 我用C#手把手教你玩微信自动化一

    知识全聚集 .Net Core 技术突破 | 我用C#手把手教你玩微信自动化一 教程 01 | 模块化方案一 02 | 模块化方案二 03 | 简单说说工作单元 其他教程预览 分库分表项目实战教程 G ...