2009-08-24 153 views
9

比方說,我們有一個Person的對象集合過濾收集與LINQ

class Person 
{ 
    public string PersonName {get;set;} 
    public string PersonAddress {get;set;}  
} 
代碼中定義集合中

而且某處

List<Person> pesonsList = new List<Person>(); 

我們需要有一個需要過濾收集過濾器和將結果返回給最終用戶。比方說,我們有過濾器類型對象的集合

class Filter 
{ 
    public string FieldName {get;set;} 
    public string FilterString {get;set;} 
} 

而且某處代碼,我們有

List<Filter> userFilters = new List<Filter>(); 

因此,我們需要通過在userFilters集合定義的過濾器的personsList集合的內容進行過濾。凡Filter.FieldName ==「PersonName」|| Filter.FieldName ==「PersonAddress」。我怎麼用冷靜的方式用LINQ來做到這一點?像switch/case或 這樣的解決方案可能是,我認爲,在PersonList上的擴展方法是根據FiledName確定要查找的Person的屬性,是已知的。還有別的嗎?一些棘手的東西:) 謝謝。

+0

這是使用內存中的LINQ或LinqToSql嗎? – JustLoren 2009-08-24 21:06:54

+0

這是內存中的LINQ。我需要使用其他集合中定義的過濾器來查詢集合中定義的一組對象。沒有任何數據庫交互。 – Tigran 2009-08-25 06:51:06

回答

9

您可以構建一個lambda表達式創建使用Expression類適當的謂詞。

public static Expression<Func<TInput, bool>> CreateFilterExpression<TInput>(
                IEnumerable<Filter> filters) 
{ 
    ParameterExpression param = Expression.Parameter(typeof(TInput), ""); 
    Expression lambdaBody = null; 
    if (filters != null) 
    { 
     foreach (Filter filter in filters) 
     { 
      Expression compareExpression = Expression.Equal(
        Expression.Property(param, filter.FieldName), 
        Expression.Constant(filter.FilterString)); 
      if (lambdaBody == null) 
       lambdaBody = compareExpression; 
      else 
       lambdaBody = Expression.Or(lambdaBody, compareExpression); 
     } 
    } 
    if (lambdaBody == null) 
     return Expression.Lambda<Func<TInput, bool>>(Expression.Constant(false)); 
    else 
     return Expression.Lambda<Func<TInput, bool>>(lambdaBody, param); 
} 

有了這個helper方法,你可以創建任何IQueryable<T>類的擴展方法,所以這應該爲每個LINQ後端工作:

public static IQueryable<T> Where<T>(this IQueryable<T> source, 
              IEnumerable<Filter> filters) 
{ 
    return Queryable.Where(source, CreateFilterExpression<T>(filters)); 
} 

...你可以這樣調用:

var query = context.Persons.Where(userFilters); 

如果你想支持IEnumerable<T>集合,以及,你需要使用這種額外的擴展方法:

public static IEnumerable<T> Where<T>(this IEnumerable<T> source, 
              IEnumerable<Filter> filters) 
{ 
    return Enumerable.Where(source, CreateFilterExpression<T>(filters).Compile()); 
} 

請注意,這隻適用於字符串屬性。如果要過濾字段,則需要將Expression.Property更改爲Expression.Field(或MakeMemberAccess),並且如果需要支持字符串屬性以外的其他類型,則必須向CreateFilterExpressionExpression.Constant部分提供更多類型信息方法。

3

您可以通過反射做到這一點:

IQueryable<Person> filteredPersons = personsList.AsQueryable(); 
Type personType = typeof(Person); 
foreach(Filter filter in userFilters) { 
    filteredPersons = filteredPersons.Where(p => (string)personType.InvokeMember(filter.FieldName, BindingFlags.GetProperty, null, p, null) == filter.FilterString); 
} 

(編譯沒有,但是這應該是沿着正確的軌道)

+0

酷!說實話,我真的不知道,如果我將這個技術應用到我的真實代碼上,順便說一句,它是非常好的。我喜歡通用的東西,即使它不適合性能方面的原因。不幸的是,我無法爲您的答案投票(我的名聲在15歲以下),但答案絕對很棒。 – Tigran 2009-08-24 21:27:22

+0

請注意,這在大多數LINQ後端無法使用;我非常懷疑LINK to SQL將能夠將反射調用轉換爲SQL。 – Ruben 2009-08-24 21:39:28

+0

是的,我同意。順便說一下,就我而言,我不需要任何數據庫交互。 – Tigran 2009-08-25 06:56:44

0

我想一個方法添加到Filter類來檢查,如果過濾器是滿意:

class Filter 
{ 
    public string FieldName {get;set;} 
    public string FilterString {get;set;} 

    public bool IsSatisfied(object o) 
    { return o.GetType().GetProperty(FieldName).GetValue(o, null) as string == FilterString; 
} 

然後,您可以使用它像這樣:

var filtered_list = personsList.Where(p => userFilters.Any(f => f.IsSatisfied(p))); 
2

你就不能這樣做

personList.Where(x => x.PersonName == "YourNameHere").ToList() ? 
+0

過濾器由FieldName屬性組成,所以我不知道用戶是否想要過濾PersonName或PersonAddress,或者可能是Person類的其他一些可能的屬性。 – Tigran 2009-08-24 22:45:40