1
Expression<Func<Tab, object>> includeExpressions = x => x.Columns;

With these lines of code

var expression = (MemberExpression)includeExpressions.Body;
string PropertyName = expression.Member.Name;

I am getting Property Name as "Columns". But here I have another Expression

Expression<Func<Tab, object>> includeExpressions = x => x.Columns.Select(y=>y.Options);

I want to get name as Columns.Options. Can anybody have idea about this ? I mean I want to get for child property as well.

theduck
  • 2,589
  • 13
  • 17
  • 23
  • Do you *just* want to support the pattern `c..Select(y => y.)`, or do you want to pick out names more generally from other patterns? – canton7 Nov 07 '19 at 12:40
  • There could be any patterns – Amrit Pannu Nov 07 '19 at 12:44
  • Well that's impossible in the general case. If you do `x.Columns.Furble(y => y.Options)`, how do you know that the method `Furble` goes through each column and calls its delegate on them? The implementation of `Furble` could be `selector(new Column())` – canton7 Nov 07 '19 at 12:46
  • Actually i have Classes and classed have navigation properties . So basically i need the name of the property through expression or if there is another way then it would help me too. like Tabs is a class which is navigating to Columns class as one to Many Relation. same as Column class navigating to Option Class – Amrit Pannu Nov 07 '19 at 12:50
  • Like EF's `Include()`? That just supports `Select` IIRC, not "any patterns" – canton7 Nov 07 '19 at 12:53
  • Yes . I want to implement Include method in my Repository Pattern . and I don't want to write static names for proprieties **public IQueryable Include(Expression> includeExpressions) { //var expression = (MemberExpression)includeExpressions.Body; //string PropertyName = expression.Member.Name; return dbSet.Include(includeExpressions).AsQueryable(); }.** – Amrit Pannu Nov 07 '19 at 12:56
  • I am calling this method as **var tabsDetail = await unitOfWork.TabRepository .Include(x=>x.Columns.Select(y=>y.Options)) .Include(x => x.Columns.Select(y => y.Options)) .ToListAsync();** – Amrit Pannu Nov 07 '19 at 12:57
  • And also i dont want to use **ThenInclude** – Amrit Pannu Nov 07 '19 at 12:58
  • I have no idea what `ThenInclude` is – canton7 Nov 07 '19 at 12:59
  • If you **ONLY** want to support `Select`, that makes life a bit easier – canton7 Nov 07 '19 at 12:59
  • Yes That i can do . I can restrict with **Select** Only – Amrit Pannu Nov 07 '19 at 13:03

1 Answers1

0

You can do something like this.

It uses an ExpressionVisitor to walk the expression, and try to follow member accesses (parent.Child). It knows about the "Select" method, and that the lambda is called once for each item in the input.

We end up with a set of parent -> child relationships: from x you access x.Columns, from x.Columns you call Select, from the Select you map the result of x.Columns onto the y parameter, from the y parameter you call y.Options, etc. We store these in a dictionary as a parent/child pair (along with the name, if it was a member access).

We then recursively walk the dictionary starting from the includeExpressions, and create chains of parent/child accesses.

public class PropertyAccessVisitor : ExpressionVisitor
{
    public Dictionary<Expression, List<(Expression child, string name)>> ParentsToChildren { get; } = new Dictionary<Expression, List<(Expression child, string name)>>();

    protected override Expression VisitMethodCall(MethodCallExpression node)
    {
        // If it's a call to Select, then we know that it calls its lambda on each of the items in its input
        if (node.Method.Name == "Select" && node.Method.DeclaringType == typeof(Enumerable))
        {
            Add(node.Arguments[0], node.Arguments[1], null);
        }

        return base.VisitMethodCall(node);
    }

    protected override Expression VisitLambda<T>(Expression<T> node)
    {
        foreach (var parameter in node.Parameters)
        {
            Add(node, parameter, null);
        }

        return base.VisitLambda(node);
    }

    protected override Expression VisitMember(MemberExpression node)
    {
        Add(node.Expression, node, node.Member.Name);
        return base.VisitMember(node);
    }

    private void Add(Expression parent, Expression child, string name)
    {
        if (!ParentsToChildren.TryGetValue(parent, out var children))
        {
            children = new List<(Expression child, string name)>();
            ParentsToChildren[parent] = children;
        }
        children.Add((child, name));
    }
}

public static void Main()
{
    Expression<Func<Tab, object>> includeExpressions = x => x.Columns.Select(y => y.Options);
    var visitor = new PropertyAccessVisitor();
    visitor.Visit(includeExpressions);

    var results = new List<string>();
    var stack = new Stack<(Expression expression, string path)>();
    stack.Push((includeExpressions, null));
    while (stack.Count > 0)
    {
        var (expression, path) = stack.Pop();
        if (visitor.ParentsToChildren.TryGetValue(expression, out var children))
        {
            foreach (var child in children)
            {
                stack.Push((child.child, child.name == null ? path : $"{path}.{child.name}"));
            }
        }
        else
        {
            results.Add(path);
        }
    }
}
canton7
  • 37,633
  • 3
  • 64
  • 77