[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岁性别未知的会员 条件组内是并且关系,但 ...
随机推荐
- linux命令-压缩数据
linux文件压缩工具:bzip2 文件扩展名 .bz2 compress 文件扩展名 .Z linux上很少看到了 uncompress解压 gzip 文件扩展名,.gz,gzip压缩文件,gzca ...
- ASP .Net Core 在 CentOS8 ARM 下连接 SQL Server 2008 R2(Hypervisor)
本文主要记录在 ARM 系统下无法连接SQL Server 2008 R2 的解决过程. 解决方案是使用 ODBC 的方式连接数据库,进行操作. 手上有公司的华为鲲鹏云计算 ARM 架构的 CentO ...
- Codeforces 1464F - My Beautiful Madness(树的直径)
Codeforces 题面传送门 & 洛谷题面传送门 树上数据结构大杂烩(?) 首先考虑什么样的点能够在所有路径的 \(d\) 邻居的交集内.显然如果一个点在一条路径的 \(d\) 邻居内则必 ...
- 洛谷 P3643 - [APIO2016]划艇(dp)
题面传送门 一道难度中等的 \(dp\)(虽然我没有想出来/kk). 首先一眼 \(dp_{i,j}\) 表示考虑到第 \(i\) 个学校,第 \(i\) 个学校派出了 \(j\) 个划艇的方案数,转 ...
- Latex 文档格式化
title: "Latex 文档格式化" author: 李龙翔 date: "Nov 22, 2019" subject: "Markdown&qu ...
- Oracle、MySQL关机操作步骤
一.Oracle数据库单机关机(eg:LEAP系统) 先关闭使用数据库的应用系统,再关闭数据库 关闭数据库执行以下命令 1.关闭Oracle数据库监听器:(使用操作系统下管理Oracle的账户,关闭监 ...
- MySQL_集群
管理节点:192.168.31.66 sql节点1+data1节点:192.168.31.42 sql节点2+data2节点:192.168.31.128 llll
- linux 实用指令时间日期类
linux 使用指令时间日期类 data 显示当前日期 基本语法 date 显示当前时间 date+%Y 显示当前年份 date+%m 显示当前月份 date+%d 显示当前是哪一天 date &qu ...
- KMP算法思路
题目 给定一个字符串\(S\),求\(M\)字符串是否是\(S\)字符串中的子串.如果是,返回\(M\)对应\(S\)的第一个下标,否则返回-1. 例如:S串为a b c d a b c d a b ...
- Java、Scala获取Class实例
Java获取Class实例的四种方式 package com.test; /** * @description: TODO * @author: HaoWu * @create: 2020/7/22 ...