Динамические рекурсивные лямбда-выражения - PullRequest
3 голосов
/ 25 мая 2011

Я хочу создать динамические лямбда-выражения, чтобы можно было фильтровать список, используя набор параметров фильтрации.Это то, что у меня есть:

Выражение построено с использованием методов ниже, где T - тип объекта списка

    public static Expression<Func<T, bool>> GetExpression<T>(IList<DynamicFilter> filters)
    {
        if (filters.Count == 0)
            return null;

        ParameterExpression param = Expression.Parameter(typeof(T), "t");
        Expression exp = null;

        if (filters.Count == 1)
            exp = GetExpression<T>(param, filters[0]);

        [...]

        return Expression.Lambda<Func<T, bool>>(exp, param);
    }

    private static Expression GetExpression<T>(ParameterExpression param, DynamicFilter filter)
    {
        MemberExpression member = Expression.Property(param, filter.PropertyName);
        ConstantExpression constant = Expression.Constant(filter.Value);

        [...]

        return Expression.Call(member, filterMethod, constant);
    }

Затем я вызываю

List<Example> list = ...;
var deleg = ExpressionBuilder.GetExpression<Example>(dynFiltersList).Compile();
list = list.Where(deleg).ToList();

Это работает так же, как и ожидалось с объектом, который содержит только простые типы, но если внутри есть сложные типы, код больше не работает.Например, допустим, у меня есть член пользовательского типа Field внутри класса Example, а Field имеет строковое свойство Value.Если бы filter.PropertyName было бы 'Field0' (типа Field), код работал бы просто отлично, но если бы у меня было 'Field0.Value', я бы получил очевидную ошибку, утверждая, что внутри нет свойства с именем 'Field0.Value'Пример класса.

Я попытался изменить метод построения выражений, например:

        MemberExpression member = null;
        if (filter.PropertyName.Contains('.'))
        {
            string[] props = filter.PropertyName.Split('.');

            ParameterExpression param1 = Expression.Parameter(typeof(T).GetProperty(props[0]).PropertyType, "t1");
            member = Expression.Property(param1, props[0]);
        }
        else
        {
            member = Expression.Property(param, filter.PropertyName);
        }

, но затем я получил ошибку Lambda parameter not in scope при компиляции выражения.Я вроде понимаю, почему я получаю эту ошибку, но я не знаю, как заставить это работать.

Суть в том, что мне нужно заставить метод построения выражений работать рекурсивно при формировании выражения MemberExpression.В конечном итоге мне нужно получить list = list.Where(deleg).ToList();, который переводится в нечто подобное list = list.Where(obj => obj.Field0.Value == 'something').ToList();

Я только начал работать с выражениями, поэтому я не очень много знаю в этой области, но любая помощь будетоценил.

Спасибо

Ответы [ 4 ]

2 голосов
/ 29 сентября 2011

Я понимаю, что это довольно старый пост, но у меня была точно такая же проблема, и я нашел что-то близкое в ответе, который Марк Гравелл опубликовал здесь . Я просто немного изменил его, чтобы удовлетворить свои потребности, и ниже приведен результат:

    private Expression GetDeepProperty(Expression parameter, string property)
    {
        var props = property.Split('.');
        var type = parameter.Type;

        var expr = parameter;
        foreach (var prop in props)
        {
            var pi = type.GetProperty(prop);
            expr = Expression.Property(expr, pi);
            type = pi.PropertyType;
        }

        return expr;
    }

Реализация:

var method = typeof (string).GetMethod("Contains", new Type[] {typeof (string)}, null);
var lambdaParameter = Expression.Parameter(typeof(TEntity), "te");
var filterExpression = Expression.Lambda<Func<TEntity, bool>> (
            filters.Select(filter => Expression.Call(GetDeepProperty(lambdaParameter, filter.Property),
                                                      method,
                                                      Expression.Constant(filter.Value))).
                Where(exp => exp != null).
                Cast<Expression>().
                ToList().
                Aggregate(Expression.Or), lambdaParameter);
1 голос
/ 26 мая 2011

Я пытаюсь обратиться к

, чтобы я мог отфильтровать список, используя набор параметров фильтрации

не с помощью ExpressionBuilder, а с помощьюуниверсальный класс Filter.

public class Filter<T> where T: class
{
    private readonly Predicate<T> criteria;

    public Filter(Predicate<T> criteria)
    {
        this.criteria = criteria;
    }

    public bool IsSatisfied(T obj)
    {
        return criteria(obj);
    }
}

Сначала нам нужно несколько классов.

public class Player
{
    public string Name { get; set; }
    public int Level { get; set; }
    public enum Sex { Male, Female, Other };
    public Weapon Weapon { get; set; }
}

public class Weapon
{
    public string Name { get; set; }
    public int MaxDamage { get; set; }
    public int Range { get; set; }
    public WeaponClass Class { get; set; }

    public enum WeaponClass { Sword, Club, Bow }
}

Затем нам нужен список объектов.

var graywand = new Weapon { Name = "Graywand", MaxDamage = 42, Range = 1, Class = Weapon.WeaponClass.Sword };
var scalpel = new Weapon { Name = "Scalpel", MaxDamage = 33, Range = 1, Class = Weapon.WeaponClass.Sword };
var players = new List<Player> {
    new Player { Name = "Fafhrd", Level = 19, Weapon = graywand }, 
    new Player { Name = "Gray Mouser", Level = 19, Weapon = scalpel }, 
    new Player { Name = "Freddy", Level = 9, Weapon = graywand }, 
    new Player { Name = "Mouse", Level = 8, Weapon = scalpel} 
};

Тогда давайтесоздайте пару фильтров и добавьте их в список.

var powerfulSwords = new Filter<Player>(p => p.Weapon.MaxDamage>35);
var highLevels = new Filter<Player>(p => p.Level>15);

var filters = new List<Filter<Player>>();
filters.Add(powerfulSwords);
filters.Add(highLevels);

Наконец, отфильтруйте список по этим фильтрам

var highLevelAndPowerfulSwords = players.Where(p => filters.All(filter => filter.IsSatisfied(p)));
var highLevelOrPowerfulSwords = players.Where(p => filters.Any(filter => filter.IsSatisfied(p)));

Только "Fafhrd" будет в highLevelAndPowerfulSwords и highLevelOrPowerfulSwords будет содержать всех игроков, кроме «Мышки».

1 голос
/ 26 мая 2011

Посмотрите на ExpressionVisitor, как описано здесь: Замена имени параметра в теле выражения

0 голосов
/ 26 мая 2011

Вы можете сгенерировать выражение для каждого элемента в фильтре и объединить их в одно выражение, используя методы, приведенные ниже:

...