2009-08-24 18 views
9

Lassen Sie uns sagen, dass wir eine Sammlung von Person haben ObjekteFiltering Sammlung mit LINQ

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

Und irgendwo im Code definiert Sammlung

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

Wir brauchen einen Filter haben, um die Sammlung zu filtern, und Geben Sie das Ergebnis an den Endbenutzer zurück. Lassen Sie uns sagen, dass wir eine Sammlung von Filtertyp

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

Und irgendwo im Code-Objekte haben wir haben

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

Also müssen wir den Inhalt der personsList Sammlung filtern, indem Sie in der userFilters Sammlung definierten Filter. Wo die Filter.FieldName == "PersonName" || Filter.FieldName == "PersonAddress". Wie kann ich das mit LINQ cool machen? Die Lösungen wie switch/case, oder können, denke ich, Erweiterungsmethode auf personsList sein, die aus dem FiledName die Eigenschaft der Person ermittelt, in die man schaut, bekannt sind. Etwas anderes ? Etwas schwierig :) Danke.

+0

Verwendet dies In-Memory-LINQ oder LinqToSql? – JustLoren

+0

Dies ist In-Memory LINQ. Ich muss eine Reihe von Objekten abfragen, die in der Sammlung mit Filtern definiert sind, die in einer anderen Sammlung definiert sind. Es gibt keine DB-Interaktion. – Tigran

Antwort

9

Sie können einen Lambda-Ausdruck bauen eine richtige Prädikat mit der Expression Klasse zu erstellen .

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

Mit dieser Helfer-Methode können Sie eine Erweiterungsmethode auf jedem IQueryable<T> Klasse erstellen, so sollte dies für jeden LINQ-Backend arbeiten:

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

..., die Sie wie folgt aufrufen können:

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

Wenn Sie IEnumerable<T> Sammlungen und unterstützen möchten, müssen Sie diese zusätzliche Erweiterungsmethode verwenden:

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

Beachten Sie, dass dies nur für String-Eigenschaften funktioniert. Wenn Sie nach Feldern filtern möchten, müssen Sie Expression.Property in Expression.Field (oder MakeMemberAccess) ändern. Wenn Sie andere Typen als Zeichenfolgeneigenschaften unterstützen müssen, müssen Sie dem Expression.Constant-Teil der CreateFilterExpression weitere Typinformationen bereitstellen Methode.

3

Sie können es über Reflexion tun:

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

(nicht kompiliert, aber das entlang dem richtigen Weg sein sollte)

+0

Cool! Ich weiß ehrlich gesagt nicht, ob ich diese Technik auf meinen echten Code anwenden werde, übrigens ist es sehr nett. Ich liebe generische Sachen, auch wenn es aus Performance-Gründen nicht so gut ist. Leider kann ich nicht für deine Antwort stimmen (mein Ruf ist unter 15), aber die Antwort ist definitiv großartig. – Tigran

+0

Beachten Sie, dass dies bei den meisten LINQ-Backends nicht funktioniert. Ich bezweifle sehr, dass LINK to SQL Reflektionsaufrufe in SQL übersetzen kann. – Ruben

+0

Ja, ich stimme zu. In meinem Fall brauche ich übrigens keine DB-Interaktion. – Tigran

0

würde ich eine Methode zur Filter Klasse hinzufügen zu überprüfen, ob die Filter erfüllt sind:

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

Sie dann es wie folgt verwenden:

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

Kannst du nicht einfach

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

Der Filter besteht aus der FieldName-Eigenschaft. Daher weiß ich nicht, ob der Benutzer nach PersonName oder PersonAddress filtern möchte oder ob es andere mögliche Eigenschaften der Person-Klasse gibt. – Tigran

Verwandte Themen