C#表达式树Expression动态创建表达式
作者:阿波Plus 发布时间:2023-03-26 06:44:39
标签:C#,Expression,动态,创建,表达式
上一篇中说到了 Expression 的一些概念性东西,其实也是为了这一篇做知识准备。为了实现 EFCore 的多条件、连表查询,简化查询代码编写,也就有了这篇文章。
在一些管理后台中,对数据进行多条件查询是一件很普遍的事情,比如在用户列表需要实现可以对 "用户名"、"手机号"、"账户是否冻结" 等等一系列的条件查询,常见的处理方式就是通过一系列 if...else... 来对条件进行拼接。这会导致查询接口实现起来堆叠了一堆看起来有用但实际很繁琐的代码。所以根据前后端请求报文协商,我们就可以按照一定的格式来动态创建表达式树。
创建 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; }
}
创建 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,
}
创建 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)),
};
}
}
使用示例
例如有以下两个实体类,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 = ExpressionExtension<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 = ExpressionExtension<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 = ExpressionExtension<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 = ExpressionExtension<BookingRecord>.ExpressionSplice(list);
// expression = {Param_0 => ((Param_0.Address.Province == Convert("广东省", String)) And Invoke(Param_1 => Param_1.Name.Contains("chen"), Param_0))}
来源:https://www.cnblogs.com/chenyanbo1024/p/15724055.html


猜你喜欢
- break和continue的说明break 循环结构,一旦执行,就结束(或跳出)当前循环结构,此关键字的后面,不能
- ExpandoObject:表示一个对象,该对象包含可在运行时动态添加和移除的成员。 dynamic dynEO = new Expando
- try &
- 由于maven 使用上手很容易所以很多时候可以囫囵吞枣能够使用就可以了,由于作者最近在做的持续集成的代码扫描的时候,发现私有云里面大型工程m
- 因为一直用spring整合了mybatis,所以很少用到mybatis的session缓存。 习惯是本地缓存自己用map写或者引入第三方的本
- 前言老师要求我们学生做一套拍照身份验证系统,经过长时间的学习,有了这篇文章,希望能帮到读者们。正文首先介绍本文的主角:AForge创建一个C
- 本文实例讲述了C#操作session的类。分享给大家供大家参考。具体分析如下:这个C#类对session操作进行了再次封装,可以大大简化se
- 一、引言想实现一个空白的画板,上面可以画出手滑动的轨迹,就这么一个小需求。一般就来讲就两种实现方式,view或者surfaceview。下面
- AndroidStudio终于出3.0正式版了,内置了kotlin(虽然我安了插件一直能用)。一直忍着没敢下rc版的好奇猫,总算装了正式版。
- 数据传输在Android开发过程中,我们常常通过Intent在各个组件之间传递数据。例如在使用startActivity(android.c
- 我之前写了一篇关于美团网,大众点评的购买框效果的文章Android对ScrollView滚动监听,实现美团、大众点评的购买悬浮效果,我自己感
- 一、分析这篇将会讲解撤销反撤销功能的实现,先讨论一下这个原理是怎么样实现的。每次撤回的内容,内容是怎么定义呢? 其实就是每一笔,每一笔作为撤
- 背景在接口请求过程中,传递json对象,springboot转换为实体VO对象后,所有属性都为null。post请求:后台接收请求:当时就懵
- Java定义Long数据类型Long lg=10L;只需要在定义的的整型后面加个L;就和定义float数据类型一样Float ft=5.20
- 本文实例为大家分享了C#生成唯一订单号的具体代码,供大家参考,具体内容如下根据GUID+DateTime.Now.Ticks生产唯一订单号/
- 队列是其元素按照先进先出(FIFO)的方式来处理的集合。队列使用System.Collections.Generic名称空间中的泛型类Que
- 昨天下午快下班的时候,无意中听到公司两位同事在探讨批量向数据库插入数据的性能优化问题,顿时来了兴趣,把自己的想法向两位同事说了一下,于是有了
- 程序如下: public static string 英汉(string english,翻译结果 一个或多个) { string 英汉辞典
- 介绍使用mybatis时可以使用二级缓存提高查询速度,进而改善用户体验。使用redis做mybatis的二级缓存可是内存可控<如将单独
- 生成唯一值的方法很多,下面就不同环境下生成的唯一标识方法一一介绍,作为工作中的一次总结,有兴趣的可以自行测试:一、在 .NET 中生成1、直