上一篇中说到了 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] 之动态创建表达式的更多相关文章

  1. EF4.0、4.3创建表达式树状动态查询总结

    ---------------------------------------------快速适用 效果: where name like '%王%' and Age>=35 or Age< ...

  2. 动态创建Lambda表达式实现高级查询

    需求简介 最近这几天做的东西总算是回归咱的老本行了,给投资管理项目做一个台账的东西,就是类似我们的报表.其 中有一个功能是一个高级查询的需求,在查询条件方面大概有7.8个查询条件.需求就是如果一个条件 ...

  3. C# 动态构建表达式树(二)——构建 Select 和 GroupBy 的表达式

    C# 动态构建表达式树(二)--构建 Select 和 GroupBy 的表达式 前言 在上篇中写了表达式的基本使用,为 Where 方法动态构建了表达式.在这篇中会写如何为 Select 和 Gro ...

  4. 使用Expression动态创建lambda表达式

    using System;using System.Linq.Expressions;using System.Reflection; namespace Helper{ public class L ...

  5. 动态创建 Lambda 表达式

    首先我们看一个简单 Lambda 表达式的构成. i => i > 5 在这个表达式中,"i" 被称为 Parameter,"i > 5" 是 ...

  6. 【C#表达式树 开篇】 Expression Tree - 动态语言

    .NET 3.5中新增的表达式树(Expression Tree)特性,第一次在.NET平台中引入了"逻辑即数据"的概念.也就是说,我们可以在代码里使用高级语言的形式编写一段逻辑, ...

  7. 泛型方法动态生成表达式树 Expression

    public string GetGridJSON(TraderInfo model) { IQueryable<TraderInfo> Temp = db.TraderInfo; if ...

  8. C#动态创建lambda表达式

    /// <summary> /// 创建lambda表达式:p=>true /// </summary> /// <typeparam name="T&q ...

  9. 动态拼接表达式——Expression

    我们在项目中会遇到以下查询需求吗? 比如需要查询出满足以下条件的会员: 条件组一:30-40岁的男性会员 条件组二:20-30岁的女性会员 条件组三:60-80岁性别未知的会员 条件组内是并且关系,但 ...

随机推荐

  1. AtCoder Beginner Contest 188题解

    A 题意 问\(x,y\)相差是否小于\(3\) #include<iostream> #include<cstdio> #include<cmath> #defi ...

  2. 【2020五校联考NOIP #6】最佳观影

    题意: 给出一个 \(k \times k\) 的网格和 \(n\) 次操作.其中 \(k\) 为奇数. 每次操作给出一个数 \(m\).每次你要找出一个三元组 \((x,l,r)\) 使得: \(r ...

  3. 【豆科基因组】鹰嘴豆Chickpea (Cicer arietinum L.)429个自然群体重测序2019NG

    目录 一.来源 二.结果 材料测序.变异检测.群体结构和LD衰减 驯化后经历选择的候选基因组区域 起源中心.迁移路线和多样性 GWAS 一.来源 Resequencing of 429 chickpe ...

  4. ZAQI

    mysql> CREATE TABLE emploee ( -> name CHAR(64) NOT NULL, -> email CHAR(64), -> password ...

  5. rsync实现windows和windows之间的数据同步

    一:环境 1.同步对象:测试数据 2.服务端:Windows Server 2008 R2 3.客户端:Windows7 旗舰版64位 4.服务端rsync版本:cwRsyncServer_4.1.0 ...

  6. Azure Key Vault(二)- 入门简介

    一,引言 在介绍 Azure Key Vault 之前,先简单介绍一下 HSM(硬件安全模块). -------------------- 我是分割线 -------------------- 1,什 ...

  7. map/multimap深度探索

    map/multimap同样以rb_tree为底层结构,同样有元素自动排序的特性,排序的依据为key. 我们无法通过迭代器来更改map/multimap的key值,这个并不是因为rb_tree不允许, ...

  8. spring-dm 一个简单的实例

    spring-dm2.0  运行环境,支持JSP页面 运行spring web 项目需要引用包

  9. 实时数据同步inotify+rsync

    inotify inotify是一个实时监控服务,他能实时监控服务器中的目录的变化,发现目录中变化后,在配合rsync服务推送到备份服务器上 inotify要求内核要在2.6.13或之上,通过noti ...

  10. linux环境centos

    qhost:查看集群 投送到集群qsub -l vf=2G,p=1 work.sh -cwd -V all_section_run.sh 杀死任务 qdel  id qstat -u \* |less ...