0

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”的数据库表,正确的 SQL where 子句字符串输出将是:

(( Scale < 5 or Scale > 20) and Scale != -100) or IsExempt = true

显然,我想象中的解析器假设在没有二元运算符的情况下简单地断言“真”,如“IsExempt = true”的情况

4

2 回答 2

2

这是一个至少可以将您的输入转换为有效 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);

我建议以更实用的方式构建表达式树。Func<bool>而不是使用混凝土来构建 a ,而foo应该使用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 to 实体和表达式树都需要 .NET 3.5,您实际上可以将 LINQ 转换为 sql 语句

我不确定类似的表达式IsExempt = TRUE是否适用于 SQL Server。我认为应该是IsExempt = 1因为数据类型是bit. 由于您不能在 SQL 表达式中使用or ,因此类似Value == nullor的表达式也Value != null需要单独处理。它必须是或。Value = NULLValue <> NULLValue IS NULLValue IS NOT NULL

于 2013-03-02T20:55:30.887 回答
1

所以听起来你的问题不是“解析”(因为表达式已经被解析为 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...
    }
}

递归意味着这种方法应该处理任何级别的括号深度。这有点天真,所以它会放入比你需要的更多的括号。

于 2013-03-02T19:28:56.623 回答