前言:
我们在工作当中,经常会接触到与表达式树相关的内容,比如 EF,EF Core中的 lambda,就是表达式树。
表达式树是一种数据结构,一个能拼装能解析的数据结构。
一、使用lambda声明表达式树
以下代码演示声明表达式树和表达式树转换为委托。
// lambda表达式 声明表达式目录树,是一个数据结构 (快捷方式)
Expression<Func<int, int, int>> expr = (m, n) => (m * n) + 2;
// 表达式目录树 转换为 委托
Func<int, int, int> func = expr.Compile();
二、表达式树的分解
先来一个简单的实例,该实例只包含了简单的运算。
// lambda表达式 声明
Expression<Func<int, int, int>> expr = (m, n) => m * n + m + n + 2;
// 常规方式 声明
ParameterExpression parameterExpression = Expression.Parameter(typeof(int), "m");
ParameterExpression parameterExpression2 = Expression.Parameter(typeof(int), "n");
BinaryExpression binaryExpression = Expression.Multiply(parameterExpression, parameterExpression2);
BinaryExpression BinaryExpression2 = Expression.Add(binaryExpression, parameterExpression);
BinaryExpression BinaryExpression3 = Expression.Add(BinaryExpression2, parameterExpression2);
ConstantExpression constantExpression = Expression.Constant(2, typeof(int));
BinaryExpression body = Expression.Add(BinaryExpression3, constantExpression);
ParameterExpression[] array = new ParameterExpression[2];
array[0] = parameterExpression;
array[1] = parameterExpression2;
Expression<Func<int, int, int>> expression = Expression.Lambda<Func<int, int, int>>(body, array);
再来一个进阶版的实例。
// lambda表达式目录树 的分解(2)
Expression<Func<User, bool>> expr = x => x.Id.ToString().Equals("5");
User user = new User();
ParameterExpression parameterExpression = Expression.Parameter(typeof(User), "x");
var constantExp = Expression.Constant("5", typeof(string));
FieldInfo userId = typeof(User).GetField("Id");
MemberExpression fieldExp = Expression.Field(parameterExpression, userId);
MethodInfo toString = user.Id.GetType().GetMethod("ToString", new Type[] {
});
var toStringExp = Expression.Call(fieldExp, toString, Array.Empty<Expression>());
var equals = typeof(string).GetMethod("Equals", new Type[] {
typeof(string) });
var equalsExp = Expression.Call(toStringExp, equals, new Expression[] {
constantExp });
ParameterExpression[] array = new ParameterExpression[1];
array[0] = parameterExpression;
Expression<Func<User, bool>> expression = Expression.Lambda<Func<User, bool>>(equalsExp, array);
三、表达式树实现实体映射
映射的代码:
public class ExpressionGenericMapper<TIn, TOut>
{
private static Func<TIn, TOut> _func = null;
static ExpressionGenericMapper()
{
ParameterExpression parameterExpression = Expression.Parameter(typeof(TIn), "p");
List<MemberBinding> memberBindingList = new List<MemberBinding>();
foreach (var item in typeof(TOut).GetProperties())
{
MemberExpression property = Expression.Property(parameterExpression, typeof(TIn).GetProperty(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
foreach (var item in typeof(TOut).GetFields())
{
MemberExpression property = Expression.Field(parameterExpression, typeof(TIn).GetField(item.Name));
MemberBinding memberBinding = Expression.Bind(item, property);
memberBindingList.Add(memberBinding);
}
MemberInitExpression memberInitExpression = Expression.MemberInit(Expression.New(typeof(TOut)), memberBindingList.ToArray());
Expression<Func<TIn, TOut>> lambda = Expression.Lambda<Func<TIn, TOut>>(memberInitExpression, new ParameterExpression[]
{
parameterExpression
});
_func = lambda.Compile();//拼装是一次性的
}
public static TOut Trans(TIn t)
{
return _func(t);
}
}
映射:
// 实体映射
var user = new User() {
Id = 1, Name = "111", Age = 12 };
var userDto = ExpressionGenericMapper<User, UserDto>.Trans(user);
四、表达式树的访问或重写
表达式树的访问或重写需要继承 ExpressionVisitor 类,重写相关方法。
以下是示例:
public class OperationsVisitor : ExpressionVisitor
{
public override Expression Visit(Expression node)
{
Console.WriteLine($"Visit {node}");
return base.Visit(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
if (node.NodeType == ExpressionType.Add)
{
var left = Visit(node.Left);
var right = Visit(node.Right);
// 将 相加 变成 相减.
return Expression.Subtract(left, right);
}
else if (node.NodeType == ExpressionType.Multiply)
{
var left = Visit(node.Left);
var right = Visit(node.Right);
// return Expression.Divide(left, right);
return Expression.Divide(node.Left, node.Right);
}
return base.VisitBinary(node);
}
}
调用方法:
// 表达式树的访问或重写 (ExpressionVisitor)
Expression<Func<int, int, int>> exp = (m, n) => m * n + 2;
OperationsVisitor visitor = new OperationsVisitor();
// 修改表达式数
Expression<Func<int, int, int>> expNew = visitor.Visit(exp) as Expression<Func<int, int, int>>;
var a = expNew.Compile()(8, 4);
// expNew 为 (m, n) => m / n - 2;
// a 为 0
五、表达式树的Sql拼装
public class OperationsVisitor : ExpressionVisitor
{
private readonly Stack<string> stack = new Stack<string>();
public string Condition()
{
var condition = string.Concat(stack.ToArray());
stack.Clear();
return condition;
}
private string ToSqlOperator(ExpressionType type)
{
switch (type)
{
case (ExpressionType.AndAlso):
return "AND";
case ExpressionType.GreaterThan:
return ">";
case (ExpressionType.Equal):
return "=";
default:
throw new Exception("不支持该方法");
}
}
protected override Expression VisitBinary(BinaryExpression node)
{
Console.WriteLine($"VisitMember {node}");
if (node == null)
throw new ArgumentNullException("BinaryExpression");
stack.Push(")");
Visit(node.Right);
stack.Push($" {ToSqlOperator(node.NodeType)} ");
Visit(node.Left);
stack.Push("(");
return node;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node == null)
throw new ArgumentNullException("MemberExpression");
stack.Push($"[{node.Member.Name}]");
return node;
}
protected override Expression VisitConstant(ConstantExpression node)
{
if (node == null)
throw new ArgumentNullException("ConstantExpression");
stack.Push($"{node.Value}");
return node;
}
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node == null)
throw new ArgumentNullException("MethodCallExpression");
Visit(node.Object);
Visit(node.Arguments[0]);
string right = stack.Pop();
string left = stack.Pop();
switch (node.Method.Name)
{
case "StartsWith":
stack.Push($"({left} LIKE '{right}%')");
break;
default:
throw new NotSupportedException($"{node.NodeType} is not supported!");
}
return node;
}
}
调用时:
Expression<Func<User, bool>> exp = x => x.Name.StartsWith("鹿") && x.Age > 5 && x.Id == 10;
OperationsVisitor visitor = new OperationsVisitor();
visitor.Visit(exp);
var sqlWhere = visitor.Condition();
// ((([Name] LIKE '鹿%') AND ([Age] > 5)) AND ([Id] = 10))