Here is an implementation that will at least transform your input into a valid SQL expression. You need to implement more expression types yourself but it gives you an idea of how it works.
This answer happens to be very similar to Kazetsukai's answer but it uses Expression.NodeType
to find the operators since there will be no MethodInfos
in the expression tree.
Also be aware that this produces more parentheses than actually needed. To reduce the number of parentheses the expression needs to be analysed further considering the operator precedence in 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();
}
}
The result is:
(((([Scale] < 5) OR ([Scale] > 20)) AND ([Scale] <> -100)) OR ([IsExempt] = TRUE))
Call the method like this:
string sqlExpression = GetSqlExpression(exprTree.Body);
I would suggest to build the expression tree in a more functional way. Instead of building a Func<bool>
using a concrete foo
you should use a Func<Foo, bool>
. However, it will work anyway. It just doesn't look right.
Expression<Func<Foo, bool>> exprTree =
(foo) => ((foo.Scale < 5 || foo.Scale > 20) && foo.Scale != -100) || foo.IsExempt == true;
Obviously it is usually not needed to build a SQL text yourself when you can use LINQ to Entities. Both LINQ to entities and expression trees require .NET 3.5 and you can actually Translate LINQ to sql statement.
I'm not sure if an expression like IsExempt = TRUE
will work on SQL Server. I think it should be IsExempt = 1
since the datatype is bit
. Also expressions like Value == null
or Value != null
need to be handled seperately since you cannot use Value = NULL
or Value <> NULL
in a SQL expression. It must be Value IS NULL
or Value IS NOT NULL
.