表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和 x < y 这样的二元运算等。可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。 表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET 之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL)。
一、基础用法
(1) Expression 和 Func 的区别
- Expression 存储了运算逻辑,可以将其保存成抽象语法树(AST),可以在运行时动态获取运算逻辑。
- Func 只是存储了结果,无法保存成语法树,也无法动态获取运算逻辑。
所以,在 EFCore 中,使用表达式对数据库数据进行查询中,我们应该选择 Expression 而不是 Func,因为使用了 Func ,实际上并无法将 Func 中的表达式转换成 SQL,而是在将所有数据加载到内存后,在内存中在过滤 Func 中的条件。
简单来说就是,此时要筛选 User 表中年龄大于18的数据,可以有这两种写法
// 这种写法,实际生成的 SQL 语句, 大概是这样的 SELECT * FROM User as T WHERE T.age > 18
Expression<Func<User,bool>> expression1 = x => x.Age > 18;
dbContext.User.Where(expression1).toList();
// 而这种, 生成的语句是这样的 SELECT * FROM User, 然后将 User 表中所有数据加载到内存中后, 在进行 age > 18 的过滤
Func<User, bool> func1 = x => x.Age > 18;
dbContext.User.Where(func1).toList();
(2) 通过代码创建表达式树
- ParameterExpression
- BinaryExpression
- MethodCallExpression
- ConstantExpression
- UnaryExpression
这些类几乎都没有提供构造方法,而且所有的属性都几乎只是只读。因此我们一般不会直接创建这些类的实例,而是调用 Expression 类的 Parameter、MakeBinary、Call、Constant等静态方法来生成,这些静态方法我们一般称作创建表达式树的工厂方法,而属性则通过方法参数类设置。
动态将表达式:u => u.Age >= 18; 通过代码构建出来
一般构建步骤:
- 先创建 ParameterExpression
- 接着由里到外逐步构建
- 先左节点(Left)
- 后右节点(Right)
- 接着Body节点
- 将其拼接成 Expression
public IActionResult GetUserByManualExpression()
{
ParameterExpression parameterExpression = Expression.Parameter(type:typeof(User), name: "u");
ConstantExpression right = Expression.Constant(18);
MemberExpression left = Expression.MakeMemberAccess(parameterExpression, member: typeof(User).GetProperty("Age"));
BinaryExpression body = Expression.GreaterThanOrEqual(left, right);
Expression<Func<User, bool>> expression = Expression.Lambda<Func<User, bool>>(body, parameters: parameterExpression);
var data = _userService.GetUsers(expression);
return Ok(new
{
code = 200,
msg = "OK",
data
});
}
二、动态创建表达式
从上述Expression表达式树元素中,可以拆解出如下几个对象定义:字段名、字段值、值类型以及逻辑运算符共四个部分。
public class Conditions
{
/// <summary>
/// 字段名称
/// </summary>
public string Key { get; set; }
/// <summary>
/// 值(不能用string,对所有传入的值类型才可智能转换处理)
/// </summary>
public object Value { get; set; }
/// <summary>
/// 值类型
/// </summary>
public string ValueType { get; set; }
/// <summary>
/// 逻辑运算符
/// </summary>
public OperatorEnum Operator { get; set; }
}
Public Enum OperatorEnum
//包含
Contains
//MethodCallExpression带方法
[Call]
//等于
Equal
//大于
Greater
//大于等于
GreaterEqual
//小于
Less
//小于等于
LessEqual
//不等于
NotEqual
//等于多个in (a,b,c)
[In]
//范围内 >= a and <= b
Between
End Enum
首先我们要创建一个查询条件转化为表达式的泛型功能类 如 ExpressionParser<T> 至于为什么要用泛型类目的很明确就是为了适配不同的模型参数。转化条件为表达式 那么处理一个方法来接受条件 返回表达式,条件可以按照自己的模式去设置。
public class ExpressionParser<T>
{
private var parameter = Expression.Parameter(typeof(T));
public Expression<Func<T, bool>> ParserConditions(IEnumerable<Conditions> conditions)
{
// 将条件转化成表达是的Body
var query = ParseExpressionBody(conditions);
return Expression.Lambda<Func<T, bool>>(query, parameter);
}
}
(1) 条件转表达式具体处理
具体去实现 ParseExpressionBody 条件 枚举提供操作方式 如:(like 、 = 、!= 、> 、< 、>= 、<= 、in 、 between)
private Expression ParseExpressionBody(IEnumerable<Conditions> conditions)
{
if (conditions == null || conditions.Count() == 0)
{
return Expression.Constant(true, typeof(bool));
}
else if (conditions.Count() == 1)
{
return ParseCondition(conditions.First());
}
else
{
Expression left = ParseCondition(conditions.First());
Expression right = ParseExpressionBody(conditions.Skip(1));
return Expression.AndAlso(left, right);
}
}
private Expression ParseCondition(Conditions condition)
{
ParameterExpression p = parameter;
Expression key = Expression.Property(p, condition.Key);
Expression value = Expression.Constant(condition.Value);
switch (condition.Operator)
{
case OperatorEnum.Contains:
return Expression.Call(key, typeof(string).GetMethod("Contains",new Type[] { typeof(string) }), value);
case OperatorEnum.Equal:
return Expression.Equal(key, Expression.Convert(value, key.Type));
case OperatorEnum.Greater:
return Expression.GreaterThan(key, Expression.Convert(value, key.Type));
case OperatorEnum.GreaterEqual:
return Expression.GreaterThanOrEqual(key, Expression.Convert(value, key.Type));
case OperatorEnum.Less:
return Expression.LessThan(key, Expression.Convert(value, key.Type));
case OperatorEnum.LessEqual:
return Expression.LessThanOrEqual(key, Expression.Convert(value, key.Type));
case OperatorEnum.NotEqual:
return Expression.NotEqual(key, Expression.Convert(value, key.Type));
case OperatorEnum.In:
return ParaseIn(p, condition);
case OperatorEnum.Between:
return ParaseBetween(p, condition);
default:
throw new NotImplementedException("不支持此操作");
}
}
(2) In和between的处理
private Expression ParaseBetween(ParameterExpression parameter, Conditions conditions)
{
ParameterExpression p = parameter;
Expression key = Expression.Property(p, conditions.Key);
var valueArr = conditions.Value.Split(',');
if (valueArr.Length != 2)
{
throw new NotImplementedException("ParaseBetween参数错误");
}
try
{
int.Parse(valueArr[0]);
int.Parse(valueArr[1]);
}
catch {
throw new NotImplementedException("ParaseBetween参数只能为数字");
}
Expression expression = Expression.Constant(true, typeof(bool));
//开始位置
Expression startvalue = Expression.Constant(int.Parse(valueArr[0]));
Expression start = Expression.GreaterThanOrEqual(key, Expression.Convert(startvalue, key.Type));
Expression endvalue = Expression.Constant(int.Parse(valueArr[1]));
Expression end = Expression.GreaterThanOrEqual(key, Expression.Convert(endvalue, key.Type));
return Expression.AndAlso(start, end);
}
private Expression ParaseIn(ParameterExpression parameter, Conditions conditions)
{
ParameterExpression p = parameter;
Expression key = Expression.Property(p, conditions.Key);
var valueArr = conditions.Value.Split(',');
Expression expression = Expression.Constant(true, typeof(bool));
foreach (var itemVal in valueArr)
{
Expression value = Expression.Constant(itemVal);
Expression right = Expression.Equal(key, Expression.Convert(value, key.Type));
expression = Expression.Or(expression, right);
}
return expression;
}
(3) 扩展分页、排序、查询条件
public static class ExpressionExtensions
{
//扩展查询
public static IQueryable<T> QueryConditions<T>(this IQueryable<T> query, IEnumerable<Conditions> conditions)
{
var parser = new ExpressionParser<T>();
var filter = parser.ParserConditions(conditions);
return query.Where(filter);
}
//扩展多条件排序(改进)
public static IQueryable<T> OrderConditions<T>(this IQueryable<T> query, IEnumerable<OrderConditions> conditions)
{
var type = typeof(T);
var parameter = Expression.Parameter(type, "p");
Expression<Func<T, object>> orderby;
foreach (var orderinfo in conditions)
{
var property = type.GetProperty(orderinfo.Key);
var propertySelector = Expression.Property(parameter, property);
var propertyAccess = Expression.PropertyOrField(parameter, orderinfo.Key);
switch (property.PropertyType)
{
case object _ when typeof(DateTime):
orderby = Expression.Lambda<Func<T, object>>(Expression.Call(Expression.Property(parameter, orderinfo.Key), typeof(DateTime).GetMethod("ToString", Type.EmptyTypes)), parameter);
break;
default:
orderby = Expression.Lambda<Func<T, object>>(propertySelector, parameter);
break;
}
if (orderinfo.Order == OrderSequence.DESC)
query = query.OrderByDescending(orderby);
else
query = query.OrderBy(orderby);
}
return query;
}
//扩展分页
public static IQueryable<T> Pager<T>(this IQueryable<T> query, int pageindex, int pagesize,out int itemCount)
{
itemCount = query.Count();
return query.Skip((pageindex - 1) * pagesize).Take(pagesize);
}
}
public enum OrderSequence
{
ASC,
DESC
}
三、具体调用
private void GetDgvHeader()
{
using (var db = new DbContext())
{
List<Conditions> queryConditions = new List<Conditions>();
// 多条件查询
if (CmbColor.SelectedIndex > 0)
{
Conditions color_filter = new Conditions()
{
Key = "Color",
Operator = OperatorEnum.Equal,
Value = CmbColor.SelectedValue,
ValueType = "String"
};
queryConditions.Add(color_filter);
}
if (CmbIBKind.SelectedIndex > 0)
{
Conditions ibkind_filter = new Conditions()
{
Key = "IbKind",
Operator = OperatorEnum.Equal,
Value = CmbIBKind.SelectedValue,
ValueType = "String"
};
queryConditions.Add(ibkind_filter);
}
if (CmbChipProcess.SelectedIndex > 0)
{
Conditions chip_filter = new Conditions()
{
Key = "ChipProcess",
Operator = OperatorEnum.Equal,
Value = CmbChipProcess.SelectedValue,
ValueType = "String"
};
queryConditions.Add(chip_filter);
}
// 副条件-Bin号
if (CmbBinno.SelectedIndex > 0)
{
Conditions bin_filter = new Conditions()
{
Key = "Binno",
Operator = OperatorEnum.Equal,
Value = CmbBinno.SelectedValue,
ValueType = "String"
};
queryConditions.Add(bin_filter);
}
// 副条件-分档
if (CmbSpec.SelectedIndex > 0)
{
Conditions spec_filter = new Conditions()
{
Key = "Spec",
Operator = OperatorEnum.Equal,
Value = CmbSpec.SelectedValue.ToString().Replace("nm", ""),
ValueType = "Double"
};
queryConditions.Add(spec_filter);
}
// 多条件排序
List<OrderConditions> orderConditions = new List<OrderConditions>()
{
new OrderConditions() { Key = "Pname", Order = OrderSequence.ASC },
new OrderConditions() { Key = "Binno", Order = OrderSequence.ASC }
};
var dtHeader = db.WaferQualityRole.Where(x => x.IsExec).QueryCondition(queryConditions).OrderConditions(orderConditions).ToList().LinqToDataTable();
}
}
至此,常用的逻辑运算表达式封装及调用已基础完整,有兴趣的朋友仍可以对数据类型多态性进行完善实现,感谢阅读。
[2024-3-26已完善]
解决非string数据类型的无法转换的处理。