2013-03-02 2 views
0

MSDN документация одного заявление имеет приятный example of parsing an expression tree:Синтаксический логическое выражение дерева

// 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, где выходной пункт строка будет:

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

Очевидно мой вообразил анализатор делает предположение, в отсутствии бинарный оператор, просто утверждать «истину», как в случае «IsExempt = true»

+1

Я просто хотел бы заявить, что вы можете «игнорировать» круглые скобки. Точка в скобках - просто сказать компилятору, если 5 + 3 + 2 должно быть (5 + 3) + 2 или 5 + (3 + 2), однако компилятор представляет выражение как дерево синтаксиса (что-то вроде этого: 'add (5, add (3, 2)') или 'add (добавить (5, 3), 2)'). – Alxandr

ответ

1

Вот реализация, которая по крайней мере преобразует ваш вход в действительное выражение 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> с использованием бетона foo, вы должны использовать Func<Foo, bool>. Однако он все равно будет работать. Это просто не выглядит правильным.

Expression<Func<Foo, bool>> exprTree = 
    (foo) => ((foo.Scale < 5 || foo.Scale > 20) && foo.Scale != -100) || foo.IsExempt == true; 

Очевидно, что, как правило, не требуется, чтобы построить SQL тексту себя, когда вы можете использовать LINQ для лиц. Оба LINQ для сущностей и деревьев выражений требуют .NET 3.5, и вы можете фактически Translate LINQ to sql statement.

Я не уверен, что на SQL Server будет работать выражение типа IsExempt = TRUE. Я думаю, что это должно быть IsExempt = 1, так как тип данных bit. Кроме того, выражения типа Value == null или Value != null необходимо обрабатывать отдельно, так как вы не можете использовать Value = NULL или Value <> NULL в выражении SQL. Он должен быть Value IS NULL или Value IS NOT NULL.

1

Так звучит так, что ваша проблема не является «разбором» как таковой (поскольку выражение уже анализируется как выражение C#), вы хотите, чтобы пересечь дерево выражений и вывести выражение SQL.

Я бы не рекомендовал перекатывать свой собственный код, если вы можете его избежать. Для большинства людей LINQ to Entities, вероятно, будет лучшим способом пойти as it basically does this for you, в то же время скрывая 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... 
    } 
} 

Будучи рекурсивным средством этого подход должен справиться с любым уровнем вводной глубины. Это немного наивно, поэтому он поставит больше скобок, чем вам нужно.

Смежные вопросы