2011-05-25 110 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