2009-08-24 17 views
9

Powiedzmy mamy zbiór Osoba obiektówFiltrowanie kolekcji z LINQ

class Person 
{ 
    public string PersonName {get;set;} 
    public string PersonAddress {get;set;}  
} 

A gdzieś w kodzie zdefiniowano kolekcji

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

Musimy mieć filtr, który trzeba filtrować kolekcję i zwróć wynik użytkownikowi końcowemu. Powiedzmy, że mamy zbiór filtrem typu obiektów

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

A gdzieś w kodzie mamy

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

Więc musimy filtrować zawartości kolekcji personsList przez filtry zdefiniowane w zbiorze userFilters. Gdzie Filter.FieldName == "PersonName" || Filter.FieldName == "PersonAddress". Jak mogę to zrobić z LINQ w fajny sposób? Rozwiązania takie jak switch/case czy mogą być, jak mi się zdawało, metodą rozszerzającą na peopleList, która określa z FiledName właściwość osoby, którą należy zbadać, są znane. Coś innego ? Coś trudnego :) Dziękuję.

+0

Czy używasz LINQ lub LinqToSql w pamięci? – JustLoren

+0

To jest LINQ w pamięci. Muszę wysłać zapytanie do zestawu obiektów zdefiniowanych w kolekcji za pomocą filtrów zdefiniowanych w innym zbiorze. Nie ma żadnej interakcji z DB. – Tigran

Odpowiedz

9

Można budować wyrażenia lambda do stworzenia właściwego orzecznik przy użyciu klasy 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); 
} 

Dzięki tej metodzie pomocnika, można utworzyć metodę rozszerzenia na dowolnej klasy IQueryable<T>, więc to powinno działać na każdym LINQ backend:

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

... co można nazwać tak:

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

Jeśli chcesz wesprzeć IEnumerable<T> kolekcje jak również, musisz skorzystać z tej dodatkowej metody rozszerzenie:

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

Należy zauważyć, że działa to tylko w przypadku właściwości łańcucha. Jeśli chcesz filtrować pola, musisz zmienić Expression.Property w Expression.Field (lub MakeMemberAccess), a jeśli potrzebujesz obsługi innych typów niż właściwości ciągów, będziesz musiał dostarczyć więcej informacji o typie do części Expression.Constant z CreateFilterExpression metoda.

3

Można to zrobić za pośrednictwem refleksji:

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); 
} 

(nie skompilowane, ale to powinno być po właściwej drodze)

+0

Fajnie! Nie wiem, szczerze mówiąc, jeśli zastosuję tę technikę na moim prawdziwym kodzie, tak przy okazji, jest to bardzo miłe. Uwielbiam ogólne rzeczy, nawet jeśli nie są tak dobre ze względu na wydajność. Niestety nie mogę głosować na twoją odpowiedź (moja reputacja jest poniżej 15 lat), ale odpowiedź jest zdecydowanie świetna. – Tigran

+0

Należy zauważyć, że to nie będzie działać na większości backendach LINQ; Bardzo wątpię, że LINK do SQL będzie w stanie tłumaczyć odwołania do odbicia na SQL. – Ruben

+0

Tak, zgadzam się. Przy okazji, w moim przypadku nie potrzebuję żadnej interakcji z DB. – Tigran

0

Chciałbym dodać metodę do klasy Filter aby sprawdzić, czy filtr jest spełnione:

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; 
} 

Następnie można go używać tak:

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

nie można po prostu zrobić

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

filtr składa się z właściwości FieldName, więc nie wiem, czy użytkownik chce filtrować pod nazwą PersonName lub PersonAddress, czy też może być innymi możliwymi właściwościami klasy Person. – Tigran

Powiązane problemy