本文通过Expression
类 实现将 ((User.Name like "%你好%"&&User.Age>10) || User.Name=="admin"
) 这样的字符串条件转换为 Lambda
表达式,从而达到动态查询的目的。 从此以后,再也不用写那么多参数的查询接口了。
1,什么是表达式树
文档地址:表达式树 (C#) System.Linq.Expressions
命名空间
表达式树以树形数据结构表示代码,其中每一个节点都是一种表达式,比如方法调用和
x < y
这样的二元运算等。
你可以对表达式树中的代码进行编辑和运算。 这样能够动态修改可执行代码、在不同数据库中执行 LINQ 查询以及创建动态查询。 有关 LINQ中表达式树的详细信息,请参阅如何使用表达式树生成动态查询 (C#)。
表达式树还能用于动态语言运行时 (DLR) 以提供动态语言和 .NET之间的互操作性,同时保证编译器编写员能够发射表达式树而非 Microsoft 中间语言 (MSIL)。 有关 DLR的详细信息,请参阅动态语言运行时概述。
你可以基于匿名 lambda 表达式通过 C# 或者 Visual Basic编译器创建表达式树,或者通过System.Linq.Expressions
名称空间手动创建。 -官网解释
重点是 x < y
这个里边,实际上是有三个表达式的,把一系列的表达式串起来以树形结构表示的即为表达式树。
2,继承关系
通过文档可以看出来,要创建表达式树需要通过Expression
类,我们的目标是是把输入的字符串转换为一个可以被orm框架识别的lamba
表达式,那第一个要知道的就一定是他们之间的关系,
随便找个Where
转到定义可以看到下图的定义
继承自LambdaExpression
LambdaExpression
继承自Expression
LambdaExpression
从官网看只有一个派生类就是where
条件中用的类型 Expression<TDelegate>
Expression
的派生类 Expression
类 (System.Linq.Expressions
) 通过文档可以看到有好多
Expression
的派生类
每个类型的使用方式在相关文档中都能找到。
我们的目标是把字符串转换为一个可以被where
方法接受的对象,所以重点就放到lamdaExpress
以及Expression
的方法上,说实话,要把它完全搞明白,好好阅读文档是必须的。
3,创建一个最简单的表达式树
假定我们的表达式是这样的
Expression<Func<string, bool>> res= x=>x.Length>0;
对
Func
不了解的可以看 FreeTimeWorker:C#委托
->Action<T>
->Func<T>
,扩展方法
如上,这是个非常简单lambda表达式,从形式上看 它是由参数和方法体组成的
首先创建一个参数表达式 表示上边代码中=>
前的x
ParameterExpression parameterExpression = Expression.Parameter(typeof(string),"x");
紧接着创建 x.Length
, x.Length
表示的是输入参数的Length
属性 这时候需要一个属性表达式
PropertyInfo length = typeof(string).GetProperty("Length");//string的Length属性
MemberExpression left = Expression.Property(parameterExpression, length);//表示输入参数x的Length属性表达式
然后创建右边的表达式,右边是个常量0
,这时候需要一个常量表达式
Expression right = Expression.Constant(0);
最后,他们两的是通过比较做的计算,前边的表达式大于后边的表达式。
Expression result = Expression.GreaterThan(left, right);
执行调用:
res1.Compile().DynamicInvoke("");//false
res1.Compile().DynamicInvoke("1");//true
4,分析字符结构,创建表达式树
User.Name=="admin" || (User.Name like "%你好%"&&User.Age>10)
这是我们的原始字符串,首先需要保证 User
是类名,Name,Age
是属性名,我们需要做的是拿到每一段的表达式User.Name like "%你好%"
User.Age>10
User.Name=="admin"
根据括号关系对表达式做连接,这个示例里,User.Name like "%你好%"
User.Age>10
它两是and
关系,结果与 User.Name=="admin"
是或的关系。
5,实现
/// <summary>
/// 根据字符串构建条件支持的比较符 >,<,>= ,<=,==, like like两种模式,contain,startwith. %x% x%;
/// </summary>
/// <typeparam name="TDelegate"></typeparam>
/// <param name="source"></param>
/// <param name="condition"> condition中的条件格式为 类名.属性名</param>
/// <returns></returns>
public static Expression<TDelegate> BuildCondition<TDelegate>(this Expression<TDelegate> source, string condition)
{
try
{
if (string.IsNullOrEmpty(condition))
{
return source;
}
string specchar = "&|()";
string tempconditon = "";
int i = 0;
Stack<string> stack = new Stack<string>();//条件符号,和(
Stack<Expression> expressions = new Stack<Expression>();
while (i < condition.Length)
{
if (!specchar.Contains(condition[i]))
{
tempconditon += condition[i];
}
else
{
switch (condition[i])
{
case '&':
i++;
stack.Push("&&");
if (!string.IsNullOrEmpty(tempconditon))
{
expressions.Push(BuildExpression(source, tempconditon));
}
tempconditon = "";
break;
case '|':
i++;
stack.Push("||");
if (!string.IsNullOrEmpty(tempconditon))
{
expressions.Push(BuildExpression(source, tempconditon));
}
tempconditon = "";
break;
case '(':
stack.Push("(");
tempconditon = "";
break;
case ')':
string top = stack.Pop();
while (top != "(")
{
if (!string.IsNullOrEmpty(tempconditon))
{
expressions.Push(BuildExpression(source, tempconditon));
tempconditon = "";
}
if (expressions.Count > 1)
{
string smby = top;//不是括号肯定是符号,不是&&就是||
var exp1 = expressions.Pop();
var exp2 = expressions.Pop();
switch (smby)
{
case "&&":
expressions.Push(Expression.And(exp1, exp2));
break;
case "||":
expressions.Push(Expression.Or(exp1, exp2));
break;
}
}
top = stack.Pop();
}
if (!string.IsNullOrEmpty(tempconditon))
{
expressions.Push(BuildExpression(source, tempconditon));
tempconditon = "";
}
break;
}
}
i++;
}
if (!string.IsNullOrWhiteSpace(tempconditon))
{
expressions.Push(BuildExpression(source, tempconditon));
}
while (expressions.Count > 1)
{
var exp1 = expressions.Pop();
var exp2 = expressions.Pop();
string smby = stack.Pop();
switch (smby)
{
case "&&":
expressions.Push(Expression.And(exp1, exp2));
break;
case "||":
expressions.Push(Expression.Or(exp1, exp2));
break;
}
}
return Expression.Lambda<TDelegate>(expressions.Pop(), source.Parameters.ToArray());
}
catch (Exception ex)
{
throw new Exception(ex.Message);
}
}
public static Expression BuildExpression<TDelegate>(Expression<TDelegate> source, string condition)
{
Regex r = new Regex("([a-zA-Z][a-zA-Z0-9_]*\\.*[a-zA-Z0-9_]*\\s*)([><=]=*|like)(.+)");
var res = r.Match(condition);
if (res.Groups.Count < 4)
{
throw new Exception("条件错误");
}
string left = res.Groups[1].ToString().Trim();
string symbol = res.Groups[2].ToString().Trim();
string right = res.Groups[3].ToString().Trim();
//根据左边表达式找到propertyinfo
string[] leftgroup = left.Split('.');
if (leftgroup.Length < 2)
{
throw new Exception("表达式错误");
}
var parmExp = source.Parameters.ToList().FirstOrDefault(o => o.Type.Name == leftgroup[0].Trim());
if (parmExp == null)
{
throw new Exception("表达式错误,请确保表达式格式为 TableName.FeildName >= 0");
}
var propertyInfo = parmExp.Type.GetProperty(leftgroup[1].Trim());
MemberExpression leftexp = Expression.Property(parmExp, propertyInfo);
if (symbol.Trim().ToLower() == "like")
{
right = right.Trim().TrimStart('\'').TrimEnd('\'').TrimStart('"').TrimEnd('"').Trim();
string methodName = "StartsWith";
if (right.StartsWith("%") && right.EndsWith("%"))
{
methodName = "Contains";
}
MethodInfo methodInfo = typeof(string).GetMethod(methodName, new Type[] { typeof(string) });
ConstantExpression c = Expression.Constant(right.TrimStart('%').TrimEnd('%').Trim(), typeof(string));
return Expression.Call(leftexp, methodInfo, c);
}
else
{
object rightValue = null;
if (propertyInfo.PropertyType == typeof(bool))
{
if (right == "0")
{
rightValue = false;
}
else
{
rightValue = true;
}
}
else if (propertyInfo.PropertyType.BaseType == typeof(System.Enum))
{
if (int.TryParse(right, out int rightintvalue))
{
rightValue = System.Enum.Parse(propertyInfo.PropertyType, System.Enum.GetName(propertyInfo.PropertyType, rightintvalue));
}
else
{
rightValue = System.Enum.Parse(propertyInfo.PropertyType, right);
}
}
else if (propertyInfo.PropertyType == typeof(int))
{
if (int.TryParse(right, out int rightintvalue))
{
rightValue = rightintvalue;
}
else
{
throw new Exception($"表达式错误类型转换失败{propertyInfo.Name}");
}
}
else //string类型的 ==
{
rightValue = right;
}
ConstantExpression rightexp = Expression.Constant(rightValue, propertyInfo.PropertyType);
switch (symbol)
{
case ">":
return Expression.GreaterThan(leftexp, rightexp);
case "<":
return Expression.LessThan(leftexp, rightexp);
case ">=":
return Expression.GreaterThanOrEqual(leftexp, rightexp);
case "==":
return Expression.Equal(leftexp, rightexp);
case "<=":
return Expression.LessThanOrEqual(leftexp, rightexp);
default:
throw new Exception($"表达式错误,未定义符号{symbol}");
}
}
}
6,使用示例
Expression<Func<ViewTestModel, bool>> condition = (item) => true;
string str = @$"(ViewTestModel.Range==1||ViewTestModel.Range>7)&&ViewTestModel.Tel like '%135%' && ViewTestModel.Id>0";
condition= condition.BuildCondition(str);
return _sqlSugarClient.Queryable<TestModel>().Select<ViewTestModel>().Where(condition).ToPage(_httpParameter);