2011-05-25 112 views
3

我想创建动态拉姆达表达式,以便我可以使用一组过滤参数来过滤列表。这是我到目前为止有:动态递归拉姆达表达式

的表达式使用内置的方法波纹管,其中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(); 

这个工程只是正如所期望的那样,只包含简单类型的对象,但如果内部存在复杂类型,代码将不再工作。例如,假设我在Example类中有一个自定义类型Field的成员,并且Field有一个字符串属性Value。如果filter.PropertyName是'Field0'(字段类型),代码将工作得很好,但如果我有'Field0.Value',我会得到一个明显的错误,指出在类Example中没有名为'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();

我刚开始使用表达式,所以我真的不知道在这方面太多,但任何帮助将不胜感激。

感谢

回答

2

我意识到这是一个相当古老的职位,但我有相同的问题,在Mark Gravell发布的回答中发现了一些接近的东西here。我只是修改了它稍微满足我的需求及以下结果:

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); 
+0

它看起来可以工作,但我现在无法测试它。无论如何,我使用Microsoft提供的动态linq库解决了我的问题,如下所示:[在Linq-to-Entities中编写动态Linq查询](http://naspinski.net/post/Writing-Dynamic-Linq-Queries-in- Linq-to-Entities.aspx)与自定义表达式构建器相结合。 – 2011-10-25 06:11:25

+0

一个很好的解决方案!第一次实施并运行。 – paqogomez 2014-02-10 20:37:30

1

看一看ExpressionVisitor如下所述:Replacing the parameter name in the Body of an Expression

+0

从我看到那里,解决的办法是对表达式树与AND/OR操作数连接在一起,但这不是我需要的。然而,由于我对表情有点新,所以我不太确定,如果我错了,我会很感激一些进一步的解释。为了使事情更清楚,这是我使用的方法:http://www.kos-data.com/Blog/post/Building-Where-clause-dynamically-to-filter-collection-using-LINQ-and -Expression-Trees.aspx – 2011-05-27 07:58:22

1

我试图解决

,这样我可以过滤使用一组过滤参数列表

不是使用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”将在highLevelAndPowerfulSwordshighLevelOrPowerfulSwords将包含所有的球员,但“鼠标”。

+0

对不起,但是这个解决方案对我来说不起作用,因为它不是真正的动态。像这样使用Filter类迫使我定义很多组合,而我需要使用参数化过滤器集合。我不知道在编译时什么字段被过滤(简单或复杂),我收到什么值和使用什么过滤函数。这就是为什么我需要反射和递归构建表达式树。 – 2011-05-27 07:31:51

+0

对不起,没有意识到你需要在运行时自己构造过滤器。如果它不必非常快,那么创建一些Python或Ruby代码并使用IronPython/IronRuby执行它可能会更容易。 – 2011-05-27 13:56:12

0

您可以生成过滤器的每个元素的表达,使用下面的方法将它们合并成一个单一的表达:

public static Expression<Func<T, K>> CombineAnd<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b) 
    { 
     ParameterExpression firstParameter = a.Parameters.First(); 
     Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter); 
     return Expression.Lambda<Func<T, K>>(Expression.And(a.Body, b1.Body), firstParameter); 
    } 
    public static Expression<Func<T, K>> CombineOr<T, K>(Expression<Func<T, K>> a, Expression<Func<T, K>> b) 
    { 
     ParameterExpression firstParameter = a.Parameters.First(); 
     Expression<Func<T, K>> b1 = Expression.Lambda<Func<T, K>>(Expression.Invoke(b, firstParameter), firstParameter); 
     return Expression.Lambda<Func<T, K>>(Expression.Or(a.Body, b1.Body), firstParameter); 
    } 
+0

我不需要合并它们(或者至少我没有问题),我只需要获得完整的属性路径,正如我在第一篇文章中所述。 – 2011-05-27 08:01:02