简体   繁体   中英

Use ExpressionVisitor to change 'obj == value' to 'obj.Equals(value)'

I'm trying to compare an object with a random value, which could be an ID, and ObjectKey or even with the same object. In short, I want to compare an object with anything, not just the same type.

To do this, I overrode the Equals() and GetHashCode() for the object, and it is working as expected. But I noticed Linq will not call these methods when I search via 'obj == value'.

If I change the queries to 'obj.Equals(value)', the Equals() method is called as it should. But it's not what I need.

Further, I've tried to overload '==' and '!=' operators, but as I'm searching via interfaces, these overloads are not being called.

At the end, I can't just change all my queries by hand, because someone may use the '==' anywhere in the future, breaking the code.

So I come to ExpressionVisitor. I noticed I can rewrite expressions for my Linq queries, but I'm kinda clueless. I've tried some examples I found, but I got some sort of errors.

Finally, this is what I need via ExpressionVisitor:

replace this: var objects = ctx.Where(obj => obj == value);

to this: var objects = ctx.Where(obj => obj.Equals(value));

Is it possible?

This is possible. You can write a proxy query provider that passes the query to the real provider after rewriting the expression.

You can also pursue the approach that "LinqKit" uses with it's AsExpandable rewriter. This approach is much easier but requires inserting these calls into each query.

You also could use Roslyn to perform a one-time refactoring of the source code. The drawback with that is that the source code will look less nice with those Equals calls.

I don't have the necessary time to sketch these solutions because they are a lot of work. For AsExpandable you can find working code on the web. I'm sure there are tutorials for writing a LINQ provider as well.

Yay. Found it:

class Program
{
    static void Main(string[] args)
    {
        //the sample:
        Expression<Func<string, bool>> expr = name => name == "AA" || name.Length > 0 || name != "b";
        Console.WriteLine(expr);
        EqualsModifier treeModifier = new EqualsModifier();
        Expression modifiedExpr = treeModifier.Modify((Expression)expr);
        Console.WriteLine(modifiedExpr);
        Console.ReadLine();
    }
}
//the ExpressionVisitor
public class EqualsModifier : ExpressionVisitor
{
    public Expression Modify(Expression expression)
    {
        return Visit(expression);
    }
    protected override Expression VisitBinary(BinaryExpression b)
    {
        if (b.NodeType == ExpressionType.Equal)
        {
            Expression left = this.Visit(b.Left);
            Expression right = this.Visit(b.Right);
            MethodInfo equalsMethod = typeof(string).GetMethod("Equals", new[] { typeof(string) });
            return Expression.Call(left, equalsMethod, right);
        }
        else if (b.NodeType == ExpressionType.NotEqual)
        {
            Expression left = this.Visit(b.Left);
            Expression right = this.Visit(b.Right);
            MethodInfo equalsMethod = typeof(string).GetMethod("Equals", new[] { typeof(string) });
            return Expression.Not(Expression.Call(left, equalsMethod, right));
        }
        return base.VisitBinary(b);
    }
}

These all output to:

Original: name => (((name == "AA") OrElse (name.Length < 0)) OrElse (name != "b"))

Converted: name => ((name.Equals("AA") OrElse (name.Length < 0)) OrElse Not(name.Equals("b")))

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM