[C# Expression] 之动态创建表达式
上一篇中说到了 Expression 的一些概念性东西,其实也是为了这一篇做知识准备。为了实现 EFCore 的多条件、连表查询,简化查询代码编写,也就有了这篇文章。
在一些管理后台中,对数据进行多条件查询是一件很普遍的事情,比如在用户列表需要实现可以对 "用户名"、"手机号"、"账户是否冻结" 等等一系列的条件查询,常见的处理方式就是通过一系列 if...else... 来对条件进行拼接。这会导致查询接口实现起来堆叠了一堆看起来有用但实际很繁琐的代码。所以根据前后端请求报文协商,我们就可以按照一定的格式来动态创建表达式树。
00、创建 QueryEntity 类
QueryEntity 是前端向 API 传递的参数列表,通过这个类,服务端可以知道前端需要查询哪个字段,使用什么方法(Equals、Contains)过滤。
/// <summary>
/// 查询实体
/// </summary>
public class QueryEntity
{
/// <summary>
/// 字段名称
/// </summary>
public string Key { get; set; }
/// <summary>
/// 值
/// </summary>
public string Value { get; set; }
/// <summary>
/// 操作方法,对应OperatorEnum枚举类
/// </summary>
public string Operator { get; set; }
/// <summary>
/// 逻辑运算符,只支持AND、OR
/// </summary>
public string LogicalOperator { get; set; }
}
01、创建 OperatorEnum 类
OperatorEnum 这是一个操作方法的枚举类,规定了 API 允许的查询方法,比如 Equals、Contains 等等。
/// <summary>
/// 操作方法枚举
/// </summary>
public enum OperatorEnum
{
/// <summary>
/// 等于
/// </summary>
Equals,
/// <summary>
/// 不等于
/// </summary>
NotEqual,
/// <summary>
/// 包含
/// </summary>
Contains,
/// <summary>
/// 由什么开始
/// </summary>
StartsWith,
/// <summary>
/// 由什么结束
/// </summary>
EndsWith,
/// <summary>
/// 大于
/// </summary>
Greater,
/// <summary>
/// 大于等于
/// </summary>
GreaterEqual,
/// <summary>
/// 小于
/// </summary>
Less,
/// <summary>
/// 小于等于
/// </summary>
LessEqual,
}
02、创建 ExpressionExtension 类
ExpressionExtension 类实现了表达式树的动态创建,将前端传入的多条件查询转换成表达式,用于 EFCore 的查询。
/// <summary>
/// 表达式扩展
/// </summary>
/// <typeparam name="T">泛型</typeparam>
public static class ExpressionExtension<T> where T : class, new()
{
/// <summary>
/// 表达式动态拼接
/// </summary>
public static Expression<Func<T, bool>> ExpressionSplice(List<QueryEntity> entities)
{
if (entities.Count < 1)
{
return ex => true;
}
var expression_first = CreateExpressionDelegate(entities[0]);
foreach (var entity in entities.Skip(1))
{
var expression = CreateExpressionDelegate(entity);
InvocationExpression invocation = Expression.Invoke(expression_first, expression.Parameters.Cast<Expression>());
BinaryExpression binary;
// 逻辑运算符判断
if (entity.LogicalOperator.ToUpper().Equals("OR"))
{
binary = Expression.Or(expression.Body, invocation);
}
else
{
binary = Expression.And(expression.Body, invocation);
}
expression_first = Expression.Lambda<Func<T, bool>>(binary, expression.Parameters);
}
return expression_first;
}
/// <summary>
/// 创建 Expression<TDelegate>
/// </summary>
private static Expression<Func<T, bool>> CreateExpressionDelegate(QueryEntity entity)
{
ParameterExpression param = Expression.Parameter(typeof(T));
Expression key = param;
var entityKey = entity.Key.Trim();
// 包含'.',说明是父表的字段
if (entityKey.Contains('.'))
{
var tableNameAndField = entityKey.Split('.');
key = Expression.Property(key, tableNameAndField[0].ToString());
key = Expression.Property(key, tableNameAndField[1].ToString());
}
else
{
key = Expression.Property(key, entityKey);
}
Expression value = Expression.Constant(ParseType(entity));
Expression body = CreateExpression(key, value, entity.Operator);
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return lambda;
}
/// <summary>
/// 属性类型转换
/// </summary>
/// <param name="entity">查询实体</param>
/// <returns></returns>
private static object ParseType(QueryEntity entity)
{
try
{
PropertyInfo property;
// 包含'.',说明是子类的字段
if (entity.Key.Contains('.'))
{
var tableNameAndField = entity.Key.Split('.');
property = typeof(T).GetProperty(tableNameAndField[0], BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
property = property.PropertyType.GetProperty(tableNameAndField[1], BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
}
else
{
property = typeof(T).GetProperty(entity.Key, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
}
return Convert.ChangeType(entity.Value, property.PropertyType);
}
catch (Exception)
{
throw new ArgumentException("字段类型转换失败:字段名错误或值类型不正确");
}
}
/// <summary>
/// 创建 Expression
/// </summary>
private static Expression CreateExpression(Expression left, Expression value, string entityOperator)
{
if (!Enum.TryParse(entityOperator, true, out OperatorEnum operatorEnum))
{
throw new ArgumentException("操作方法不存在,请检查operator的值");
}
return operatorEnum switch
{
OperatorEnum.Equals => Expression.Equal(left, Expression.Convert(value, left.Type)),
OperatorEnum.NotEqual => Expression.NotEqual(left, Expression.Convert(value, left.Type)),
OperatorEnum.Contains => Expression.Call(left, typeof(string).GetMethod("Contains", new Type[] { typeof(string) }), value),
OperatorEnum.StartsWith => Expression.Call(left, typeof(string).GetMethod("StartsWith", new Type[] { typeof(string) }), value),
OperatorEnum.EndsWith => Expression.Call(left, typeof(string).GetMethod("EndsWith", new Type[] { typeof(string) }), value),
OperatorEnum.Greater => Expression.GreaterThan(left, Expression.Convert(value, left.Type)),
OperatorEnum.GreaterEqual => Expression.GreaterThanOrEqual(left, Expression.Convert(value, left.Type)),
OperatorEnum.Less => Expression.LessThan(left, Expression.Convert(value, left.Type)),
OperatorEnum.LessEqual => Expression.LessThanOrEqual(left, Expression.Convert(value, left.Type)),
_ => Expression.Equal(left, Expression.Convert(value, left.Type)),
};
}
}
03、使用示例
例如有以下两个实体类,Address 是 User 的子类
public class User
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
public DateTime CreateTime { get; set; }
public Address Address { get; set; }
}
public class Address
{
public string Province { get; set; }
public string City { get; set; }
}
单条件查询
查询用户表中名称(name) 包含 "chen" :
List<QueryEntity> list = new List<QueryEntity>
{
new QueryEntity
{
Key = "name",
Value = "chen",
Operator = "Contains"
}
};
var expression = QueryExtension<User>.ExpressionSplice(list);
// expression = Param_0 => Param_0.Name.Contains("chen")
查询用户表中年龄(age) 大于等于 18:
List<QueryEntity> list = new List<QueryEntity>
{
new QueryEntity
{
Key = "age",
Value = "18",
Operator = "GreaterEqual"
}
};
var expression = QueryExtension<User>.ExpressionSplice(list);
// expression = Param_0 => Param_0.Name.GreaterThanOrEqual(18)
多条件查询
查询用户表中名称(name) 包含 "chen" 并且年龄(age) 大于等于 18:
List<QueryEntity> list = new List<QueryEntity>
{
new QueryEntity
{
Key = "name",
Value = "chen",
Operator = "Contains"
},
new QueryEntity
{
Key = "age",
Value = "18",
Operator = "GreaterEqual",
// 注意:这里得填入 "AND",代表两个条件是并且的关系,如果需要查询名称包含 "chen" 或者 年龄大于等于18,则填入 "OR"
"logicalOperator": "AND"
}
};
var expression = QueryExtension<User>.ExpressionSplice(list);
// expression = Param_0 => ((Param_0.Status >= Convert(1, Int32)) And Invoke(Param_1 => Param_1.OpenId.Contains("9JJdFTVt6oimCgdbW61sk"), Param_0))
多表查询
查询用户表中名称(name) 包含 "chen" 并且 地址(address)在广东省:
List<QueryEntity> list = new List<QueryEntity>
{
new QueryEntity
{
Key = "name",
Value = "chen",
Operator = "Contains"
},
new QueryEntity
{
Key = "address.Province",
Value = "广东省",
Operator = "Equals",
// 注意:这里得填入 "AND",代表两个条件是并且的关系,如果需要查询名称包含 "chen" 或者 年龄大于等于18,则填入 "OR"
"logicalOperator": "AND"
}
};
var expression = QueryExtension<BookingRecord>.ExpressionSplice(list);
// expression = {Param_0 => ((Param_0.Address.Province == Convert("广东省", String)) And Invoke(Param_1 => Param_1.Name.Contains("chen"), Param_0))}
[C# Expression] 之动态创建表达式的更多相关文章
- EF4.0、4.3创建表达式树状动态查询总结
---------------------------------------------快速适用 效果: where name like '%王%' and Age>=35 or Age< ...
- 动态创建Lambda表达式实现高级查询
需求简介 最近这几天做的东西总算是回归咱的老本行了,给投资管理项目做一个台账的东西,就是类似我们的报表.其 中有一个功能是一个高级查询的需求,在查询条件方面大概有7.8个查询条件.需求就是如果一个条件 ...
- C# 动态构建表达式树(二)——构建 Select 和 GroupBy 的表达式
C# 动态构建表达式树(二)--构建 Select 和 GroupBy 的表达式 前言 在上篇中写了表达式的基本使用,为 Where 方法动态构建了表达式.在这篇中会写如何为 Select 和 Gro ...
- 使用Expression动态创建lambda表达式
using System;using System.Linq.Expressions;using System.Reflection; namespace Helper{ public class L ...
- 动态创建 Lambda 表达式
首先我们看一个简单 Lambda 表达式的构成. i => i > 5 在这个表达式中,"i" 被称为 Parameter,"i > 5" 是 ...
- 【C#表达式树 开篇】 Expression Tree - 动态语言
.NET 3.5中新增的表达式树(Expression Tree)特性,第一次在.NET平台中引入了"逻辑即数据"的概念.也就是说,我们可以在代码里使用高级语言的形式编写一段逻辑, ...
- 泛型方法动态生成表达式树 Expression
public string GetGridJSON(TraderInfo model) { IQueryable<TraderInfo> Temp = db.TraderInfo; if ...
- C#动态创建lambda表达式
/// <summary> /// 创建lambda表达式:p=>true /// </summary> /// <typeparam name="T&q ...
- 动态拼接表达式——Expression
我们在项目中会遇到以下查询需求吗? 比如需要查询出满足以下条件的会员: 条件组一:30-40岁的男性会员 条件组二:20-30岁的女性会员 条件组三:60-80岁性别未知的会员 条件组内是并且关系,但 ...
随机推荐
- 洛谷 P3287 - [SCOI2014]方伯伯的玉米田(BIT 优化 DP)
洛谷题面传送门 怎么题解区全是 2log 的做法/jk,这里提供一种 1log 并且代码更短(bushi)的做法. 首先考虑对于一个序列 \(a\) 怎样计算将其变成单调不降的最小代价.对于这类涉及区 ...
- Codeforces 1149C - Tree Generator™(线段树+转化+标记维护)
Codeforces 题目传送门 & 洛谷题目传送门 首先考虑这个所谓的"括号树"与直径的本质是什么.考虑括号树上两点 \(x,y\),我们不妨用一个"DFS&q ...
- 决策单调性&wqs二分
其实是一个还算 trivial 的知识点吧--早在 2019 年我就接触过了,然鹅当时由于没认真学并没有把自己学懂,故今复学之( 1. 决策单调性 引入:在求解 DP 问题的过程中我们常常遇到这样的问 ...
- windows和linux文本的编码格式不一样所出的错
windows下编写的python脚本上传的linux下执行会出现错误: usr/bin/python^M: bad interpreter: No such file or directory 原因 ...
- BeautifulSoup解析库的介绍和使用
### BeautifulSoup解析库的介绍和使用 ### 三大选择器:节点选择器,方法选择器,CSS选择器 ### 使用建议:方法选择器 > CSS选择器 > 节点选择器 ## 测试文 ...
- Python基础之基本运算符
目录 1. 算数运算符 2. 比较运算符 3. 赋值运算符 4. 逻辑运算符 5. 身份运算 6. 运算符优先级 1. 算数运算符 常用算术运算符使用方法如下: x = 5 y = 2 a = x + ...
- WINDOWS中使用svn
官网:https://tortoisesvn.net/index.zh.html (SVN安装包) 然后下载对应的64位安装包(语言包) 安装完后运行 可以存到D盘,新建一个文件夹存放 右键桌面会多 ...
- 04 Windows安装python运行环境
安装python运行环境 使用微信扫码关注微信公众号,并回复:"Python工具包",免费获取下载链接! 1.卸载程序(电脑未装此程序,跳过此过程) 卸载这两个程序 出现下图所示, ...
- URLDNS分析
学习了很久的Java基础,也看了很多的Java反序列化分析,现在也来分析学习哈最基础的URLDNS反序列化吧. Java反序列化基础 为了方便数据的存储,于是乎有了现在的Java序列化于反序列化.序列 ...
- 判断是否有重复,判断字符串是否有重复汉字【c#】
string corn = "公司"; int n = 0; if (tbCorporateName.Text.IndexOf(corn) > -1) { string co ...