[C#.NET 拾遗补漏]13:动态构建LINQ查询表达式
最近工作中遇到一个这样的需求:在某个列表查询功能中,可以选择某个数字列(如商品单价、当天销售额、当月销售额等),再选择 小于或等于
和 大于或等于
,再填写一个待比较的数值,对数据进行查询过滤。
如果只有一两个这样的数字列,那么使用 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查询表达式的更多相关文章
- 通过动态构建Expression Select表达式并创建动态类型来控制Property可见性
通过动态构建Expression Select表达式并创建动态类型来控制Property可见性 项目中经常遇到的一个场景,根据当前登录用户权限,仅返回权限内可见的内容.参考了很多开源框架,更多的是在V ...
- LINQ查询表达式---------where子句
LINQ查询表达式---------where子句 where 子句用在查询表达式中,用于指定将在查询表达式中返回数据源中的哪些元素. 它将一个布尔条件(“谓词”)应用于每个源元素(由范围变量引用), ...
- Linq专题之创建Linq查询表达式
本节我们主要介绍一下如何创建查询集合类型,关系数据库类型,DataSet对象类型和XML类型的数据源的Linq查询表达式. 下面在实例代码ReadyCollectionData()函数创建了准备的数据 ...
- 2.3 LINQ查询表达式中 使用select子句 指定目标数据
本篇讲解LINQ查询的三种形式: 查询对象 自定义查询对象某个属性 查询匿名类型结果 [1.查询结果返回集合元素] 在LINQ查询中,select子句和from子句都是必备子句.LINQ查询表达式必须 ...
- LINQ查询表达式---------let子句
LINQ查询表达式---------let子句 let子句创建一个范围变量来存储结果,变量被创建后,不能修改或把其他表达式的结果重新赋值给它.此范围变量可以再后续的LINQ子句中使用. class P ...
- LINQ查询表达式---------join子句
LINQ查询表达式---------join子句 join 子句接受两个源序列作为输入. 每个序列中的元素都必须是可以与另一个序列中的相应属性进行比较的属性,或者包含一个这样的属性. join子句使用 ...
- LINQ查询表达式---------orderby子句
LINQ查询表达式---------orderby子句 LINQ可以按元素的一个或多个属性对元素进行排序. class Program { public class PerInfo { public ...
- LINQ查询表达式---------into
LINQ查询表达式---------into into 上下文关键字创建一个临时标识符,以便将 group.join 或 select 子句的结果存储到新的标识符 class Program { pu ...
- LINQ查询表达式---------group子句
LINQ查询表达式---------group子句 LINQ表达式必须以from子句开头,以select或group子句结束.使用guoup子句来返回元素分组后的结果.group 子句返回一个 IGr ...
随机推荐
- ScheduledExecutor定时器
为了弥补Timer 的上述缺陷,在Java 5的时候推出了基于线程池设计的 ScheduledExecutor.其设计思想是:每一个被调度的任务都会由线程池中一个线程去执行,因此任务是并发执行的,相互 ...
- ES6字符串随笔
ES6对JavaScript的变动的很大,增加了一些新特性. 字符串模板: 字符串模块使用``来表示,相比原来使用双引号来标记,多了许多便利性 1 // 原始的标记使用+来连接,遇到一些复杂的字符串会 ...
- Map 和 Json 互转
Json 转 Map<String,String> String query = "{tableName:\"iov_biz_car_info\",selec ...
- 【转】Setting up SDL 2 on MinGW
FROM: http://lazyfoo.net/tutorials/SDL/01_hello_SDL/windows/mingw/index.php Setting up SDL 2 on MinG ...
- day80:luffy:短信sdk接入&点击获取验证码&注册功能的实现&Celery实现短信发送功能
目录 1.短信sdk接入 2.前端点击获取验证码效果 3.注册后端接口实现 4.注册-前端 5.Celery 6.Celery完成短信发送功能 1.短信sdk接入 1.准备工作 1.下载云通讯相关的文 ...
- python面向对象(类与对象)
面向对象思想 关注公众号"轻松学编程"了解更多. 1.面向对象的设计思想 面向对象是基于万物皆对象这个哲学观点. 2.面向对象和面向过程的区别 面向过程 在生活中: 它是一种看待问 ...
- jquery播放图片
* { margin:0; padding:0; word-break:break-all; } body { background:#FFF; color:#333; font:12px/1.5em ...
- vgg学习
LeNet-5是一个较简单的卷积神经网络.下图显示了其结构:输入的二维图像,先经过两次卷积层到池化层,再经过全连接层,最后使用softmax分类作为输出层. AlexNet中包含了几个比较新的技术点, ...
- IAuthorizationFilter学习笔记(权限控制)以及非全局的filter
第一步:新建类CheckLoginFilter实现接口IAuthorizationFilter.请注意接口位于命名空间using System.Web.Mvc; public void OnAutho ...
- Oracle(第二天)
一.外键(foreign key):constraint , refenerces 例如:sno number(7) constraint fk_sno references student(sno) ...