Nathan Ridley
Nathan Ridley

Reputation: 34396

How to evaluate a standalone boolean expression in a LINQ expression tree

I'm using the standard visitor pattern to iterate through a LINQ expression tree in order to generate dynamic SQL WHERE clauses.

My issue is that unlike C#, you can't use a standalone boolean expression in SQL; you have to compare it to either 1 or 0.

Given this hypothetical lambda expression:

h => h.Enabled || h.Enabled == false

It would be easy to mistakenly generate this code:

WHERE Enabled OR Enabled = 0

or this code:

WHERE (Enabled = 1) OR (Enabled = 1) = 0

Both of course will generate an SQL error. What logic should I apply to get around this without my code starting to look really obtuse as I delve deep into subtrees to figure out what the case may be?

EDIT: The example above is of course redundant - I am only using it to illustrate a point.

Examples that could create this scenario:

h => h.Enabled
h => h.Enabled == enabled
h => h.Enabled == true

Naturally that last example is poor style, but my code is being designed to work independent of the programmer's skill level, so to not cater for redundant scenarios would be poor form on my part.

Upvotes: 4

Views: 3774

Answers (3)

Ali
Ali

Reputation: 478

Other may have same question. I have solved this by saving last BinaryExpression into a variable This will solve the following different boolean clauses

Where(f => f.IsActive == true || !f.IsActive == false || (f.Name == "adfd") && (f.IsActive || !f.IsBin) )

This the code which handles standalone boolean

private string GetFilterMember(MemberExpression exp)
{
    var str = exp.ToString();
    if (str.Count(f => f == '.') > 1)
    {
        var expType = exp.Type;
        var declareType = exp.Member.DeclaringType;
        if (declareType == typeof(string))
        {
            if (exp.Member.Name == StringMethods.Length)
            {
                return $"{StringMethods.GetODataMethod(StringMethods.Length)}({exp.NameLeftPart()})";
            }
        }
        else if (declareType == typeof(DateTime))
        {

        }
    }
    if (exp.Type == typeof(bool))
    {
        if (prviousBinaryExp != null && (prviousBinaryExp.Left == exp || prviousBinaryExp.Right == exp || (prviousBinaryExp.Left.NodeType == ExpressionType.Not && ((UnaryExpression)prviousBinaryExp.Left).Operand == exp) || (prviousBinaryExp.Right.NodeType == ExpressionType.Not && ((UnaryExpression)prviousBinaryExp.Right).Operand == exp)) && (prviousBinaryExp.NodeType != ExpressionType.Equal && prviousBinaryExp.NodeType != ExpressionType.NotEqual))
        {          
            return $"{exp.Member.Name}{ODataQueryOperator.Equal}{FilterBuilder(Expression.Constant(true))}";
        }
    }
    return exp.Member.Name;
}
BinaryExpression prviousBinaryExp = null;
protected virtual string FilterBuilder(Expression exp)
{

    switch (exp.NodeType)
    {
        case ExpressionType.OrElse:
        case ExpressionType.AndAlso:
        case ExpressionType.Equal:
            prviousBinaryExp = (BinaryExpression)exp;
            return BinaryExpressionBuilder(((BinaryExpression)exp).Left, ODataQueryOperator.GetLogicalOperator(exp.NodeType), ((BinaryExpression)exp).Right);
        case ExpressionType.Constant:
            return GetConstant(exp.Type, ((ConstantExpression)exp).Value);
        case ExpressionType.MemberAccess:
            return GetFilterMember((MemberExpression)exp);
        case ExpressionType.Not:
            return UnaryExpressionBuilder((UnaryExpression)exp);
        case ExpressionType.Call:
            return MethodBuilder((MethodCallExpression)exp);
        default:
            return string.Empty;

    }
}

Not down this variable BinaryExpression prviousBinaryExp = null; When you hit BinaryExpression then save that expression in above variable

case ExpressionType.OrElse:
case ExpressionType.AndAlso:
case ExpressionType.Equal:
     prviousBinaryExp = (BinaryExpression)exp;
     return BinaryExpressionBuilder(((BinaryExpression)exp).Left, ODataQueryOperator.GetLogicalOperator(exp.NodeType), ((BinaryExpression)exp).Right);

This is the logic which checks if a previous expression was binaryexpression and the the current expression is the part of the BinaryExpression's Left or Right expression. We will check the Nodtype which should not be ExpressionType.Equal and ExpressionType.NotEqual(f.IsActive == true). We will also make sure that if Left and Right of BinaryExpression is UnaryExpression than we will check it's UnaryExpression.Operand is equal to current expression. This will handled !f.Enabled or !f.IsActive etc.

Finally I am converting Expression to OData equivalent string. Which is

        // converted String IsActive to OData equivalent
        //         IsActive          eq            true
        return $"{exp.Member.Name}{ODataQueryOperator.Equal}{FilterBuilder(Expression.Constant(true))}";

Upvotes: 0

Brannon
Brannon

Reputation: 26109

The following cases are pretty straight forward:

h => h.Enabled == enabled
h => h.Enabled == true

These are BinaryExpression nodes, and you can directly translate them into:

WHERE (Enabled = @p0)
WHERE (Enabled = 1)

The special case(s) that you need to handle are:

h => h.Enabled
h => !h.Enabled

Those are represented differently in the expression tree (as a MemberExpression). So you would need to special case the MemberExpression and determine if it's accessing a boolean property or not. If it is, then you translate it into the canonical form (detecting the UnaryExpression in the second example):

WHERE (Enabled = 1)
WHERE (Enabled = 0)

Alternatively, you might be able to pre-process the expression tree and translate any special cases into their canonical (expression tree) form. For example, any MemberExpression nodes that fit the criteria could be transformed into the correct BinaryExpression.

Upvotes: 5

Matt Kocaj
Matt Kocaj

Reputation: 11535

Is it not possible to process the operands completely before eveluating the operators?

Ie. Eval each of:

h => h.Enabled
h => h.Enabled == enabled
h => h.Enabled == true

to

WHERE (Enabled = 1)

and then in the case where operators are included in the lambda, process the collection of rendered operands with the equivalent SQL to meet the operator requirements.

Upvotes: 0

Related Questions