[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岁性别未知的会员 条件组内是并且关系,但 ...
随机推荐
- arthas 简单使用
简介 Arthas 是Alibaba开源的Java诊断工具,深受开发者喜爱. 官网地址:https://arthas.aliyun.com/ 它可以做什么,以下功能都是直接操作线上跑着的jar包!!! ...
- java数组中Arrays类
使用Arrays类之后要先导入包,即在开头添加这行: import.java.util.Arrays 1,排序:Arrays.sort(数组名) 排序后为数组升序. 2,将数组转换成字符串:Array ...
- 21-Add Two Numbers-Leetcode
You are given two linked lists representing two non-negative numbers. The digits are stored in rever ...
- 类成员函数调用delete this会发生什么呢?
有如下代码 class myClass { public: myClass(){}; ~myClass(){}; void foo() { delete this; } }; int main() { ...
- 进阶版的java面试
来自一名2019届应届毕业生总结的Java研发面试题汇总(2019秋招篇) 2018年Java研发工程师面试题 Java研发工程师面试题(Java基础) ...
- SonarQube的部分规则探讨
引言:为了更好的使项目代码规范化,减少Bug的出现,因此最近引入了SonarQube来帮助检测代码问题,这里就分享部分有趣的规则. 注:因为保密原则,文章贴出来的代码都是我按照格式仿写的,并非公司源码 ...
- Spring Boot 热启动插件
1. maven依赖 <dependency> <groupId>org.springframework.boot</groupId> <artifactId ...
- 数仓day03-----日志预处理
1. 为什么要构建一个地理位置维表(字典) 在埋点日志中,有用户的地理位置信息,但是原始数据形式是GPS坐标,而GPS坐标在后续(地理位置维度分析)的分析中不好使用.gps坐标的匹配,不应该做这种精确 ...
- CSS系列,清除浮动方法总结
在非IE浏览器(如Firefox)下,当容器的高度为auto,且容器的内容中有浮动(float为left或right)的元素.在这种情况下,容器的高度不能自动伸长以适应内容的高度,使得内容溢出到容器外 ...
- @Order注解使用
注解@Order或者接口Ordered的作用是定义Spring IOC容器中Bean的执行顺序的优先级,而不是定义Bean的加载顺序,Bean的加载顺序不受@Order或Ordered接口的影响: @ ...