Вот реализация, которая по крайней мере преобразует ваш вход в действительное выражение 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
.
Я просто хотел бы заявить, что вы можете «игнорировать» круглые скобки. Точка в скобках - просто сказать компилятору, если 5 + 3 + 2 должно быть (5 + 3) + 2 или 5 + (3 + 2), однако компилятор представляет выражение как дерево синтаксиса (что-то вроде этого: 'add (5, add (3, 2)') или 'add (добавить (5, 3), 2)'). – Alxandr