Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Remove a condition from an expression using an ExpressionVisitor

Tags:

c#

linq

I am trying to build a LINQ provider to a well defined web API with a well defined model. I am following these walkthroughs:

  • Part I
  • Part II

I have been able to create the Query Provider that translates the expression to the desired URL and it works pretty good.

Now here is the next step that I just cannot figure out. Imagine that one of the requests returns an object defined like this:

[JsonObject]
public class SomeObject
{
   [JsonProperty(PropertyName = "id")]
   public string Id {get;set;}
   [JsonProperty(PropertyName = "name")]
   public string Name {get;set;}
   [JsonProperty(PropertyName = "is_active")]
   public bool IsActive {get;set;}

   public string SomeOtherObjectId {get;set;}
}

Now as you can see the SomeOtherObjectId is not decorated with the JsonProperty attribute, that is because it is not part of the returned object definition but it is needed because it is the parameter that the get request depends on. This means that an expression like the following:

Expression<Func<SomeObject, bool>> exp = o => o.SomeOtherObjectId == "someId" && o.IsActive;

Is translated into something like:

blablabla/someId/....

Now the web API searches are limited in the parameters they expect so the IsActive filter will be ignored so I figured that after the response is received I could use the same expression as a predicate in a LINQ to Objects query so the rest of the filters are taken into consideration but in order to do so I would have to recreate the expression without the ProjectId filter since it does not exist in the returned JSON that gets deserialized into the object, so it would have to become something like:

Expression<Func<SomeObject, bool>> modifiedExp = o => o.IsActive;

So then I can do something like

return deserializedObject.Where(modifiedExp);

I have been searching for examples of how to do this using an ExpressionVisitor but I just cannot understand how to recreate the expression without the filter I want removed.

Thank you for your help.

UPDATE:

Thank you to both Evk and MaKCbIMKo since with their combined answers I got the final solution which I am including in the hope that it might help others.

protected override Expression VisitBinary(BinaryExpression node)
    {
        if (node.NodeType != ExpressionType.Equal) return base.VisitBinary(node);

        if (new[] { node.Left, node.Right }
            .Select(child => child as MemberExpression)
            .Any(memberEx => memberEx != null && memberEx.Member.CustomAttributes.All(p => p.AttributeType.Name != "JsonPropertyAttribute")))
        {
            return Expression.Constant(true);
        }

        return base.VisitBinary(node);
    }
like image 919
Sergio Romero Avatar asked Apr 20 '16 14:04

Sergio Romero


2 Answers

It depends on how complex your expression tree really is, but in most simple case you can do like that:

class Visitor : ExpressionVisitor {
    protected override Expression VisitBinary(BinaryExpression node) {
        // SomeOtherObjectId == "someId"
        if (node.NodeType == ExpressionType.Equal) {
            foreach (var child in new[] {node.Left, node.Right}) {
                var memberEx = child as MemberExpression;
                if (memberEx != null && memberEx.Member.Name == "SomeOtherObjectId") {
                    // return True constant
                    return Expression.Constant(true);
                }
            }
        }
        return base.VisitBinary(node);
    }        
}

This assumes your "SomeOtherObjectId == "someId"" expression is at some "and" chain, so you just replace it with "true" which removes it's effect.

Then you do:

var anotherExp = (Expression<Func<SomeObject, bool>>) new Visitor().Visit(exp);

If your expression might be more complex - this example should give you an idea of how to handle it.

like image 147
Evk Avatar answered Nov 09 '22 10:11

Evk


Here is a basic example about how to modify expression tree.

You will need to create your own ExpressionVisitor and configure it in a way to look for the conditions that use properties that you want to skip (e.g. Json attribute is missing or by different attribute).

Then just delete them from tree or replace with 'always true' expression instead of checking the property value.

Also, here you can find helpful information about what you're asking for.

like image 2
MaKCbIMKo Avatar answered Nov 09 '22 10:11

MaKCbIMKo