1

I have an expression of this form:

Expression<Func<ShowParticipant, bool>> expr = z => z.Show.OrgName == "xyz";

I need to convert/expand it to following form:

Expression<Func<ShowParticipant, bool>> expr = z => z.Show.Organization.Name == "xyz";

where OrgName property on Show entity resolves to Organization.Name. How can I achieve this assuming I need this to work in EF? You can imagine OrgName defined in Show class as below -

public partial class Show
{
    public string OrgName
    {
        get
        {
            return this.Organization.Name;
        }
        set
        {
            this.Organization.Name = value;
        }
    }
}

Appreciate your response,

Anand.

Anand
  • 73
  • 7
  • Is there a reason you can't just write z.Show.Organization.Name to begin with? – Ken Wayne VanderLinde Feb 17 '11 at 23:36
  • I want to expose friendly properties on Show object which will let end user to work with Show class easy. End user can potentially write expressions to query on those properties which I want to flatten out before I execute them. – Anand Feb 17 '11 at 23:45
  • I see. I recommend you use the Facade pattern. I can see only pain down the Expression route. –  Feb 18 '11 at 05:26
  • What you are asking is equivalent to [converting a .net Func to a .net Expression>](http://stackoverflow.com/questions/767733/converting-a-net-funct-to-a-net-expressionfunct) – Ani Feb 18 '11 at 06:08

2 Answers2

1

By itself, expression trees can't do this as there's no way for an expression tree to know what calling the OrgName property does under the covers.

However if you were to put an attribute on the property, perhaps then some factory could replace a call to property1 ("OrgName") with the property path Organization.Name

Sample code below.

    static void Main(string[] args)
    {
        Company company = new Company { Organization = { Name = "Microsoft" } };
        Expression<Func<Company, int>> lambdaExpression = c => c.OrgName.Length;
        var expanded = Expand(lambdaExpression);
    }

    private static Expression<Func<TSource, TResult>> Expand<TSource, TResult>(Expression<Func<TSource, TResult>> lambdaExpression)
    {
        Expression expanded = GetExpandedExpression(lambdaExpression.Body);
        if (Object.ReferenceEquals(lambdaExpression.Body, expanded))
        {
            return lambdaExpression;
        }
        return Expression.Lambda<Func<TSource, TResult>>(
            expanded,
            lambdaExpression.Parameters
        );
    }

    private static Expression GetExpandedExpression(Expression expression)
    {
        Expression expandedContainer;
        switch (expression.NodeType)
        {
            case ExpressionType.MemberAccess:
                MemberExpression memberExpression = (MemberExpression)expression;
                if (memberExpression.Expression != null)
                {
                    expandedContainer = GetExpandedExpression(memberExpression.Expression);
                    PropertyPathAttribute attribute = memberExpression.Member.GetCustomAttributes(typeof(PropertyPathAttribute), false).Cast<PropertyPathAttribute>().FirstOrDefault();
                    if (attribute != null && !String.IsNullOrEmpty(attribute.Path))
                    {
                        string[] parts = attribute.Path.Split('.');
                        expression = expandedContainer;
                        for (int i = 0; i < parts.Length; i++)
                        {
                            string part = parts[i];
                            expression = Expression.MakeMemberAccess(
                                expression,
                                (MemberInfo)expression.Type.GetProperty(part, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic) ??
                                    expression.Type.GetField(part, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)
                            );
                        }
                    }
                    else if (!Object.ReferenceEquals(expandedContainer, memberExpression.Expression))
                    {
                        return Expression.MakeMemberAccess(
                            expandedContainer,
                            memberExpression.Member
                        );
                    }
                }
                break;
            case ExpressionType.ArrayLength:
                UnaryExpression unaryExpression = (UnaryExpression)expression;
                if (!Object.ReferenceEquals(expandedContainer = GetExpandedExpression(unaryExpression.Operand), unaryExpression.Operand))
                {
                    return Expression.ArrayLength(expandedContainer);
                }
                break;
        }
        return expression;
    }

    [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field, AllowMultiple = false)]
    public sealed class PropertyPathAttribute : Attribute
    {
        private readonly string _path;

        public string Path
        {
            get
            {
                return this._path;
            }
        }

        public PropertyPathAttribute(string path)
        {
            this._path = path;
        }
    }

    public class Organization
    {
        public string Name { get; set; }
    }

    public class Company
    {
        [PropertyPath("Organization.Name")]
        public string OrgName
        {
            get
            {
                return this.Organization.Name;
            }
            set
            {
                this.Organization.Name = value;
            }
        }

        public Organization Organization { get; private set; }

        public Company()
        {
            this.Organization = new Organization();
        }
    }
Double Down
  • 898
  • 6
  • 13
0

Thanks for your reply. This exactly answers my question. In mean time I was coming up with solution using ExpressionVisitor. It's not complete but I am giving approach I took here -

public class ExpressionExpander : ExpressionVisitor
{
    private Dictionary<Expression, Expression> parameterMap = new Dictionary<Expression, Expression>();

    protected override Expression VisitParameter(ParameterExpression node)
    {
        if (parameterMap.ContainsKey(node))
            return parameterMap[node];

        return node;
    }

    protected override Expression VisitMember(MemberExpression node)
    {
            var newObj = Visit(node.Expression);
            if (NeedsInlineExpansion(newObj.Type, node.Member.Name))
            {
                LambdaExpression exp = GetPropertyTransform();

                if (parameterMap.ContainsKey(node.Expression))
                    parameterMap.Add(exp.Parameters[0], node.Expression);

                var visitedExp = Visit(exp.Body);
                var memExp = (MemberExpression)visitedExp;

                parameterMap.Add(node, memExp);
                return memExp;
            }
            else
            {
                var newMember = newObj.Type.GetMember(node.Member.Name).First();
                var newMemberAccessExpr = Expression.MakeMemberAccess(newObj, newMember);
                parameterMap.Add(node, newMemberAccessExpr);
                return newMemberAccessExpr;
            }
    }

    private bool NeedsInlineExpansion(Type type, string coreMemberName)
    {
        // Figure out way to determine if the property needs to be flattened out...
        // may be using attribute on Property
    }

    private LambdaExpression GetPropertyTransform()
    {
        // this is hardcoded right now, but represents some mechanism of getting a lambda 
        // returned for property in question...
        Expression<Func<Show, string>> exp = z => z.Organization.Name;
        var lambda = (LambdaExpression)exp;
        return lambda;
    }
}

And on caller would look like below -

        Expression<Func<ShowParticipant, bool>> expr1 = z => z.Show.OrgName == "xyz";
        ExpressionExpander expander = new ExpressionExpander();
        var inlineExpanded = expander.Visit(expr1);

Thanks, Anand.

Anand
  • 73
  • 7