Search code examples
c#linqexpression-trees

Iterate through the properties of a lambda expression?


How can I iterate through an expression and change the property names based on a custom attribute that I decorated them with?

I use the following code to get the custom attribute of a property, but it works for a simple expression with one property:

var comparison = predicate.Body as BinaryExpression;

var member = (comparison.Left.NodeType == ExpressionType.Convert ?
            ((UnaryExpression)comparison.Left).Operand :
            comparison.Left) as MemberExpression;

var value = comparison.Right as ConstantExpression;

var attribute = Attribute.GetCustomAttribute(member.Member, typeof(MyAttribute)) as MyAttribute;
var columnName = attribute.Name ?? member.Member.Name;
var columnValue = value.Value;

EDIT

Deriving from ExpressionVisitor, I can change the property name by overriding the method VisitMember.

Is this the only place where a property name is used to build the expression?


Solution

  • You can implement a System.Linq.Expressions.ExpressionVisitor to rewrite MemberExpression with the new mapped property. And yes VisitMember is the only place you have to implement this remapping, this is one of the advantages to expression trees and visitors. The only weird case you have to deal with is if the property's type is different to the type of the mapped property.

    void Main()
    {
        var data = new List<TestClass>();
        data.Add(new TestClass()
        {
            FirstName = "First",
            LastName = "Last",
        });
    
        var q = data.AsQueryable().Select(x => x.FirstName);
        var vistor = new MyRewriter();
    
        var newExpression = vistor.Visit(q.Expression);
        var output = newExpression.ToString();
        //System.Collections.Generic.List`1[UserQuery+TestClass].Select(x => x.LastName)
    }
    
    class TestClass
    {
        [MyAttribute(nameof(LastName))]
        public string FirstName { get; set; }
        public string LastName { get; set; }
    }
    
    class MyAttribute : Attribute
    {
        public string MapTo { get; }
        public MyAttribute(string mapTo)
        {
            MapTo = mapTo;
        }
    }
    
    
    class MyRewriter : ExpressionVisitor
    {
        protected override Expression VisitMember(System.Linq.Expressions.MemberExpression node)
        {
            var att = node.Member.GetCustomAttribute<MyAttribute>();
            if (att != null)
            {
                var newMember = node.Expression.Type.GetProperty(att.MapTo);
                if (newMember != null)
                {
                    return Expression.Property(
                        Visit(node.Expression), // Its very important to remember to visit the inner expression
                        newMember);
                }
            }
    
            return base.VisitMember(node);
        }
    }
    

    You can run this in LinqPad to test it. This code assumes that you are mapping to a property.