Création de MemberAcces avec ExpressionTree vers un type complexe

c# expression expression-trees lambda linq

Question

Je voudrais créer dynamiquement une expression MemberAcess à un niveau plus profond que 1 (récursivement):

public class Job
{
    public string Name { get; set; }
    public int Salary { get; set; }
}
public class Employee
{
    public string Name { get; set; }
    public Job Job { get; set; }
}

Et je veux créer dynamiquement une liste de MemberAccesExpressions pour chaque propriété dans Employee et chaque propriété dans les membres complexes de Employee, le résultat devrait ressembler à ceci:

MemberAccesExpression[] {
    { e => e.Name },
    { e => e.Job.Name },
    { e => e.Job.Name }
}

Ceci est un pseudo code de ce que j'ai obtenu:

List list = new List();
public Expression<Func<TModel, dynamic>> CreateME<TModel>(TModel model)
{
        var type = typeof (TModel);
        var properties = type.GetProperties();
        foreach (var prop in properties)
        {
            // I want to ignore collections
            if (typeof(ICollection).IsAssignableFrom(prop.PropertyType)) continue;

            // Recall for complex property
            if (prop.PropertyType.Namespace != "System")
            {
                // CreateME(model, ) // THIS IS WHEN I DON'T KNOW WHAT TO DO
                continue;
            }

            var param = Expression.Parameter(type, "x");
            var memberAccess = Expression.PropertyOrField(param, prop.Name);
            list.Add(Expression.Lambda<Func<TModel, dynamic>>(memberAccess, param));
        }
}

Comment puis-je en faire une méthode récursive? J'ai pensé à ajouter un paramètre facultatif nommé (modèle TModel, Expression> baseMemberAccess = null) et à concaténer l'expression du membre avec baseMemberAccess si elle n'est pas nulle.

PS est le meilleur moyen de déterminer si un Type n'est pas un type atomique, alors ceci (prop.PropertyType.Namespace! = "System")? (pas int, float, string, etc ...)

Apprécie toute aide, Adam

Une édition pour essayer de la poser plus simplement:

Si je veux créer un arbre d'expression d'un accès membre à Employee.Name, voici ce que je fais:

        var param = Expression.Parameter(type, "x");
        var memberAccess = Expression.PropertyOrField(param, memberName);
        return Expression.Lambda<Func<TModel, TMember>>(memberAccess, param);

Quel est l'équivalent de ceci pour un accès membre à Employee.Job.Salary?

Réponse acceptée

public IEnumerable<Expression<Func<TModel, dynamic>>> CreateME<TModel>()
    {
        var stack = new Stack<StackItem>();
        var type = typeof(TModel);
        var parameterExpression = Expression.Parameter(type, "x");
        stack.Push(new StackItem(typeof(TModel), parameterExpression));

        while (stack.Count > 0)
        {
            var currentItem = stack.Pop();
            var properties = currentItem.PropertyType.GetProperties();
            foreach (var property in properties)
            {
                if (IsComplexProperty(property))
                    stack.Push(new StackItem(property.PropertyType, Expression.PropertyOrField(currentItem.AccessChainExpression, property.Name)));
                else
                {
                    yield return GetSimplePropertyExpression<TModel>(parameterExpression, currentItem.AccessChainExpression, property.Name);
                }
            }
        }


    }

    private static Expression<Func<TModel, dynamic>> GetSimplePropertyExpression<TModel>(ParameterExpression lhs, Expression accessChain, string propertyName)
    {
        var memberAccess = Expression.Convert(Expression.PropertyOrField(accessChain, propertyName), typeof(object));
        return Expression.Lambda<Func<TModel, dynamic>>(memberAccess, lhs);
    }

    private static bool IsComplexProperty(PropertyInfo p)
    {
        return !typeof (ICollection).IsAssignableFrom(p.PropertyType) && p.PropertyType.Namespace != "System";
    }


    class StackItem
    {
        public StackItem(Type propertyType, Expression accessChainExpression)
        {
            PropertyType = propertyType;
            AccessChainExpression = accessChainExpression;
        }

        public Type PropertyType { get; private set; }
        public Expression AccessChainExpression { get; private set; }
    }

Je suis sûr que cela peut être amélioré, mais cela devrait marcher.



Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi