Extract all conditions from Expression by Type
Asked Answered
F

1

8

Given an Expression<Func<TEntity, bool>> along the lines of

entity => entity.SubEntity.Any(
    subEntity => (
        (subEntity.SomeProperty == False)
        AndAlso
        subEntity.SubSubEntity.FooProperty.StartsWith(
            value(SomeClass+<>c__DisplayClass0).ComparisonProperty
        )
        AndAlso
        subEntity.SubSubEntity.BarProperty == "Bar"
        AndAlso
        subEntity.SubSubEntity.SubSubSubEntity.Any(
            subSubSubEntity => (x.SubSubSubSubEntity.BazProperty == "whatever")
        )
    )
)

I am trying to extract a list property conditions by type, i.e.

TEntity             : [ /* no conditions for immediate members of TEntity */ ] 
TSubEntity          : [ { SomeProperty == False } ]
TSubSubEntity       : [ { FooProperty.StartsWith(/* ... */) },
                        { BarProperty == "Bar" } ],
TSubSubSubEntity    : [ /* no conditions for immediate members of TSubSubSubEntity */ ],
TSubSubSubSubEntity : [ { BazProperty == "whatever" } ]

So far, I have created an ExpressionVisitor and identified the VisitBinary method as the one I want to plug into in order to obtain my information.

I am still at a loss about

  • how to determine whether the BinaryExpression I am looking at represents a terminal statement (in the sense that there are no more nested expressions that I need to look at)
  • how to determine the Entity type that the BinaryExpression is concerned with
  • whether I need to override any of the other ExpressionVisitor methods to cover for cases that I have not considered yet.
Frimaire answered 27/1, 2016 at 16:24 Comment(1)
Can you post the ExpressionVisitor code?Derek
C
4

Not sure what really is the use case, but here is some starting point

class TestVisitor : ExpressionVisitor
{
    public Dictionary<Type, List<Tuple<MemberExpression, Expression>>> Result = new Dictionary<Type, List<Tuple<MemberExpression, Expression>>>();
    Stack<Expression> stack = new Stack<Expression>();
    public override Expression Visit(Expression node)
    {
        stack.Push(node);
        base.Visit(node);
        stack.Pop();
        return node;
    }
    protected override Expression VisitMember(MemberExpression node)
    {
        if (node.Expression.NodeType != ExpressionType.Constant && (node.Type == typeof(string) || !typeof(IEnumerable).IsAssignableFrom(node.Type)))
        {
            var expression = stack.Skip(1).FirstOrDefault();
            if (expression != null && expression.Type == typeof(bool))
            {
                List<Tuple<MemberExpression, Expression>> resultList;
                if (!Result.TryGetValue(node.Expression.Type, out resultList))
                    Result.Add(node.Expression.Type, resultList = new List<Tuple<MemberExpression, Expression>>());
                resultList.Add(Tuple.Create(node, expression));
            }
        }
        return base.VisitMember(node);
    }
}

The idea is simple. Override Visit method just to maintain a stack of processing expressions. The main processing is inside the VisitMember override, which is called for each property/field accessor. The node.Expression.NodeType != ExpressionType.Constant is used to eliminate the closure members, while the second condition eliminates collection properties. Finally, the potential condition expression is extracted from the stack.

The result is including both MemberExpression and the Expression where it is used. MemberExpression.Expression.Type is your entity type, MemberExpression.Member is the property/field of that type.

Sample test:

class Entity
{
    public ICollection<SubEntity> SubEntity { get; set; }
}

class SubEntity
{
    public bool SomeProperty { get; set; }
    public SubSubEntity SubSubEntity { get; set; }
}

class SubSubEntity
{
    public string FooProperty { get; set; }
    public string BarProperty { get; set; }
    public ICollection<SubSubSubEntity> SubSubSubEntity { get; set; }
}

class SubSubSubEntity
{
    public SubSubSubSubEntity SubSubSubSubEntity { get; set; }
}

class SubSubSubSubEntity
{
    public string BazProperty { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        string comparisonProperty = "Ivan";
        Expression<Func<Entity, bool>> e =
            entity => entity.SubEntity.Any(subEntity =>
                subEntity.SomeProperty == false
                &&
                subEntity.SubSubEntity.FooProperty.StartsWith(comparisonProperty)
                &&
                subEntity.SubSubEntity.BarProperty == "Bar"
                &&
                subEntity.SubSubEntity.SubSubSubEntity.Any(subSubSubEntity => subSubSubEntity.SubSubSubSubEntity.BazProperty == "whatever")
                );
        var v = new TestVisitor();
        v.Visit(e);
        var result = v.Result;
    }
}
Cherri answered 28/1, 2016 at 11:7 Comment(1)
this is a very interesting idea!!! unfortunately there are way too many corner cases, that you will only be discovering on the way, as bugs. for example t.BoolProp == true && t.BoolProp mean the same, but are represented quite differently. What about t.CollectionProp.Any(), t.CollectionProp.FirstOrDefault() != null, etc etc etc.... This problem gets very compilcated really soon!!! and im quite sure that in some cases it is not even clear how to proceed. But +1 for the stackBehring

© 2022 - 2024 — McMap. All rights reserved.