C#表达式树Expression动态创建表达式

作者:阿波Plus 时间:2023-03-26 06:44:39 

上一篇中说到了 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

标签:C#,Expression,动态,创建,表达式
0
投稿

猜你喜欢

  • C# WPF Image控件的绑定方法

    2023-09-03 06:41:42
  • 一篇文章带你入门Springboot沙箱环境支付宝支付(附源码)

    2021-06-26 23:21:16
  • C#采用HttpWebRequest实现保持会话上传文件到HTTP的方法

    2023-07-18 17:20:29
  • Android 获取屏幕的多种宽高信息的示例代码

    2021-07-10 06:07:10
  • unity 如何修改材质属性和更换shader

    2023-02-22 12:42:44
  • 客户端Socket与服务端ServerSocket串联实现网络通信

    2023-08-11 00:01:17
  • 使用C#开发OPC Server服务器源码解析

    2021-05-30 04:30:14
  • 详解android 用webview加载网页(https和http)

    2021-12-29 11:14:30
  • java导出生成word的简单方法

    2023-11-23 23:07:13
  • 解析C#中委托的同步调用与异步调用(实例详解)

    2022-12-24 19:06:47
  • C#委托初级使用的实例代码

    2022-12-01 09:59:44
  • Android Studio 新建项目通过git上传到码云图文教程详解

    2021-08-09 12:12:05
  • 利用java反射机制实现自动调用类的简单方法

    2023-11-29 15:57:22
  • Unity3D实现甜品消消乐游戏

    2022-11-06 16:26:24
  • Android View 事件防抖的两种方案

    2022-02-04 15:46:52
  • Spring之@Aspect中通知的5种方式详解

    2021-12-12 20:28:02
  • java 之JNA中的Memory和Pointer的使用方法

    2022-10-25 14:18:35
  • Android scheme 跳转的设计与实现详解

    2021-07-29 03:42:52
  • C# MVC 使用LayUI实现下拉框二级联动的功能

    2022-12-07 02:10:05
  • Java回调函数原理实例与代理模式的区别讲解

    2023-04-16 21:21:01
  • asp之家 软件编程 m.aspxhome.com