按类型从表达式中提取所有条件

8

给定一个类似于 Expression<Func<TEntity, bool>> 的表达式

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")
        )
    )
)

我正在尝试按类型提取列表属性条件,即:
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" } ]

到目前为止,我已经创建了一个ExpressionVisitor并确定了VisitBinary方法是我想要插入以获取信息的方法。
但是,我仍然不知道:
- 如何确定我正在查看的BinaryExpression是否表示终端语句(即没有更多嵌套表达式需要查看) - 如何确定BinaryExpression所涉及的实体类型 - 是否需要覆盖其他ExpressionVisitor方法以涵盖我尚未考虑的情况。

你能发布ExpressionVisitor的代码吗? - Shlomo
1个回答

4

不确定真正的用例是什么,但这里有一些起点

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);
    }
}

这个想法很简单。覆盖 Visit 方法只是为了维护一个处理表达式的堆栈。主要处理在 VisitMember 覆盖中进行,该覆盖会为每个属性/字段访问器调用。使用 node.Expression.NodeType != ExpressionType.Constant 来消除闭包成员,而第二个条件则消除了集合属性。最后,从堆栈中提取潜在的条件表达式。
结果包括 MemberExpression 和使用它的 ExpressionMemberExpression.Expression.Type 是实体类型,MemberExpression.Member 是该类型的属性/字段。
样例测试:
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;
    }
}

2
这是一个非常有趣的想法!!!不幸的是,有太多的边角情况需要考虑,你只能在发现错误时逐步解决。例如,t.BoolProp == true && t.BoolProp 意思相同,但表示方式却截然不同。t.CollectionProp.Any()、t.CollectionProp.FirstOrDefault() != null 等等等等,怎么办呢?这个问题很快就变得非常复杂了!我相信,在某些情况下甚至不清楚如何继续进行。但是对于这个堆栈,我给出+1。 - MBoros

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接