解析单语句布尔表达式树
本文关键字:布尔表达式 语句 单语句 | 更新日期: 2023-09-27 18:01:57
MSDN文档有一个解析表达式树的好例子:
// Create an expression tree.
Expression<Func<int, bool>> exprTree = num => num < 5;
// Decompose the expression tree.
ParameterExpression param = (ParameterExpression)exprTree.Parameters[0];
BinaryExpression operation = (BinaryExpression)exprTree.Body;
ParameterExpression left = (ParameterExpression)operation.Left;
ConstantExpression right = (ConstantExpression)operation.Right;
Console.WriteLine("Decomposed expression: {0} => {1} {2} {3}",
param.Name, left.Name, operation.NodeType, right.Value);
但是我还没有见过这样解析的例子:
MyDomainObject foo;
Expression<Func<bool>> exprTree = () => ((foo.Scale < 5 || foo.Scale > 20) && foo.Scale <> -100) || foo.IsExempt;
我的目标是找到或构建一个实用程序,它可以(1)处理任何级别的括号嵌套,(2)生成包含等效SQL"where"子句的字符串,作为解析表达式树的产物。谁有代码片段可以帮助或者知道解决这个问题的nuget包?
对于我上面的嵌套表达式,假设一个名为'MyDomainObject'的DB表,正确的SQL where子句字符串输出将是:
(( Scale < 5 or Scale > 20) and Scale != -100) or IsExempt = true
显然,我想象中的解析器假设,在没有二进制操作符的情况下,简单地断言'true',如"IsExempt = true"
下面是一个实现,它至少将您的输入转换为有效的SQL表达式。你需要自己实现更多的表达式类型,但是它可以让你了解它是如何工作的。
这个答案恰好与Kazetsukai的答案非常相似,但它使用Expression.NodeType
来查找运算符,因为表达式树中没有MethodInfos
。
也要注意,这会产生比实际需要更多的括号。为了减少括号的数量,需要进一步分析表达式,考虑SQL中的操作符优先级。
public static string GetSqlExpression(Expression expression)
{
if (expression is BinaryExpression)
{
return string.Format("({0} {1} {2})",
GetSqlExpression(((BinaryExpression)expression).Left),
GetBinaryOperator((BinaryExpression)expression),
GetSqlExpression(((BinaryExpression)expression).Right));
}
if (expression is MemberExpression)
{
MemberExpression member = (MemberExpression)expression;
// it is somewhat naive to make a bool member into "Member = TRUE"
// since the expression "Member == true" will turn into "(Member = TRUE) = TRUE"
if (member.Type == typeof(bool))
{
return string.Format("([{0}] = TRUE)", member.Member.Name);
}
return string.Format("[{0}]", member.Member.Name);
}
if (expression is ConstantExpression)
{
ConstantExpression constant = (ConstantExpression)expression;
// create a proper SQL representation for each type
if (constant.Type == typeof(int) ||
constant.Type == typeof(string))
{
return constant.Value.ToString();
}
if (constant.Type == typeof(bool))
{
return (bool)constant.Value ? "TRUE" : "FALSE";
}
throw new ArgumentException();
}
throw new ArgumentException();
}
public static string GetBinaryOperator(BinaryExpression expression)
{
switch (expression.NodeType)
{
case ExpressionType.Equal:
return "=";
case ExpressionType.NotEqual:
return "<>";
case ExpressionType.OrElse:
return "OR";
case ExpressionType.AndAlso:
return "AND";
case ExpressionType.LessThan:
return "<";
case ExpressionType.GreaterThan:
return ">";
default:
throw new ArgumentException();
}
}
结果是:
(((([Scale] < 5) OR ([Scale] > 20)) AND ([Scale] <> -100)) OR ([IsExempt] = TRUE))
像这样调用方法:
string sqlExpression = GetSqlExpression(exprTree.Body);
我建议以更实用的方式构建表达式树。而不是使用具体的foo
构建Func<bool>
,你应该使用Func<Foo, bool>
。然而,无论如何,它都会起作用。这看起来就是不对。
Expression<Func<Foo, bool>> exprTree =
(foo) => ((foo.Scale < 5 || foo.Scale > 20) && foo.Scale != -100) || foo.IsExempt == true;
显然,当你可以使用LINQ to Entities时,通常不需要自己构建SQL文本。LINQ到实体和表达式树都需要。net 3.5,你可以把LINQ转换成sql语句。
我不确定像IsExempt = TRUE
这样的表达式是否会在SQL Server上工作。我认为应该是IsExempt = 1
,因为数据类型是bit
。另外,像Value == null
或Value != null
这样的表达式需要单独处理,因为不能在SQL表达式中使用Value = NULL
或Value <> NULL
。必须为Value IS NULL
或Value IS NOT NULL
所以听起来你的问题不是"解析"这样(因为表达式已经被解析为c#表达式),你想要的是遍历表达式树并输出一个SQL表达式。
如果可以避免的话,我不建议您为它滚动自己的代码。对于大多数人来说,LINQ to Entities可能是更好的方式,因为它基本上为你做了这些,同时隐藏了SQL。如果你有其他要求(比如低。net版本,或者你必须有SQL字符串),并且愿意自己写代码,你可以用递归函数来实现。这个函数可以接受一个表达式,并以字符串的形式返回SQL子句。
类似于(没有测试过,把它当作伪代码):
public string WriteClause(Expression exp)
{
if (exp is ParameterExpression)
{
return (exp as ParameterExpression).Name;
}
else if (exp is BinaryExpression)
{
var binaryExpression = exp as BinaryExpression;
return "(" +
WriteClause(binaryExpression.Left) + " "
GetSqlOperator(binaryExpression.Method) + " "
WriteClause(binaryExpression.Right) +
")";
}
else if...
...etc...
}
public string GetSqlOperator(MethodInfo method)
{
switch (method.Name)
{
case "Add":
return "+";
case "Or":
return "or";
...etc...
}
}
是递归的意味着该方法应该处理任何级别的括号深度。它有点naïve,所以它会放入比您需要的更多的括号。